From: Chris Duncan Date: Sun, 17 Nov 2024 07:21:04 +0000 (-0800) Subject: Reintroduce former Pool as Thread so that ckd tasks can be managed in a queue. X-Git-Tag: v0.0.20~15 X-Git-Url: https://zoso.dev/?a=commitdiff_plain;h=4a6affc51fd250bbc01815053cad4d8fa574375b;p=libnemo.git Reintroduce former Pool as Thread so that ckd tasks can be managed in a queue. --- diff --git a/src/lib/thread.ts b/src/lib/thread.ts new file mode 100644 index 0000000..79addc4 --- /dev/null +++ b/src/lib/thread.ts @@ -0,0 +1,59 @@ +// SPDX-FileCopyrightText: 2024 Chris Duncan +// SPDX-License-Identifier: GPL-3.0-or-later + +if (globalThis.Worker == null) { + const { Worker } = await import('node:worker_threads') + //@ts-expect-error + Worker.prototype.addEventListener = Worker.prototype.addListener + //@ts-expect-error + globalThis.Worker = Worker +} + +type Task = { + data: object, + resolve: Function +} + +/** +* Processes tasks from a queue using a Web Worker. +*/ +export class Thread { + #isAvailable: boolean = true + #queue: Task[] = [] + #task?: Task + #worker: Worker + + #post (next: Task) { + this.#isAvailable = false + this.#task = next + this.#worker.postMessage(next.data) + } + + constructor (url: string | URL) { + this.#worker = new Worker(new URL(url, import.meta.url), { type: 'module' }) + this.#worker.addEventListener('message', (event) => { + const result = event.data ?? event + if (this.#task == null) { + throw new ReferenceError(`Error resolving Worker result: ${result}`) + } + const resolve = this.#task.resolve + const next = this.#queue.shift() + if (next == null) { + this.#isAvailable = true + } else { + this.#post(next) + } + resolve(result) + }) + } + + async work (data: object): Promise { + return new Promise(resolve => { + if (this.#isAvailable) { + this.#post({ data, resolve }) + } else { + this.#queue.push({ data, resolve }) + } + }) + } +} diff --git a/src/lib/wallet.ts b/src/lib/wallet.ts index 8d8fa80..1a4f5cc 100644 --- a/src/lib/wallet.ts +++ b/src/lib/wallet.ts @@ -5,6 +5,7 @@ import { Account } from './account.js' import { Bip39Mnemonic } from './bip39-mnemonic.js' import { ADDRESS_GAP, SEED_LENGTH_BIP44, SEED_LENGTH_BLAKE2B } from './constants.js' import { Entropy } from './entropy.js' +import { Thread } from './thread.js' import { Rpc } from './rpc.js' import { Safe } from './safe.js' import type { Ledger } from './ledger.js' @@ -230,14 +231,14 @@ abstract class Wallet { */ export class Bip44Wallet extends Wallet { static #isInternal: boolean = false - #worker: Worker + #thread: Thread constructor (seed: string, mnemonic?: Bip39Mnemonic, id?: string) { if (!Bip44Wallet.#isInternal) { throw new Error(`Bip44Wallet cannot be instantiated directly. Use 'await Bip44Wallet.create()' instead.`) } super(seed, mnemonic, id) - this.#worker = new Worker(new URL('./ckd.js', import.meta.url), { type: 'module' }) + this.#thread = new Thread(new URL('./ckd.js', import.meta.url)) Bip44Wallet.#isInternal = false } @@ -389,16 +390,11 @@ export class Bip44Wallet extends Wallet { * @returns {Promise} */ async ckd (index: number): Promise { - return new Promise(resolve => { - this.#worker.addEventListener('message', (message) => { - const key = message.data ?? message - if (typeof key !== 'string') { - throw new TypeError('BIP-44 child key derivation returned invalid data') - } - Account.fromPrivateKey(key, index).then(resolve) - }, { once: true }) - this.#worker.postMessage({ type: 'bip44', seed: this.seed, index }) - }) + const key = await this.#thread.work({ type: 'bip44', seed: this.seed, index }) + if (typeof key !== 'string') { + throw new TypeError('BIP-44 child key derivation returned invalid data') + } + return Account.fromPrivateKey(key, index) } } @@ -420,14 +416,14 @@ export class Bip44Wallet extends Wallet { */ export class Blake2bWallet extends Wallet { static #isInternal: boolean = false - #worker: Worker + #thread: Thread constructor (seed: string, mnemonic?: Bip39Mnemonic, id?: string) { if (!Blake2bWallet.#isInternal) { throw new Error(`Blake2bWallet cannot be instantiated directly. Use 'await Blake2bWallet.create()' instead.`) } super(seed, mnemonic, id) - this.#worker = new Worker(new URL('./ckd.js', import.meta.url), { type: 'module' }) + this.#thread = new Thread(new URL('./ckd.js', import.meta.url)) Blake2bWallet.#isInternal = false } @@ -541,16 +537,11 @@ export class Blake2bWallet extends Wallet { * @returns {Promise} */ async ckd (index: number): Promise { - return new Promise(resolve => { - this.#worker.addEventListener('message', (message) => { - const key = message.data ?? message - if (typeof key !== 'string') { - throw new TypeError('BLAKE2b child key derivation returned invalid data') - } - Account.fromPrivateKey(key, index).then(resolve) - }, { once: true }) - this.#worker.postMessage({ type: 'blake2b', seed: this.seed, index }) - }) + const key = await this.#thread.work({ type: 'blake2b', seed: this.seed, index }) + if (typeof key !== 'string') { + throw new TypeError('BLAKE2b child key derivation returned invalid data') + } + return Account.fromPrivateKey(key, index) } }