type TaskData = {
[key: string]: Data | Record<string, Data>
url: string
- id: number
+ id: string
}
type Task = {
#job?: Task
#isLocked: boolean = true
#isTerminated: boolean = false
- #queue: Task[] = []
+ #tasks: Map<string, Task> = new Map<string, Task>()
#url: string
#worker: NodeWorker | Worker
removeEventListener = this.#eventTarget.removeEventListener.bind(this.#eventTarget)
constructor () {
- BROWSER: this.#url = URL.createObjectURL(new Blob([vaultWorker], { type: 'text/javascript' }))
- BROWSER: this.#worker = new Worker(this.#url, { type: 'module' })
- BROWSER: this.#worker.addEventListener('message', message => {
+ const listener = (message: MessageEvent<any>) => {
+ console.log('host listener(message)', message, message?.data)
this.#report(message.data)
- })
- NODE: this.#worker = new NodeWorker(vaultWorker, {
- eval: true,
- stderr: false,
- stdout: false
- })
- NODE: this.#worker.on('message', message => {
- this.#report(message)
- })
- NODE: this.#url = this.#worker.threadId.toString()
+ }
+ BROWSER: {
+ this.#url = URL.createObjectURL(new Blob([vaultWorker], { type: 'text/javascript' }))
+ this.#worker = new Worker(this.#url, { type: 'module' })
+ this.#worker.addEventListener('message', listener)
+ }
+ NODE: {
+ this.#worker = new NodeWorker(vaultWorker, {
+ eval: true,
+ stderr: false,
+ stdout: false
+ })
+ this.#url = this.#worker.threadId.toString()
+ this.#worker.on('message', listener)
+ }
}
get isLocked (): boolean { return this.#isLocked }
if (this.#isTerminated) {
throw new Error(TERMINATED)
}
+ const buffers: ArrayBuffer[] = []
+ for (const d of Object.values(payload)) {
+ if (d instanceof ArrayBuffer) {
+ buffers.push(d)
+ }
+ }
+ const id = crypto.randomUUID()
const data: TaskData = {
...payload,
url: this.#url,
- id: performance.now(),
+ id
}
return new Promise((resolve, reject): void => {
const task: Task = {
resolve,
reject
}
- this.#queue.push(task)
- if (this.#job == null) this.#process()
+ this.#tasks.set(id, task)
+ try {
+ console.log('host postMessage(data)', data)
+ BROWSER: this.#worker.postMessage(data, buffers)
+ NODE: this.#worker.postMessage({ data }, buffers)
+ } catch (err) {
+ reject(err)
+ }
})
}
NODE: this.#worker.unref()
this.#job?.reject(TERMINATED)
this.#job = undefined
- for (const task of this.#queue) {
+ for (const [_, task] of this.#tasks) {
task?.reject?.(TERMINATED)
}
- this.#queue = []
+ this.#tasks.clear()
}
- #process = (): void => {
- this.#job = this.#queue.shift()
- if (this.#job != null) {
- const { data, reject } = this.#job
- const buffers: ArrayBuffer[] = []
- for (const d of Object.values(data)) {
- if (d instanceof ArrayBuffer) {
- buffers.push(d)
- }
+ #report (results: unknown): void {
+ if (results == null) return
+ const { url, id, error, isLocked } = results as Record<string, unknown>
+ if (url === this.#url) {
+ if (typeof id !== 'string') {
+ throw new Error('Vault worker job invalid ID')
}
- try {
- BROWSER: this.#worker.postMessage(data, buffers)
- NODE: this.#worker.postMessage({ data }, buffers)
- } catch (err) {
- reject(err)
- }
- }
- }
-
- #report (results: any): void {
- if (results === LOCKED || results === UNLOCKED) {
- const isLocked = results === LOCKED
- if (this.#isLocked !== isLocked) {
+ if (typeof isLocked === 'boolean' && this.#isLocked !== isLocked) {
this.#isLocked = isLocked
- this.dispatchEvent(new Event(results))
+ this.dispatchEvent(new Event(isLocked ? LOCKED : UNLOCKED))
}
- return
- }
- if (this.#job == null) {
- throw new Error('Vault worker returned results without an associated job.')
- }
- const { data: { url, id }, resolve, reject } = this.#job
- if (url == null) {
- throw new Error('Vault worker job missing URL')
- }
- if (id == null) {
- throw new Error('Vault worker job missing ID')
- }
- if (results?.url === url && results?.id === id) {
- try {
- if (results?.error != null) {
- reject(results)
- } else {
- resolve(results)
- }
- } catch (err) {
- reject(err)
- } finally {
- this.#process()
+ const task = this.#tasks.get(id)
+ if (task == null) {
+ throw new Error('Vault worker returned results without an associated job.')
+ }
+ const { resolve, reject } = task
+ if (error != null) {
+ reject(results)
+ } else {
+ resolve(results)
}
}
}
}
const data = event.data as Record<string, unknown>
const { url, id } = data
- if (typeof id !== 'number' && typeof id !== 'string') return
+ if (typeof id !== 'string') return
BROWSER: if (url !== location.href) return
NODE: if (url !== threadId.toString()) return
NODE: if (parentPort == null) setTimeout(() => listener(event), 0)
return update(key, keySalt)
}
case 'verify': {
- return Promise.resolve(verify(seed, mnemonicPhrase))
+ return verify(seed, mnemonicPhrase)
}
default: {
throw new Error(`Unknown wallet action '${action}'`)
result.id = id
console.log('worker postMessage(result)', result)
//@ts-expect-error
- BROWSER: postMessage(result, transfer)
- NODE: parentPort?.postMessage(result, transfer)
+ BROWSER: self.postMessage(result, transfer)
+ NODE: parentPort?.postMessage({ data: result }, transfer)
})
.catch((err: any) => {
for (let data of Object.values(event.data)) {
data = undefined
}
console.log('worker postMessage(error)', err)
- BROWSER: postMessage({ url, id, error: 'Failed to process Vault request', cause: err })
- NODE: parentPort?.postMessage({ url, id, error: 'Failed to process Vault request', cause: err })
+ BROWSER: self.postMessage({ url, id, error: 'Failed to process Vault request', cause: err })
+ NODE: parentPort?.postMessage({ data: { url, id, error: 'Failed to process Vault request', cause: err } })
})
}
-BROWSER: addEventListener('message', listener)
+BROWSER: self.addEventListener('message', listener)
NODE: parentPort?.on('message', listener)
/**
.finally(() => lock())
}
-async function lock (): Promise<void> {
+async function lock (): Promise<Record<string, boolean>> {
_mnemonic = undefined
_seed = undefined
_locked = true
_timer?.pause()
- BROWSER: postMessage('locked')
- NODE: parentPort?.postMessage('locked')
- return Promise.resolve()
+ return Promise.resolve({ isLocked: true })
}
/**
/**
* Decrypts the input and sets the seed and, if it is included, the mnemonic.
*/
-async function unlock (type?: WalletType, key?: CryptoKey, iv?: ArrayBuffer, encrypted?: ArrayBuffer): Promise<void> {
+async function unlock (type?: WalletType, key?: CryptoKey, iv?: ArrayBuffer, encrypted?: ArrayBuffer): Promise<Record<string, boolean>> {
if (type == null) {
throw new TypeError('Wallet type is required')
}
if (type === 'Ledger') {
_locked = false
- BROWSER: postMessage('unlocked')
- NODE: parentPort?.postMessage('unlocked')
- return Promise.resolve()
+ return Promise.resolve({ isLocked: false })
}
if (key == null) {
throw new TypeError('Wallet password is required')
_mnemonic = mnemonic
_locked = false
_timer = new VaultTimer(lock, _timeout)
- BROWSER: postMessage('unlocked')
- NODE: parentPort?.postMessage('unlocked')
+ return Promise.resolve({ isLocked: false })
})
.catch(err => {
console.error(err)
* Checks the seed and, if it exists, the mnemonic against input. The wallet
* must be unlocked prior to verification.
*/
-function verify (seed?: ArrayBuffer, mnemonicPhrase?: string): Record<string, boolean> {
+function verify (seed?: ArrayBuffer, mnemonicPhrase?: string): Promise<Record<string, boolean>> {
try {
if (_locked) {
throw new Error('Wallet is locked')
}
isVerified = diff === 0
}
- return { isVerified }
+ return Promise.resolve({ isVerified })
} catch (err) {
console.error(err)
throw new Error('Failed to verify wallet', { cause: err })