]> git.codecow.com Git - libnemo.git/commitdiff
Refactor types for data passed to workers and reduce back to one parameter with buffe...
authorChris Duncan <chris@zoso.dev>
Sun, 20 Jul 2025 10:31:43 +0000 (03:31 -0700)
committerChris Duncan <chris@zoso.dev>
Sun, 20 Jul 2025 10:31:43 +0000 (03:31 -0700)
23 files changed:
src/lib/account.ts
src/lib/blake2b.ts
src/lib/block.ts
src/lib/rpc.ts
src/lib/tools.ts
src/lib/wallets/bip44-wallet.ts
src/lib/wallets/wallet.ts
src/lib/workers/bip44-ckd.ts
src/lib/workers/index.ts
src/lib/workers/nano-nacl.ts
src/lib/workers/safe.ts
src/lib/workers/worker-interface.ts
src/lib/workers/worker-queue.ts [moved from src/lib/workers/queue.ts with 67% similarity]
src/types.d.ts
test/GLOBALS.mjs
test/perf.account.js
test/perf.block.js
test/perf.wallet.js
test/test.calculate-pow.mjs
test/test.ledger.mjs
test/test.refresh-accounts.mjs
test/test.runner-check.mjs
test/test.tools.mjs

index 8674c5e565497b09973759835e9dc6fb0eb536c8..7367d0fd21cb8d4f77de03c9aa9b7699173a4207 100644 (file)
@@ -6,7 +6,7 @@ import { ChangeBlock, ReceiveBlock, SendBlock } from './block'
 import { ACCOUNT_KEY_BYTE_LENGTH, ACCOUNT_KEY_HEX_LENGTH, ALPHABET, PREFIX, PREFIX_LEGACY } from './constants'\r
 import { base32, bytes, hex, utf8 } from './convert'\r
 import { Rpc } from './rpc'\r
