From 7fdc8eac2f893ae0dc97be2a35a63e8251c6259c Mon Sep 17 00:00:00 2001 From: shum Date: Thu, 19 Feb 2026 09:00:36 +0000 Subject: [PATCH] fix worker message race: wait for ready signal before posting messages --- xftp-web/web/crypto-backend.ts | 22 ++++++++++++++++++++-- xftp-web/web/crypto.worker.ts | 5 ++++- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/xftp-web/web/crypto-backend.ts b/xftp-web/web/crypto-backend.ts index eb4155128..e1ed52b84 100644 --- a/xftp-web/web/crypto-backend.ts +++ b/xftp-web/web/crypto-backend.ts @@ -28,10 +28,27 @@ class WorkerBackend implements CryptoBackend { private pending = new Map() private nextId = 1 private progressCb: ((done: number, total: number) => void) | null = null + private ready: Promise constructor() { this.worker = new Worker(new URL('./crypto.worker.ts', import.meta.url), {type: 'module'}) - this.worker.onmessage = (e) => this.handleMessage(e.data) + let rejectReady: (e: Error) => void + this.ready = new Promise((resolve, reject) => { + rejectReady = reject + this.worker.onmessage = (e) => { + if (e.data?.type === 'ready') { + this.worker.onmessage = (e) => this.handleMessage(e.data) + resolve() + } else { + reject(new Error('Worker: unexpected first message')) + } + } + }) + this.worker.onerror = (e) => { + rejectReady(new Error('Worker failed to load: ' + e.message)) + for (const p of this.pending.values()) p.reject(new Error('Worker error: ' + e.message)) + this.pending.clear() + } } private handleMessage(msg: {id: number, type: string, [k: string]: any}) { @@ -49,7 +66,8 @@ class WorkerBackend implements CryptoBackend { } } - private send(msg: Record, transfer?: Transferable[]): Promise { + private async send(msg: Record, transfer?: Transferable[]): Promise { + await this.ready const id = this.nextId++ return new Promise((resolve, reject) => { this.pending.set(id, {resolve, reject}) diff --git a/xftp-web/web/crypto.worker.ts b/xftp-web/web/crypto.worker.ts index 39c2ba580..36301217e 100644 --- a/xftp-web/web/crypto.worker.ts +++ b/xftp-web/web/crypto.worker.ts @@ -256,9 +256,9 @@ async function handleCleanup(id: number) { // ── Message dispatch ──────────────────────────────────────────── self.onmessage = async (e: MessageEvent) => { - await initPromise const msg = e.data try { + await initPromise switch (msg.type) { case 'encrypt': await handleEncrypt(msg.id, msg.data, msg.fileName) @@ -302,3 +302,6 @@ const initPromise = (async () => { await sodium.ready await sweepStale() })() + +// Signal main thread that the worker is ready to receive messages +initPromise.then(() => self.postMessage({type: 'ready'}), () => {})