--- /dev/null
+// SPDX-FileCopyrightText: 2024 Chris Duncan <chris@zoso.dev>
+// 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<any> {
+ return new Promise(resolve => {
+ if (this.#isAvailable) {
+ this.#post({ data, resolve })
+ } else {
+ this.#queue.push({ data, resolve })
+ }
+ })
+ }
+}
import { Bip39Mnemonic } from './bip39-mnemonic.js'\r
import { ADDRESS_GAP, SEED_LENGTH_BIP44, SEED_LENGTH_BLAKE2B } from './constants.js'\r
import { Entropy } from './entropy.js'\r
+import { Thread } from './thread.js'\r
import { Rpc } from './rpc.js'\r
import { Safe } from './safe.js'\r
import type { Ledger } from './ledger.js'\r
*/\r
export class Bip44Wallet extends Wallet {\r
static #isInternal: boolean = false\r
- #worker: Worker\r
+ #thread: Thread\r
\r
constructor (seed: string, mnemonic?: Bip39Mnemonic, id?: string) {\r
if (!Bip44Wallet.#isInternal) {\r
throw new Error(`Bip44Wallet cannot be instantiated directly. Use 'await Bip44Wallet.create()' instead.`)\r
}\r
super(seed, mnemonic, id)\r
- this.#worker = new Worker(new URL('./ckd.js', import.meta.url), { type: 'module' })\r
+ this.#thread = new Thread(new URL('./ckd.js', import.meta.url))\r
Bip44Wallet.#isInternal = false\r
}\r
\r
* @returns {Promise<Account>}\r
*/\r
async ckd (index: number): Promise<Account> {\r
- return new Promise(resolve => {\r
- this.#worker.addEventListener('message', (message) => {\r
- const key = message.data ?? message\r
- if (typeof key !== 'string') {\r
- throw new TypeError('BIP-44 child key derivation returned invalid data')\r
- }\r
- Account.fromPrivateKey(key, index).then(resolve)\r
- }, { once: true })\r
- this.#worker.postMessage({ type: 'bip44', seed: this.seed, index })\r
- })\r
+ const key = await this.#thread.work({ type: 'bip44', seed: this.seed, index })\r
+ if (typeof key !== 'string') {\r
+ throw new TypeError('BIP-44 child key derivation returned invalid data')\r
+ }\r
+ return Account.fromPrivateKey(key, index)\r
}\r
}\r
\r
*/\r
export class Blake2bWallet extends Wallet {\r
static #isInternal: boolean = false\r
- #worker: Worker\r
+ #thread: Thread\r
\r
constructor (seed: string, mnemonic?: Bip39Mnemonic, id?: string) {\r
if (!Blake2bWallet.#isInternal) {\r
throw new Error(`Blake2bWallet cannot be instantiated directly. Use 'await Blake2bWallet.create()' instead.`)\r
}\r
super(seed, mnemonic, id)\r
- this.#worker = new Worker(new URL('./ckd.js', import.meta.url), { type: 'module' })\r
+ this.#thread = new Thread(new URL('./ckd.js', import.meta.url))\r
Blake2bWallet.#isInternal = false\r
}\r
\r
* @returns {Promise<Account>}\r
*/\r
async ckd (index: number): Promise<Account> {\r
- return new Promise(resolve => {\r
- this.#worker.addEventListener('message', (message) => {\r
- const key = message.data ?? message\r
- if (typeof key !== 'string') {\r
- throw new TypeError('BLAKE2b child key derivation returned invalid data')\r
- }\r
- Account.fromPrivateKey(key, index).then(resolve)\r
- }, { once: true })\r
- this.#worker.postMessage({ type: 'blake2b', seed: this.seed, index })\r
- })\r
+ const key = await this.#thread.work({ type: 'blake2b', seed: this.seed, index })\r
+ if (typeof key !== 'string') {\r
+ throw new TypeError('BLAKE2b child key derivation returned invalid data')\r
+ }\r
+ return Account.fromPrivateKey(key, index)\r
}\r
}\r
\r