-import { Data, Key, KeyPair } from '#types'\r
+import { Key, KeyPair, NamedData } from '#types'\r
 import { NanoNaClWorker, SafeWorker } from '#workers'\r
 \r
 /**\r
@@ -253,15 +253,13 @@ export class Account {
        async sign (block: ChangeBlock | ReceiveBlock | SendBlock, password: Key): Promise<string> {\r
                if (typeof password === 'string') password = utf8.toBytes(password)\r
                try {\r
-                       const headers = {\r
-                               method: 'detached'\r
-                       }\r
-                       const result = await NanoNaClWorker.assign(headers, {\r
+                       const { signature } = await NanoNaClWorker.assign<ArrayBuffer>({\r
+                               method: 'detached',\r
                                privateKey: (await this.#export(password)).buffer,\r
                                msg: hex.toBytes(block.hash).buffer\r
                        })\r
-                       block.signature = result\r
-                       return result\r
+                       block.signature = bytes.toHex(new Uint8Array(signature))\r
+                       return block.signature\r
                } catch (err) {\r
                        throw new Error(`Failed to sign block`, { cause: err })\r
                } finally {\r
@@ -329,15 +327,13 @@ export class Account {
                        throw new Error('Password must be string or bytes')\r
                }\r
                try {\r
-                       const headers = {\r
+                       const response = await SafeWorker.assign<ArrayBuffer>({\r
                                method: 'get',\r
                                name: this.publicKey,\r
-                               store: 'Account'\r
-                       }\r
-                       const response = await SafeWorker.assign(headers, {\r
+                               store: 'Account',\r
                                password: password.buffer\r
                        })\r
-                       return new Uint8Array(response[this.publicKey] as ArrayBuffer)\r
+                       return new Uint8Array(response[this.publicKey])\r
                } catch (err) {\r
                        throw new Error(`Failed to export private key for Account ${this.address}`, { cause: err })\r
                } finally {\r
@@ -361,7 +357,11 @@ export class Account {
                }\r
 \r
                const accounts: Account[] = []\r
-               const privateAccounts: Data = {}\r
+               const privateAccounts: NamedData = {\r
+                       method: 'set',\r
+                       store: 'Account',\r
+                       password: password.buffer\r
+               }\r
                for (let keypair of keypairs) {\r
                        let { index, privateKey } = keypair\r
                        if (index == null) {\r
@@ -370,14 +370,13 @@ export class Account {
                        this.#validateKey(privateKey)\r
                        if (typeof privateKey === 'string') privateKey = hex.toBytes(privateKey)\r
                        try {\r
-                               const headers = {\r
-                                       method: 'convert'\r
-                               }\r
-                               const publicKey = await NanoNaClWorker.assign(headers, {\r
+                               const result = await NanoNaClWorker.assign<ArrayBuffer>({\r
+                                       method: 'convert',\r
                                        privateKey: new Uint8Array(privateKey).buffer\r
                                })\r
-                               privateAccounts[publicKey] = privateKey.buffer\r
-                               const address = this.#keyToAddress(hex.toBytes(publicKey))\r
+                               const publicKey = new Uint8Array(result.publicKey)\r
+                               privateAccounts[bytes.toHex(publicKey)] = privateKey.buffer\r
+                               const address = this.#keyToAddress(publicKey)\r
                                this.#isInternal = true\r
                                accounts.push(new this(address, publicKey, index))\r
                        } catch (err) {\r
@@ -386,12 +385,7 @@ export class Account {
                }\r
 \r
                try {\r
-                       const headers = {\r
-                               method: 'set',\r
-                               store: 'Account'\r
-                       }\r
-                       privateAccounts.password = password.buffer\r
-                       const isLocked = await SafeWorker.assign(headers, privateAccounts)\r
+                       const isLocked = await SafeWorker.assign(privateAccounts)\r
                        if (!isLocked) {\r
                                throw null\r
                        }\r
index b9e25dfcfbec1cfb3244186b880b566a61127a1f..c4f07a178beffc298b30760a85c7e8a927dd1ced 100644 (file)
@@ -264,10 +264,10 @@ export class Blake2b {
                return this
        }
 
-       digest (): Uint8Array
+       digest (): Uint8Array<ArrayBuffer>
        digest (out: 'hex'): string
-       digest (out: 'binary' | Uint8Array): Uint8Array
-       digest (out?: 'binary' | 'hex' | Uint8Array): string | Uint8Array {
+       digest (out: 'binary' | Uint8Array<ArrayBuffer>): Uint8Array<ArrayBuffer>
+       digest (out?: 'binary' | 'hex' | Uint8Array<ArrayBuffer>): string | Uint8Array<ArrayBuffer> {
                const buf = (!out || out === 'binary' || out === 'hex') ? new Uint8Array(this.#outlen) : out
                if (!(buf instanceof Uint8Array)) throw new TypeError(`out must be "binary", "hex", Uint8Array, or Buffer`)
                if (buf.length < this.#outlen) throw new RangeError(`out must have at least outlen bytes of space`)
index 1eec2c98ef20ab8620cebc7c9ae4cfe0efc651e3..bc2d2b6fe753b1625520e1e1674df48b6bc5b77d 100644 (file)
@@ -191,14 +191,13 @@ abstract class Block {
                        throw new Error('Provide a key for block signature verification.')
                }
                try {
-                       const headers = {
-                               method: 'verify'
-                       }
-                       return await NanoNaClWorker.assign(headers, {
+                       const { isVerified } = await NanoNaClWorker.assign<boolean>({
+                               method: 'verify',
                                msg: hex.toBytes(this.hash).buffer,
                                signature: hex.toBytes(this.signature ?? '').buffer,
                                publicKey: hex.toBytes(key).buffer
                        })
+                       return isVerified
                } catch (err) {
                        throw new Error(`Failed to derive public key from private key`, { cause: err })
                }
index be6fc4c030abd8195bb77fd6b35c73a615553ad2..1fc8d25d5e41336aa43d90b659ecdba73dd4d587 100644 (file)
@@ -19,12 +19,13 @@ export class Rpc {
        }
 
        /**
-        *
-        * @param {string} action - Nano protocol RPC call to execute
-        * @param {object} [data] - JSON to send to the node as defined by the action
-        * @returns {Promise<any>} JSON-formatted RPC results from the node
-        */
-       async call (action: string, data?: { [key: string]: any }): Promise<any> {
+       * Sends a nano RPC call to a node endpoint.
+       *
+       * @param {string} action - Nano protocol RPC call to execute
+       * @param {object} [data] - JSON to send to the node as defined by the action
+       * @returns {Promise<any>} JSON-formatted RPC results from the node
+       */
+       async call (action: string, data?: { [key: string]: unknown }): Promise<any> {
                var process: any = process || null
                this.#validate(action)
                const headers: { [key: string]: string } = {}
index eeeaac24a4d7d79a04fd27069bad81b104b65c7b..a61bece73a98a0d9fb5c1b3f41ee5c85eb5a1682 100644 (file)
@@ -17,7 +17,8 @@ type SweepResult = {
        message: string
 }
 
-function hash (data: string | string[], encoding?: 'hex', format?: 'hex'): string | Uint8Array {
+function hash (data: string | string[], encoding?: 'hex'): Uint8Array<ArrayBuffer>
+function hash (data: string | string[], encoding?: 'hex', format?: 'hex'): string | Uint8Array<ArrayBuffer> {
        if (!Array.isArray(data)) data = [data]
        const hash = new Blake2b(32)
        if (encoding === 'hex') {
@@ -90,15 +91,13 @@ export async function convert (amount: bigint | string, inputUnit: string, outpu
 */
 export async function sign (key: Key, ...input: string[]): Promise<string> {
        if (typeof key === 'string') key = hex.toBytes(key)
-       let signature: string
        try {
-               const headers = {
-                       method: 'detached'
-               }
-               return await NanoNaClWorker.assign(headers, {
+               const { signature } = await NanoNaClWorker.assign<ArrayBuffer>({
+                       method: 'detached',
                        privateKey: key.buffer,
-                       msg: (hash(input) as Uint8Array<ArrayBuffer>).buffer
+                       msg: hash(input).buffer
                })
+               return bytes.toHex(new Uint8Array(signature))
        } catch (err) {
                throw new Error(`Failed to sign message with private key`, { cause: err })
        } finally {
@@ -178,14 +177,13 @@ export async function sweep (
 export async function verify (key: Key, signature: string, ...input: string[]): Promise<boolean> {
        if (typeof key === 'string') key = hex.toBytes(key)
        try {
-               const headers = {
-                       method: 'verify'
-               }
-               return await NanoNaClWorker.assign(headers, {
-                       msg: (hash(input) as Uint8Array<ArrayBuffer>).buffer,
+               const { isVerified } = await NanoNaClWorker.assign<boolean>({
+                       method: 'verify',
+                       msg: hash(input).buffer,
                        signature: hex.toBytes(signature).buffer,
                        publicKey: new Uint8Array(key).buffer
                })
+               return isVerified
        } catch (err) {
                throw new Error('Failed to verify signature', { cause: err })
        } finally {
index 8ea927690a23d9919afdbf2b690957492a35ef4d..1a1e36825692626f2edfa95e3c02bde788ee7e57 100644 (file)
@@ -212,10 +212,8 @@ export class Bip44Wallet extends Wallet {
        * @returns {Promise<Account>}\r
        */\r
        async ckd (indexes: number[]): Promise<KeyPair[]> {\r
-               const headers = {\r
-                       indexes\r
-               }\r
-               const results = await Bip44CkdWorker.assign(headers, {\r
+               const results = await Bip44CkdWorker.assign({\r
+                       indexes,\r
                        seed: hex.toBytes(this.seed).buffer\r
                })\r
                const privateKeys: KeyPair[] = []\r
index d6069b464b69bd94d81b8ae75f15a8642c371672..f14029bed922603db15f1c5278857ace1f68a854 100644 (file)
@@ -7,7 +7,7 @@ import { ADDRESS_GAP } from '#src/lib/constants.js'
 import { bytes, hex, utf8 } from '#src/lib/convert.js'\r
 import { Entropy } from '#src/lib/entropy.js'\r
 import { Rpc } from '#src/lib/rpc.js'\r
-import { Key, KeyPair } from '#types'\r
+import { Key, KeyPair, NamedData } from '#types'\r
 import { SafeWorker } from '#workers'\r
 \r
 /**\r
@@ -146,6 +146,7 @@ export abstract class Wallet {
                        method: 'destroy',\r
                        name: this.id\r
                })\r
+               // NODE: await SafeWorker.assign({ method: 'STOP' })\r
        }\r
 \r
        /**\r
@@ -169,11 +170,9 @@ export abstract class Wallet {
                                seed: this.seed\r
                        })\r
                        const encoded = utf8.toBytes(serialized)\r
-                       const headers = {\r
+                       const success = await SafeWorker.assign({\r
                                method: 'set',\r
-                               store: 'Wallet'\r
-                       }\r
-                       const success = await SafeWorker.assign(headers, {\r
+                               store: 'Wallet',\r
                                [this.id]: encoded.buffer,\r
                                password: password.buffer\r
                        })\r
@@ -232,15 +231,13 @@ export abstract class Wallet {
                        throw new Error('Failed to unlock wallet')\r
                }\r
                try {\r
-                       const headers = {\r
+                       const response = await SafeWorker.assign<ArrayBuffer>({\r
                                method: 'get',\r
                                name: this.id,\r
                                store: 'Wallet',\r
-                       }\r
-                       const response = await SafeWorker.assign(headers, {\r
                                password: password.buffer\r
                        })\r
-                       const decoded = bytes.toUtf8(response[this.id])\r
+                       const decoded = bytes.toUtf8(new Uint8Array(response[this.id]))\r
                        const deserialized = JSON.parse(decoded)\r
                        let { id, mnemonic, seed } = deserialized\r
                        if (id == null) {\r
index 2076cca939ae3b679d2b2be479378c809b4de43d..da67264b360bbc949a043812712ad42473feedb2 100644 (file)
@@ -2,7 +2,7 @@
 // SPDX-License-Identifier: GPL-3.0-or-later
 
 import { WorkerInterface } from './worker-interface'
-import { Data, Headers } from '#types'
+import { Data, NamedData } from '#types'
 
 type ExtendedKey = {
        privateKey: DataView<ArrayBuffer>
@@ -19,14 +19,22 @@ export class Bip44Ckd extends WorkerInterface {
                this.listen()
        }
 
-       static async work (headers: Headers, data: Data): Promise<Data> {
-               let { coin, indexes } = headers
-               let { seed } = data
-               if (coin != null && (typeof coin !== 'number' || !Number.isInteger(coin))) {
+       static async work (data: NamedData): Promise<NamedData<ArrayBuffer>> {
+               if (data.coin != null && (typeof data.coin !== 'number' || !Number.isInteger(data.coin))) {
                        throw new TypeError('BIP-44 coin derivation level must be an integer')
                }
-               if (!Array.isArray(indexes)) indexes = [indexes]
-               const privateKeys: Data = {}
+               if (!Array.isArray(data.indexes) || data.indexes.some(i => !Number.isInteger(i))) {
+                       throw new TypeError('BIP-44 account indexes must be an array of integers')
+               }
+               if (!(data.seed instanceof ArrayBuffer)) {
+                       throw new TypeError('BIP-44 seed must be an ArrayBuffer')
+               }
+               const coin: number = data.coin
+               const indexes = Array.isArray(data.indexes)
+                       ? data.indexes
+                       : [data.indexes]
+               const seed = data.seed
+               const privateKeys: NamedData<ArrayBuffer> = {}
                for (const i of indexes) {
                        if (typeof i !== 'number' || !Number.isInteger(i)) {
                                throw new TypeError('BIP-44 account derivation level must be an integer')
index e3631710dcfabd23dc97030a136ca1af0a19348c..2b8c811d139e447de7dbb0246439385c5c1feb79 100644 (file)
@@ -1,4 +1,4 @@
 // SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
 // SPDX-License-Identifier: GPL-3.0-or-later
 
-export { Bip44CkdWorker, NanoNaClWorker, SafeWorker } from './queue'
+export { Bip44CkdWorker, NanoNaClWorker, SafeWorker } from './worker-queue'
index d6b1ed1ae81163efa0ffb33ce86f47c7ef0fd283..58c7616f5be782540dc73f75828a4a5a82d99f4a 100644 (file)
@@ -6,7 +6,7 @@
 import { WorkerInterface } from './worker-interface'\r
 import { Blake2b } from '#src/lib/blake2b.js'\r
 import { default as Convert, bytes, hex } from '#src/lib/convert.js'\r
-import { Data, Headers, Key } from '#types'\r
+import { Data, NamedData, Key } from '#types'\r
 \r
 /**\r
 * Ported in 2014 by Dmitry Chestnykh and Devi Mandiri.\r
@@ -26,8 +26,16 @@ export class NanoNaCl extends WorkerInterface {
                this.listen()\r
        }\r
 \r
-       static async work (headers: Headers, data: Data): Promise<boolean | string> {\r
-               const { method } = headers\r
+       static async work (data: NamedData<string | ArrayBuffer>): Promise<NamedData> {\r
+               if (typeof data.method !== 'string'\r
+                       || !(data.msg == null || data.msg instanceof ArrayBuffer)\r
+                       || !(data.privateKey == null || data.privateKey instanceof ArrayBuffer)\r
+                       || !(data.publicKey == null || data.publicKey instanceof ArrayBuffer)\r
+                       || !(data.signature == null || data.signature instanceof ArrayBuffer)\r
+               ) {\r
+                       throw new TypeError('Invalid NanoNaCl input')\r
+               }\r
+               const method = data.method\r
                const msg = new Uint8Array(data.msg)\r
                const privateKey = new Uint8Array(data.privateKey)\r
                const publicKey = new Uint8Array(data.publicKey)\r
@@ -35,13 +43,13 @@ export class NanoNaCl extends WorkerInterface {
                try {\r
                        switch (method) {\r
                                case 'convert': {\r
-                                       return bytes.toHex(await this.convert(privateKey))\r
+                                       return { publicKey: (await this.convert(privateKey)).buffer }\r
                                }\r
                                case 'detached': {\r
-                                       return bytes.toHex(await this.detached(msg, privateKey))\r
+                                       return { signature: (await this.detached(msg, privateKey)).buffer }\r
                                }\r
                                case 'verify': {\r
-                                       return await this.verify(msg, signature, publicKey)\r
+                                       return { isVerified: await this.verify(msg, signature, publicKey) }\r
                                }\r
                                default: {\r
                                        throw new TypeError(`unknown NanoNaCl method ${method}`)\r
index 824599ec9355fd5dc3d43132a15b4606d7731fe1..5ddbb8b7b6180db917e1e515e5e8bfef813f7f7b 100644 (file)
@@ -7,7 +7,7 @@ import { WorkerInterface } from './worker-interface'
 import { PBKDF2_ITERATIONS } from '#src/lib/constants.js'
 import { default as Convert, bytes } from '#src/lib/convert.js'
 import { Entropy } from '#src/lib/entropy.js'
-import { Data, Headers, SafeRecord } from '#types'
+import { NamedData, SafeRecord } from '#types'
 
 /**
 * Encrypts and stores data in the browser using IndexedDB.
@@ -22,25 +22,33 @@ export class Safe extends WorkerInterface {
                this.listen()
        }
 
-       static async work (headers: Headers, data: Data): Promise<boolean | Data> {
-               const { method, name, store } = headers
-               const password = data?.password
-               if (data != null) delete data.password
+       static async work (data: NamedData): Promise<NamedData<boolean | ArrayBuffer>> {
+               const { method, name, store } = data
+               if (typeof method !== 'string') {
+                       throw new TypeError('Invalid method')
+               }
+               delete data.method
+               if (name != null && typeof name !== 'string') {
+                       throw new TypeError('Invalid name')
+               }
+               delete data.name
+               if (typeof store !== 'string') {
+                       throw new TypeError('Invalid store')
+               }
+               delete data.store
+               const password = data.password
+               delete data.password
                this.#storage = await this.#open(this.DB_NAME)
-               let result
                try {
                        switch (method) {
                                case 'set': {
-                                       result = await this.set(data, store, password)
-                                       break
+                                       return { result: await this.set(data, store, password) }
                                }
                                case 'get': {
-                                       result = await this.get(name, store, password)
-                                       break
+                                       return await this.get(name, store, password)
                                }
                                case 'destroy': {
-                                       result = await this.destroy(name, store)
-                                       break
+                                       return { result: await this.destroy(name, store) }
                                }
                                default: {
                                        throw new Error(`unknown Safe method ${method}`)
@@ -50,7 +58,6 @@ export class Safe extends WorkerInterface {
                        console.log(err)
                        throw new Error('Safe error', { cause: err })
                }
-               return result
        }
 
        /**
@@ -68,7 +75,7 @@ export class Safe extends WorkerInterface {
        /**
        * Encrypts data with a password byte array and stores it in the Safe.
        */
-       static async set (data: Data | unknown, store: string | unknown, password: ArrayBuffer | unknown): Promise<boolean> {
+       static async set (data: NamedData | unknown, store: string | unknown, password: ArrayBuffer | unknown): Promise<boolean> {
                this.#isDataValid(data)
                if (typeof store !== 'string' || store === '') {
                        throw new Error('Invalid database store name')
@@ -103,7 +110,7 @@ export class Safe extends WorkerInterface {
        /**
        * Retrieves data from the Safe and decrypts it with a password byte array.
        */
-       static async get (name: string | string[] | unknown, store: string | unknown, password: ArrayBuffer | unknown): Promise<Data> {
+       static async get (name: string | string[] | unknown, store: string | unknown, password: ArrayBuffer | unknown): Promise<NamedData<ArrayBuffer>> {
                const names = Array.isArray(name) ? name : [name]
                if (names.some(v => typeof v !== 'string')) {
                        throw new Error('Invalid fields')
@@ -116,7 +123,7 @@ export class Safe extends WorkerInterface {
                        throw new Error('Invalid password')
                }
 
-               const results: Data = {}
+               const results: NamedData<ArrayBuffer> = {}
                try {
                        const records: SafeRecord[] = await this.#get(fields, store)
                        if (records == null || records.length === 0) {
@@ -184,7 +191,7 @@ export class Safe extends WorkerInterface {
                })
        }
 
-       static #isDataValid (data: unknown): asserts data is Data {
+       static #isDataValid (data: unknown): asserts data is { [key: string]: ArrayBuffer } {
                if (typeof data !== 'object') {
                        throw new Error('Invalid data')
                }
index 9b801e635370893ccf65e9a5d8c11420d89808a0..76b03d7f6667ec4f3310a16fc2d7292bf3921603 100644 (file)
@@ -2,7 +2,7 @@
 // SPDX-License-Identifier: GPL-3.0-or-later
 
 import { parentPort } from 'node:worker_threads'
-import { Data, Headers } from '#types'
+import { Data, NamedData } from '#types'
 
 /**
 * Provides basic worker event messaging to extending classes.
@@ -32,22 +32,16 @@ export abstract class WorkerInterface {
        * function signature and providing their own processing call in the try-catch
        * block.
        *
-       * @param {Header} headers - Flat object of header data
-       * @param {Data} data - String keys for ArrayBuffer values to transfer and process
+       * @param {NamedData} data - Flat object of data key-value pairs
        * @returns Promise for processed data
        */
-       static async work (headers: Headers | null, data?: Data): Promise<any> {
-               return new Promise(async (resolve, reject): Promise<void> => {
+       static work (data: NamedData): Promise<NamedData> {
+               return new Promise((resolve, reject): void => {
                        try {
-                               let x, y = new ArrayBuffer(0)
-                               if (headers != null) {
-                                       const { sample } = headers
-                                       x = sample
-                               }
-                               if (data != null) {
-                                       const { buf } = data
-                                       y = buf
+                               if (data == null) {
+                                       throw new Error('Missing data')
                                }
+                               const { x, y } = data
                                resolve({ x, y })
                        } catch (err) {
                                reject(err)
@@ -58,15 +52,15 @@ export abstract class WorkerInterface {
        /**
        * Transfers buffers of worker results back to the main thread.
        *
-       * @param {Headers} results - Key-value pairs of processed data
+       * @param {NamedData} data - Key-value pairs of processed data
        */
-       static report (results: Headers): void {
+       static report (data: NamedData): void {
                const buffers = []
                try {
-                       if (typeof results === 'object') {
-                               for (const d of Object.keys(results)) {
-                                       if (results[d] instanceof ArrayBuffer) {
-                                               buffers.push(results[d])
+                       if (typeof data === 'object') {
+                               for (const d of Object.keys(data)) {
+                                       if (data[d] instanceof ArrayBuffer) {
+                                               buffers.push(data[d])
                                        }
                                }
                        }
@@ -75,8 +69,8 @@ export abstract class WorkerInterface {
                        throw new Error('Failed to report results', { cause: err })
                }
                //@ts-expect-error
-               BROWSER: postMessage(results, buffers)
-               NODE: parentPort?.postMessage(results, buffers)
+               BROWSER: postMessage(data, buffers)
+               NODE: parentPort?.postMessage({ data }, buffers)
        }
 
        /**
@@ -91,12 +85,14 @@ export abstract class WorkerInterface {
        */
        static listen (): void {
                const listener = async (message: MessageEvent<any>): Promise<void> => {
-                       const { name, headers, data } = message.data
-                       if (name === 'STOP') {
-                               close()
+                       // console.log(message)
+                       const { data } = message
+                       if (data.method === 'STOP') {
                                this.report({})
+                               BROWSER: close()
+                               NODE: process.exit()
                        } else {
-                               this.work(headers, data).then(this.report).catch(this.report)
+                               this.work(data).then(this.report).catch(this.report)
                        }
                }
                BROWSER: addEventListener('message', listener)
similarity index 67%
rename from src/lib/workers/queue.ts
rename to src/lib/workers/worker-queue.ts
index 5a49dac69a949da875c67f417117d2df51f59fdd..6cea63b5e14d4b6c6eca36b2b8067bc99497a6ed 100644 (file)
@@ -5,12 +5,11 @@ import { Worker as NodeWorker } from 'node:worker_threads'
 import { default as bip44 } from './bip44-ckd'
 import { default as nacl } from './nano-nacl'
 import { default as safe } from './safe'
-import { Data, Headers } from '#types'
+import { Data, NamedData } from '#types'
 
 type Task = {
        id: number
-       headers: Headers | null
-       data?: Data
+       data: NamedData
        reject: (value: any) => void
        resolve: (value: any) => void
 }
@@ -18,9 +17,9 @@ type Task = {
 /**
 * Processes a queue of tasks using Web Workers.
 */
-export class Queue {
-       static #instances: Queue[] = []
-       static get instances (): Queue[] { return this.#instances }
+export class WorkerQueue {
+       static #instances: WorkerQueue[] = []
+       static get instances (): WorkerQueue[] { return this.#instances }
 
        #job?: Task
        #isIdle: boolean
@@ -50,15 +49,15 @@ export class Queue {
                NODE: this.#worker.on('message', message => {
                        this.#report(message)
                })
-               Queue.#instances.push(this)
+               WorkerQueue.#instances.push(this)
        }
 
-       async assign (headers: Headers | null, data?: Data): Promise<any> {
-               return this.#assign(task => this.#queue.push(task), headers, data)
+       async assign<T extends Data> (data: NamedData): Promise<NamedData<T>> {
+               return this.#assign<T>(data, task => this.#queue.push(task))
        }
 
-       async prioritize (headers: Headers | null, data?: Data): Promise<any> {
-               return this.#assign(task => this.#queue.unshift(task), headers, data)
+       async prioritize<T extends Data> (data: NamedData): Promise<NamedData<T>> {
+               return this.#assign<T>(data, task => this.#queue.unshift(task))
        }
 
        terminate (): void {
@@ -66,11 +65,10 @@ export class Queue {
                this.#worker.terminate()
        }
 
-       async #assign (enqueue: (task: Task) => number, headers: Headers | null, data?: Data): Promise<Data> {
+       async #assign<T extends Data> (data: NamedData, enqueue: (task: Task) => number): Promise<NamedData<T>> {
                return new Promise(async (resolve, reject): Promise<void> => {
                        const task: Task = {
                                id: performance.now(),
-                               headers,
                                data,
                                resolve,
                                reject
@@ -84,16 +82,16 @@ export class Queue {
                this.#job = this.#queue.shift()
                this.#isIdle = this.#job == null
                if (this.#job != null) {
-                       const { id, headers, data, reject } = this.#job
+                       const { data, reject } = this.#job
                        try {
                                const buffers: ArrayBuffer[] = []
-                               if (data != null) {
-                                       for (let d of Object.keys(data)) {
+                               for (let d of Object.keys(data)) {
+                                       if (data[d] instanceof ArrayBuffer) {
                                                buffers.push(data[d])
                                        }
                                }
-                               BROWSER: this.#worker.postMessage({ headers, data }, buffers)
-                               NODE: this.#worker.postMessage({ data: { headers, data } }, buffers)
+                               BROWSER: this.#worker.postMessage(data, buffers)
+                               NODE: this.#worker.postMessage({ data }, buffers)
                        } catch (err) {
                                reject(err)
                        }
@@ -115,6 +113,6 @@ export class Queue {
        }
 }
 
-export const Bip44CkdWorker = new Queue(bip44)
-export const NanoNaClWorker = new Queue(nacl)
-export const SafeWorker = new Queue(safe)
+export const Bip44CkdWorker = new WorkerQueue(bip44)
+export const NanoNaClWorker = new WorkerQueue(nacl)
+export const SafeWorker = new WorkerQueue(safe)
index 50a94152cd10e4ca30c285ea2f5e9a160f6b62a1..d3a73b7893381da3deceaa7d75d82d26f92faebe 100644 (file)
@@ -276,12 +276,10 @@ export declare class ChangeBlock extends Block {
        constructor (account: Account | string, balance: string, representative: Account | string, frontier: string, work?: string)
 }
 
-export type Data = {
-       [key: string]: ArrayBuffer
-}
+export type Data = boolean | number[] | string | ArrayBuffer
 
-export type Headers = {
-       [key: string]: any
+export type NamedData<T extends Data = Data> = {
+       [key: string]: T
 }
 
 export type Key = string | Uint8Array<ArrayBuffer>
@@ -304,8 +302,8 @@ export declare class Queue {
        * @param {number} [count=1] - Integer between 1 and CPU thread count shared among all Pools
        */
        constructor (worker: string)
-       assign (headers: Headers | null, data?: Data): Promise<any>
-       prioritize (headers: Headers | null, data?: Data): Promise<any>
+       assign (data: NamedData): Promise<NamedData>
+       prioritize (data: NamedData): Promise<NamedData>
        terminate (): void
 }
 export declare const Bip44CkdWorker: Queue
index ea87e35023532ad8d97dce66c3997536f7998d54..96da91cba37b5026d4744ec6d7e7f8da85fd5401 100644 (file)
@@ -1,13 +1,12 @@
 // SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
 // SPDX-License-Identifier: GPL-3.0-or-later
 
+export { env } from '../env.mjs'
 import { Queue } from './QUEUE.mjs'
-import { process } from '../env.mjs'
 
 const queue = new Queue()
 
-export { process }
-export const isNode = process.versions?.node != null
+export const isNode = globalThis !== globalThis.window
 
 let NodeTestSuite, NodeTestTest
 if (isNode) {
index e33296a3c778f155b3a29fd13c63c6291307f84f..1e1b9486e9ddb40641875040810a873525b4c1e3 100644 (file)
@@ -7,7 +7,7 @@ import { assert, stats, suite, test } from './GLOBALS.mjs'
 import { NANO_TEST_VECTORS } from './VECTORS.js'
 import { Bip44Wallet, Blake2bWallet } from '../dist/main.min.js'
 
-await suite('Account performance', async () => {
+await suite('Account performance', { skip: true }, async () => {
        const COUNT = 0x200
 
        await test(`Time to create ${COUNT} BIP-44 accounts`, async () => {
@@ -18,7 +18,7 @@ await suite('Account performance', async () => {
                const end = performance.now()
                console.log(`Total: ${end - start} ms`)
                console.log(`Average: ${(end - start) / COUNT} ms`)
-               assert.equals(accounts.length, COUNT)
+               assert.equal(accounts.length, COUNT)
                await wallet.destroy()
        })
 
@@ -30,7 +30,7 @@ await suite('Account performance', async () => {
                const end = performance.now()
                console.log(`Total: ${end - start} ms`)
                console.log(`Average: ${(end - start) / COUNT} ms`)
-               assert.equals(accounts.length, COUNT)
+               assert.equal(accounts.length, COUNT)
                await wallet.destroy()
        })
 
index 567a29fbcc6697bed1a1c48b876d9ea8d4405c13..20b83505104588ece4d60a6232a2452c8d44a6e7 100644 (file)
@@ -3,11 +3,11 @@
 
 'use strict'
 
-import { assert, stats, suite, test } from './GLOBALS.mjs'
+import { stats, suite, test } from './GLOBALS.mjs'
 import { NANO_TEST_VECTORS } from './VECTORS.js'
 import { SendBlock } from '../dist/main.min.js'
 
-await suite('Block performance', async () => {
+await suite('Block performance', { skip: true }, async () => {
        const COUNT = 0x200
 
        await test(`libnemo: Time to calculate proof-of-work for a send block ${COUNT} times`, { skip: true }, async () => {
index e1dd75c30c381b12d237c194014076cfd5351232..b58adc75c7698063f49b1eadda80037f16347c31 100644 (file)
@@ -3,11 +3,11 @@
 
 'use strict'
 
-import { assert, stats, suite, test } from './GLOBALS.mjs'
+import { stats, suite, test } from './GLOBALS.mjs'
 import { NANO_TEST_VECTORS } from './VECTORS.js'
 import { Bip44Wallet, Blake2bWallet } from '../dist/main.min.js'
 
-await suite(`Wallet performance`, async () => {
+await suite(`Wallet performance`, { skip: true }, async () => {
        const COUNT = 0x20
 
        await test(`Time to create ${COUNT} BIP-44 wallets`, async () => {
index 2844d507e4ff48735895b05069954cf8dcc05509..29b6bdbf94925ca8e8880a0e9d13e85d80a7c32b 100644 (file)
@@ -3,11 +3,11 @@
 \r
 'use strict'\r
 \r
-import { assert, suite, test } from './GLOBALS.mjs'\r
+import { assert, isNode, suite, test } from './GLOBALS.mjs'\r
 import { NANO_TEST_VECTORS } from './VECTORS.js'\r
 import { SendBlock, Blake2b } from '../dist/main.min.js'\r
 \r
-await suite('Calculate proof-of-work', async () => {\r
+await suite('Calculate proof-of-work', { skip: isNode }, async () => {\r
 \r
        await test('SendBlock PoW', async () => {\r
                const block = new SendBlock(\r
index 28c21738da740e2ca59d5d211404ea4808816381..812dda7fff537e4979bc875c8543ac866568d09e 100644 (file)
@@ -3,11 +3,11 @@
 
 'use strict'
 
-import { assert, click, isNode, process, suite, test } from './GLOBALS.mjs'
+import { assert, click, env, isNode, suite, test } from './GLOBALS.mjs'
 import { NANO_TEST_VECTORS } from './VECTORS.js'
 import { Account, LedgerWallet, ReceiveBlock, Rpc, SendBlock } from '../dist/main.min.js'
 
-const rpc = new Rpc(process.env.NODE_URL ?? '', process.env.API_KEY_NAME)
+const rpc = new Rpc(env.NODE_URL ?? '', env.API_KEY_NAME)
 
 /**
 * HID interactions require user gestures, so to reduce clicks, the variables
index 5d070511e1a454938d0810dc3284f727173c6e68..409a0774df2dff731fafe005a0eaeed89197211e 100644 (file)
@@ -3,11 +3,11 @@
 
 'use strict'
 
-import { assert, process, suite, test } from './GLOBALS.mjs'
+import { assert, env, suite, test } from './GLOBALS.mjs'
 import { NANO_TEST_VECTORS } from './VECTORS.js'
 import { Account, Bip44Wallet, Rpc } from '../dist/main.min.js'
 
-const rpc = new Rpc(process.env.NODE_URL ?? '', process.env.API_KEY_NAME)
+const rpc = new Rpc(env.NODE_URL ?? '', env.API_KEY_NAME)
 
 await suite('Refreshing account info', { skip: true }, async () => {
 
index 2295d307055c1ed8bfef05f53f5154a6790c9ca4..1f2bd9e81159315cfa1e8dc773594ae336ca2faa 100644 (file)
@@ -1,42 +1,45 @@
 // SPDX-FileCopyrightText: 2025 Chris Duncan <chris@zoso.dev>
 // SPDX-License-Identifier: GPL-3.0-or-later
 
-import { failures, passes, suite, test } from './GLOBALS.mjs'
+import { failures, isNode, passes, suite, test } from './GLOBALS.mjs'
 
-await suite('TEST RUNNER CHECK', async () => {
-       await new Promise(r => setTimeout(r, 0))
+if (!isNode) {
+       await suite('TEST RUNNER CHECK', async () => {
+               await new Promise(r => setTimeout(r, 0))
 
-       console.assert(failures.length === 0)
-       console.assert(passes.length === 0)
+               console.assert(failures.length === 0)
+               console.assert(passes.length === 0)
 
-       //@ts-expect-error
-       await test('promise should pass', new Promise(resolve => resolve(null)))
-       console.assert(failures.some(call => /.*promise should pass.*/.test(call[0])) === false, `good promise errored`)
-       console.assert(passes.some(call => /.*promise should pass.*/.test(call)) === true, `good promise not logged`)
+               //@ts-expect-error
+               await test('promise should pass', new Promise(resolve => resolve(null)))
+               console.assert(failures.some(call => /.*promise should pass.*/.test(call[0])) === false, `good promise errored`)
+               console.assert(passes.some(call => /.*promise should pass.*/.test(call)) === true, `good promise not logged`)
 
-       //@ts-expect-error
-       await test('promise should fail', new Promise((resolve, reject) => reject('FAILURE EXPECTED HERE')))
-       console.assert(failures.some(call => /.*promise should fail.*/.test(call)) === true, `bad promise not errored`)
-       console.assert(passes.some(call => /.*promise should fail.*/.test(call)) === false, 'bad promise logged')
+               //@ts-expect-error
+               await test('promise should fail', new Promise((resolve, reject) => reject('FAILURE EXPECTED HERE')))
+               console.assert(failures.some(call => /.*promise should fail.*/.test(call)) === true, `bad promise not errored`)
+               console.assert(passes.some(call => /.*promise should fail.*/.test(call)) === false, 'bad promise logged')
 
-       await test('async should pass', async () => {})
-       console.assert(failures.some(call => /.*async should pass.*/.test(call)) === false, 'good async errored')
-       console.assert(passes.some(call => /.*async should pass.*/.test(call)) === true, 'good async not logged')
+               await test('async should pass', async () => {})
+               console.assert(failures.some(call => /.*async should pass.*/.test(call)) === false, 'good async errored')
+               console.assert(passes.some(call => /.*async should pass.*/.test(call)) === true, 'good async not logged')
 
-       await test('async should fail', async () => { throw new Error('FAILURE EXPECTED HERE') })
-       console.assert(failures.some(call => /.*async should fail.*/.test(call)) === true, 'bad async not errored')
-       console.assert(passes.some(call => /.*async should fail.*/.test(call)) === false, 'bad async logged')
+               await test('async should fail', async () => { throw new Error('FAILURE EXPECTED HERE') })
+               console.assert(failures.some(call => /.*async should fail.*/.test(call)) === true, 'bad async not errored')
+               console.assert(passes.some(call => /.*async should fail.*/.test(call)) === false, 'bad async logged')
 
-       await test('function should pass', () => {})
-       console.assert(failures.some(call => /.*function should pass.*/.test(call)) === false, 'good function errored')
-       console.assert(passes.some(call => /.*function should pass.*/.test(call)) === true, 'good function not logged')
+               await test('function should pass', () => {})
+               console.assert(failures.some(call => /.*function should pass.*/.test(call)) === false, 'good function errored')
+               console.assert(passes.some(call => /.*function should pass.*/.test(call)) === true, 'good function not logged')
 
-       //@ts-expect-error
-       await test('function should fail', 'FAILURE EXPECTED HERE')
-       console.assert(failures.some(call => /.*function should fail.*/.test(call)) === true, 'bad function not errored')
-       console.assert(passes.some(call => /.*function should fail.*/.test(call)) === false, 'bad function logged')
+               //@ts-expect-error
+               await test('function should fail', 'FAILURE EXPECTED HERE')
+               console.assert(failures.some(call => /.*function should fail.*/.test(call)) === true, 'bad function not errored')
+               console.assert(passes.some(call => /.*function should fail.*/.test(call)) === false, 'bad function logged')
 
-       failures.splice(0)
-       passes.splice(0)
-})
-console.log(`%cTEST RUNNER CHECK DONE`, 'font-weight:bold')
+               failures.splice(0)
+               passes.splice(0)
+       })
+
+       console.log(`%cTEST RUNNER CHECK DONE`, 'font-weight:bold')
+}
index 903a5ec4a12c570141f123144601952942885f6f..e5a4599c89b7eee88bd604a30d2dbbf7af8da53f 100644 (file)
@@ -3,17 +3,11 @@
 \r
 'use strict'\r
 \r
-import { assert, suite, test } from './GLOBALS.mjs'\r
+import { assert, env, suite, test } from './GLOBALS.mjs'\r
 import { RAW_MAX, NANO_TEST_VECTORS } from './VECTORS.js'\r
 import { Bip44Wallet, Account, SendBlock, Rpc, Tools } from '../dist/main.min.js'\r
 \r
-let rpc\r
-//@ts-ignore\r
-var process = process || null\r
-if (process) {\r
-       //@ts-expect-error\r
-       rpc = new Rpc(process?.env?.NODE_URL ?? '', process?.env?.API_KEY_NAME)\r
-}\r
+const rpc = new Rpc(env?.NODE_URL ?? '', env?.API_KEY_NAME)\r
 \r
 await suite('unit conversion tests', async () => {\r
 \r