From 19dbfff5a438de4501f977068bf2649d154e5435 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Mon, 18 Nov 2024 15:22:57 -0800 Subject: [PATCH] Re-re-factor Thread back to Pool. It doesn't have an application in this library at the moment, but perhaps in the future. --- src/lib/pool.ts | 78 +++++++++++++++++++++++++++++++++++++++++++++++ src/lib/safe.ts | 10 ++---- src/lib/thread.ts | 59 ----------------------------------- 3 files changed, 80 insertions(+), 67 deletions(-) create mode 100644 src/lib/pool.ts delete mode 100644 src/lib/thread.ts diff --git a/src/lib/pool.ts b/src/lib/pool.ts new file mode 100644 index 0000000..e8a9826 --- /dev/null +++ b/src/lib/pool.ts @@ -0,0 +1,78 @@ +// SPDX-FileCopyrightText: 2024 Chris Duncan +// SPDX-License-Identifier: GPL-3.0-or-later + +import { isDataView } from 'node:util/types' + +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 Thread = { + isBusy: boolean + worker: Worker +} + +/** +* Processes an array of tasks using Web Workers. +*/ +export class Pool { + #queue: object[] = [] + #resolve: Function = (value: unknown) => { } + #results: object[] = [] + #threads: Thread[] = [] + + get isDone () { + for (const thread of this.#threads) { + if (thread.isBusy) { + return false + } + return true + } + } + + constructor (url: string | URL) { + for (let i = navigator.hardwareConcurrency - 1; i > 0; i--) { + const thread = { + isBusy: false, + worker: new Worker(new URL(url, import.meta.url), { type: 'module' }) + } + thread.worker.addEventListener('message', (message) => { + thread.isBusy = false + this.#report(thread, message.data ?? message) + }) + this.#threads.push(thread) + } + } + + #assign (thread: Thread) { + const next = this.#queue.shift() + if (next != null) { + thread.isBusy = true + thread.worker.postMessage(next) + } + } + + #report (thread: Thread, result: any) { + this.#results.push(result) + if (this.#queue.length > 0) { + this.#assign(thread) + } else if (this.isDone) { + this.#resolve(this.#results) + } + } + + async work (data: object[]): Promise { + if (!Array.isArray(data)) data = [data] + return new Promise(resolve => { + this.#queue = data + this.#resolve = resolve + for (const thread of this.#threads) { + this.#assign(thread) + } + }) + } +} diff --git a/src/lib/safe.ts b/src/lib/safe.ts index 9c541ed..e22503f 100644 --- a/src/lib/safe.ts +++ b/src/lib/safe.ts @@ -3,18 +3,14 @@ import { buffer, hex, utf8 } from './convert.js' import { Entropy } from './entropy.js' -import { workerUrl } from './passkey.js' -import { Thread } from './thread.js' const { subtle } = globalThis.crypto const ERR_MSG = 'Failed to store item in Safe' export class Safe { #storage: Storage - #thread: Thread constructor () { this.#storage = globalThis.sessionStorage - this.#thread = new Thread(workerUrl) } /** @@ -48,10 +44,8 @@ export class Safe { const iv = new Entropy() if (typeof passkey === 'string') { try { - const keyBuffer = await this.#thread.work({ password: passkey, iv: iv.bytes }) - passkey = await subtle.importKey('raw', keyBuffer, 'AES-GCM', false, ['encrypt']) - // passkey = await subtle.importKey('raw', utf8.toBytes(passkey), 'PBKDF2', false, ['deriveBits', 'deriveKey']) - // passkey = await subtle.deriveKey({ name: 'PBKDF2', hash: 'SHA-512', salt: iv.bytes, iterations: 210000 }, passkey, { name: 'AES-GCM', length: 256 }, false, ['encrypt']) + passkey = await subtle.importKey('raw', utf8.toBytes(passkey), 'PBKDF2', false, ['deriveBits', 'deriveKey']) + passkey = await subtle.deriveKey({ name: 'PBKDF2', hash: 'SHA-512', salt: iv.bytes, iterations: 210000 }, passkey, { name: 'AES-GCM', length: 256 }, false, ['encrypt']) } catch (err) { throw new Error(ERR_MSG) } diff --git a/src/lib/thread.ts b/src/lib/thread.ts deleted file mode 100644 index 171416b..0000000 --- a/src/lib/thread.ts +++ /dev/null @@ -1,59 +0,0 @@ -// 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', (message) => { - const result = message.data ?? message - 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 }) - } - }) - } -} -- 2.34.1