From ed0c24f96664cc6a79de6d068adb3bd4809cf76a Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Tue, 16 Jun 2026 15:53:11 -0700 Subject: [PATCH] Use URL and random UUID to uniquely identify each async call and prevent crosstalk with other workers. --- src/lib/nano25519.ts | 60 +++++++++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/src/lib/nano25519.ts b/src/lib/nano25519.ts index 42b869a..3aea89b 100644 --- a/src/lib/nano25519.ts +++ b/src/lib/nano25519.ts @@ -1,11 +1,13 @@ //! SPDX-FileCopyrightText: 2026 Chris Duncan //! SPDX-License-Identifier: GPL-3.0-or-later -import { Worker as NodeWorker } from 'node:worker_threads' +import { Worker as NodeWorker, threadId } from 'node:worker_threads' //@ts-expect-error import nano25519_wasm from '../../build/nano25519.wasm' type Data = { + url: string + id: string action: string message?: string | ArrayBuffer privateKey?: string @@ -188,18 +190,30 @@ const nano25519_init = (bytes: number[]): { derive: typeof derive, sign: typeof function handleMessage (message: unknown): void { NODE: if (host == null) setTimeout(() => handleMessage(message), 0) let result: undefined | boolean | string | Uint8Array + let url: undefined | string + let id: undefined | string try { if (message == null || typeof message !== 'object' || !('data' in message) || message.data == null || typeof message.data !== 'object' + || !('url' in message.data) + || typeof message.data.url !== 'string' + || !('id' in message.data) + || typeof message.data.id !== 'string' || !('action' in message.data) || typeof message.data.action !== 'string' ) { throw new TypeError('Invalid nano25519Worker request') } - const data: Data = message.data as object & Record<"action", string> + const data: Data = message.data as object & { url: string, id: string, action: string } + url = data.url + id = data.id + if (typeof url !== 'string' || typeof id !== 'string') return + BROWSER: if (url !== location.href) return + NODE: if (url !== threadId.toString()) return + if (data.action === 'start') { isListening = true result = 'started' @@ -246,8 +260,8 @@ const nano25519_init = (bytes: number[]): { derive: typeof derive, sign: typeof result = JSON.stringify(err) } } finally { - BROWSER: postMessage(result) - NODE: host?.postMessage({ data: result }) + BROWSER: postMessage({ url, id, result }) + NODE: host?.postMessage({ data: { url, id, result } }) } } BROWSER: addEventListener('message', handleMessage) @@ -283,13 +297,14 @@ function isBytes (a: unknown): a is Uint8Array { // Create worker module function init (): void { try { - url = URL.createObjectURL(new Blob([nano25519_worker], { type: 'text/javascript' })) + BROWSER: url = URL.createObjectURL(new Blob([nano25519_worker], { type: 'text/javascript' })) BROWSER: worker = new Worker(url, { type: 'module' }) NODE: worker = new NodeWorker(nano25519_worker, { eval: true, stderr: false, stdout: false }) + NODE: url = worker.threadId.toString() console.log(`nano25519 initialized.`) isWorkerReady = true } catch (err) { @@ -311,14 +326,17 @@ async function start (): Promise { if (!isWorkerReady) init() if (!isWorkerListening) { return new Promise(async (resolve, reject): Promise => { - const onstarted = (msg: any): void => { - const result = msg.data + const onstarted = (msg: { data: Record }): void => { + const { data } = msg + if (data.url !== url) return + const { result } = data if (result === 'started') { console.log('worker started successfully') isWorkerListening = true - resolve(result) + resolve() } else { - reject(result) + isWorkerListening = false + reject() } } //@ts-expect-error @@ -330,8 +348,9 @@ async function start (): Promise { //@ts-expect-error NODE: worker.on('message', onstarted) console.log(`starting worker`) - BROWSER: worker.postMessage({ action: 'start' }) - NODE: worker.postMessage({ data: { action: 'start' } }) + const id = crypto.randomUUID() + BROWSER: worker.postMessage({ url, id, action: 'start' }) + NODE: worker.postMessage({ data: { url, id, action: 'start' } }) }) } } @@ -346,8 +365,10 @@ async function dispatch (data: { [key: string]: string | ArrayBuffer | Uint8Arra transfer.push(data[k]) } } - const onresult = (msg: Record<"data", unknown>): void => { - const result = msg.data + const onresult = (msg: { data: Record }): void => { + const { data } = msg + if (data.url !== url) return + const { result } = data console.log(`received result from worker: `, result) if (typeof result !== 'boolean' && typeof result !== 'string' && !isBytes(result)) { return reject('Invalid return type') @@ -363,6 +384,8 @@ async function dispatch (data: { [key: string]: string | ArrayBuffer | Uint8Arra //@ts-expect-error NODE: worker.on('message', onresult) console.log(`sending data to worker: `, data) + data.url = url + data.id = crypto.randomUUID() BROWSER: worker.postMessage(data, transfer) NODE: worker.postMessage({ data }, transfer) }) @@ -371,8 +394,10 @@ async function dispatch (data: { [key: string]: string | ArrayBuffer | Uint8Arra // Request that the worker stop listening without terminating async function stop (): Promise { return new Promise((resolve, reject): void => { - const onstop = (msg: any): void => { - const result = msg.data + const onstop = (msg: { data: Record }): void => { + const { data } = msg + if (data.url !== url) return + const { result } = data if (result === 'stopped') { console.log('worker stopped successfully') isWorkerListening = false @@ -390,8 +415,9 @@ async function stop (): Promise { //@ts-expect-error NODE: worker.on('message', onstop) console.log(`stopping worker`) - BROWSER: worker.postMessage({ action: 'stop' }) - NODE: worker.postMessage({ data: { action: 'stop' } }) + const id = crypto.randomUUID() + BROWSER: worker.postMessage({ url, id, action: 'stop' }) + NODE: worker.postMessage({ data: { url, id, action: 'stop' } }) }) } -- 2.52.0