data: TaskData
reject: (value: any) => void
resolve: (value: any) => void
+ timeout: number | NodeJS.Timeout
}
//@ts-expect-error
export class Vault {
#eventTarget: EventTarget = new EventTarget()
- #job?: Task
#isLocked: boolean = true
#isTerminated: boolean = false
#tasks: Map<string, Task> = new Map<string, Task>()
BROWSER: {
this.#url = URL.createObjectURL(new Blob([vaultWorker], { type: 'text/javascript' }))
this.#worker = new Worker(this.#url, { type: 'module' })
- this.#worker.addEventListener('message', listener)
+ this.#worker.addEventListener('message', this.#report)
+ this.#worker.addEventListener('error', this.terminate)
}
NODE: {
this.#worker = new NodeWorker(vaultWorker, {
get isLocked (): boolean { return this.#isLocked }
- request<T extends Data | Record<string, Data>> (payload: Record<string, Data | Record<string, Data>>): Promise<Record<string, T>> {
+ request<T extends Data | Record<string, Data>> (payload: Record<string, Data | Record<string, Data>> & Record<'action', string>): Promise<Record<string, T>> {
if (this.#isTerminated) {
throw new Error(TERMINATED)
}
taskId
}
return new Promise((resolve, reject): void => {
+ const timeout = setTimeout(() => {
+ this.#tasks.delete(taskId)
+ reject(new Error('Vault request timed out', { cause: payload.action }))
+ }, 10_000)
const task: Task = {
data,
resolve,
- reject
+ reject,
+ timeout
}
this.#tasks.set(taskId, task)
try {
BROWSER: this.#worker.postMessage(data, buffers)
NODE: this.#worker.postMessage({ data }, buffers)
} catch (err) {
+ clearTimeout(timeout)
this.#tasks.delete(taskId)
+ for (const buffer of buffers) {
+ new Uint8Array(buffer).fill(0)
+ }
reject(err)
}
})
BROWSER: URL.revokeObjectURL(this.#url)
//@ts-expect-error
NODE: this.#worker.unref()
- this.#job?.reject(TERMINATED)
- this.#job = undefined
for (const [_, task] of this.#tasks) {
task?.reject?.(TERMINATED)
}
this.#tasks.clear()
}
- #report (results: unknown): void {
- if (results == null) return
- const { taskUrl, taskId, error, isLocked } = results as Record<string, unknown>
+ #report (message: unknown): void {
+ if (message == null || typeof message !== 'object') return
+ if (!('data' in message) || message.data == null || typeof message !== 'object') return
+ const results = message.data as Record<string, unknown>
+ const { taskUrl, taskId, error, isLocked } = results
if (taskUrl === this.#url) {
if (typeof taskId !== 'string') {
- throw new Error('Vault worker job invalid ID')
+ throw new Error('Vault worker job invalid ID', { cause: typeof taskId })
}
if (typeof isLocked === 'boolean' && this.#isLocked !== isLocked) {
this.#isLocked = isLocked
}
const { resolve, reject } = task
if (error != null) {
- reject(results)
+ reject(new Error(String(error)))
} else {
resolve(results)
}