From 78364181f4a6becc54ee18bcac213acae9a068e0 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Fri, 26 Jun 2026 14:58:15 -0700 Subject: [PATCH] Refactor vault tasks to use map of IDs instead of a serial queue. Improve worker transaction ID with random string instead of performance timer. Simplify vault result processing. Resolve lock/unlock with value-wrapped object to be consistent with other vault results. --- src/lib/vault/index.ts | 120 +++++++++++++++------------------- src/lib/vault/vault-worker.ts | 33 ++++------ 2 files changed, 68 insertions(+), 85 deletions(-) diff --git a/src/lib/vault/index.ts b/src/lib/vault/index.ts index dd80441..e020c39 100644 --- a/src/lib/vault/index.ts +++ b/src/lib/vault/index.ts @@ -7,7 +7,7 @@ import { Data } from '../database' type TaskData = { [key: string]: Data | Record url: string - id: number + id: string } type Task = { @@ -27,7 +27,7 @@ export class Vault { #job?: Task #isLocked: boolean = true #isTerminated: boolean = false - #queue: Task[] = [] + #tasks: Map = new Map() #url: string #worker: NodeWorker | Worker @@ -37,20 +37,24 @@ export class Vault { removeEventListener = this.#eventTarget.removeEventListener.bind(this.#eventTarget) constructor () { - BROWSER: this.#url = URL.createObjectURL(new Blob([vaultWorker], { type: 'text/javascript' })) - BROWSER: this.#worker = new Worker(this.#url, { type: 'module' }) - BROWSER: this.#worker.addEventListener('message', message => { + const listener = (message: MessageEvent) => { + console.log('host listener(message)', message, message?.data) this.#report(message.data) - }) - NODE: this.#worker = new NodeWorker(vaultWorker, { - eval: true, - stderr: false, - stdout: false - }) - NODE: this.#worker.on('message', message => { - this.#report(message) - }) - NODE: this.#url = this.#worker.threadId.toString() + } + BROWSER: { + this.#url = URL.createObjectURL(new Blob([vaultWorker], { type: 'text/javascript' })) + this.#worker = new Worker(this.#url, { type: 'module' }) + this.#worker.addEventListener('message', listener) + } + NODE: { + this.#worker = new NodeWorker(vaultWorker, { + eval: true, + stderr: false, + stdout: false + }) + this.#url = this.#worker.threadId.toString() + this.#worker.on('message', listener) + } } get isLocked (): boolean { return this.#isLocked } @@ -59,10 +63,17 @@ export class Vault { if (this.#isTerminated) { throw new Error(TERMINATED) } + const buffers: ArrayBuffer[] = [] + for (const d of Object.values(payload)) { + if (d instanceof ArrayBuffer) { + buffers.push(d) + } + } + const id = crypto.randomUUID() const data: TaskData = { ...payload, url: this.#url, - id: performance.now(), + id } return new Promise((resolve, reject): void => { const task: Task = { @@ -70,8 +81,14 @@ export class Vault { resolve, reject } - this.#queue.push(task) - if (this.#job == null) this.#process() + this.#tasks.set(id, task) + try { + console.log('host postMessage(data)', data) + BROWSER: this.#worker.postMessage(data, buffers) + NODE: this.#worker.postMessage({ data }, buffers) + } catch (err) { + reject(err) + } }) } @@ -83,61 +100,32 @@ export class Vault { NODE: this.#worker.unref() this.#job?.reject(TERMINATED) this.#job = undefined - for (const task of this.#queue) { + for (const [_, task] of this.#tasks) { task?.reject?.(TERMINATED) } - this.#queue = [] + this.#tasks.clear() } - #process = (): void => { - this.#job = this.#queue.shift() - if (this.#job != null) { - const { data, reject } = this.#job - const buffers: ArrayBuffer[] = [] - for (const d of Object.values(data)) { - if (d instanceof ArrayBuffer) { - buffers.push(d) - } + #report (results: unknown): void { + if (results == null) return + const { url, id, error, isLocked } = results as Record + if (url === this.#url) { + if (typeof id !== 'string') { + throw new Error('Vault worker job invalid ID') } - try { - BROWSER: this.#worker.postMessage(data, buffers) - NODE: this.#worker.postMessage({ data }, buffers) - } catch (err) { - reject(err) - } - } - } - - #report (results: any): void { - if (results === LOCKED || results === UNLOCKED) { - const isLocked = results === LOCKED - if (this.#isLocked !== isLocked) { + if (typeof isLocked === 'boolean' && this.#isLocked !== isLocked) { this.#isLocked = isLocked - this.dispatchEvent(new Event(results)) + this.dispatchEvent(new Event(isLocked ? LOCKED : UNLOCKED)) } - return - } - if (this.#job == null) { - throw new Error('Vault worker returned results without an associated job.') - } - const { data: { url, id }, resolve, reject } = this.#job - if (url == null) { - throw new Error('Vault worker job missing URL') - } - if (id == null) { - throw new Error('Vault worker job missing ID') - } - if (results?.url === url && results?.id === id) { - try { - if (results?.error != null) { - reject(results) - } else { - resolve(results) - } - } catch (err) { - reject(err) - } finally { - this.#process() + const task = this.#tasks.get(id) + if (task == null) { + throw new Error('Vault worker returned results without an associated job.') + } + const { resolve, reject } = task + if (error != null) { + reject(results) + } else { + resolve(results) } } } diff --git a/src/lib/vault/vault-worker.ts b/src/lib/vault/vault-worker.ts index 8ad0c25..3573311 100644 --- a/src/lib/vault/vault-worker.ts +++ b/src/lib/vault/vault-worker.ts @@ -29,7 +29,7 @@ const listener = (event: MessageEvent): void => { } const data = event.data as Record const { url, id } = data - if (typeof id !== 'number' && typeof id !== 'string') return + if (typeof id !== 'string') return BROWSER: if (url !== location.href) return NODE: if (url !== threadId.toString()) return NODE: if (parentPort == null) setTimeout(() => listener(event), 0) @@ -70,7 +70,7 @@ const listener = (event: MessageEvent): void => { return update(key, keySalt) } case 'verify': { - return Promise.resolve(verify(seed, mnemonicPhrase)) + return verify(seed, mnemonicPhrase) } default: { throw new Error(`Unknown wallet action '${action}'`) @@ -91,8 +91,8 @@ const listener = (event: MessageEvent): void => { result.id = id console.log('worker postMessage(result)', result) //@ts-expect-error - BROWSER: postMessage(result, transfer) - NODE: parentPort?.postMessage(result, transfer) + BROWSER: self.postMessage(result, transfer) + NODE: parentPort?.postMessage({ data: result }, transfer) }) .catch((err: any) => { for (let data of Object.values(event.data)) { @@ -102,11 +102,11 @@ const listener = (event: MessageEvent): void => { data = undefined } console.log('worker postMessage(error)', err) - BROWSER: postMessage({ url, id, error: 'Failed to process Vault request', cause: err }) - NODE: parentPort?.postMessage({ url, id, error: 'Failed to process Vault request', cause: err }) + BROWSER: self.postMessage({ url, id, error: 'Failed to process Vault request', cause: err }) + NODE: parentPort?.postMessage({ data: { url, id, error: 'Failed to process Vault request', cause: err } }) }) } -BROWSER: addEventListener('message', listener) +BROWSER: self.addEventListener('message', listener) NODE: parentPort?.on('message', listener) /** @@ -220,14 +220,12 @@ async function load (type?: WalletType, key?: CryptoKey, keySalt?: ArrayBuffer, .finally(() => lock()) } -async function lock (): Promise { +async function lock (): Promise> { _mnemonic = undefined _seed = undefined _locked = true _timer?.pause() - BROWSER: postMessage('locked') - NODE: parentPort?.postMessage('locked') - return Promise.resolve() + return Promise.resolve({ isLocked: true }) } /** @@ -267,15 +265,13 @@ async function sign (index?: number, data?: ArrayBuffer): Promise { +async function unlock (type?: WalletType, key?: CryptoKey, iv?: ArrayBuffer, encrypted?: ArrayBuffer): Promise> { if (type == null) { throw new TypeError('Wallet type is required') } if (type === 'Ledger') { _locked = false - BROWSER: postMessage('unlocked') - NODE: parentPort?.postMessage('unlocked') - return Promise.resolve() + return Promise.resolve({ isLocked: false }) } if (key == null) { throw new TypeError('Wallet password is required') @@ -300,8 +296,7 @@ async function unlock (type?: WalletType, key?: CryptoKey, iv?: ArrayBuffer, enc _mnemonic = mnemonic _locked = false _timer = new VaultTimer(lock, _timeout) - BROWSER: postMessage('unlocked') - NODE: parentPort?.postMessage('unlocked') + return Promise.resolve({ isLocked: false }) }) .catch(err => { console.error(err) @@ -344,7 +339,7 @@ async function update (key?: CryptoKey, salt?: ArrayBuffer): Promise { +function verify (seed?: ArrayBuffer, mnemonicPhrase?: string): Promise> { try { if (_locked) { throw new Error('Wallet is locked') @@ -377,7 +372,7 @@ function verify (seed?: ArrayBuffer, mnemonicPhrase?: string): Record