@qntx/wallet-worker 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  SparkError,
3
3
  SparkCryptoError
4
- } from "./worker-gxb54jcq.js";
4
+ } from "./worker-gsare3mw.js";
5
5
 
6
- // ../../node_modules/.bun/@qntx+spark@0.5.0/node_modules/@qntx/spark/dist/crypto.mjs
6
+ // ../../node_modules/.bun/@qntx+spark@0.5.2/node_modules/@qntx/spark/dist/crypto.mjs
7
7
  var SPARK_ABI_VERSION = 1;
8
8
 
9
- // ../../node_modules/.bun/@qntx+spark-wasm@0.5.0+8f9f9cc94002ed5a/node_modules/@qntx/spark-wasm/dist/integrity-BP6ziDEM.mjs
9
+ // ../../node_modules/.bun/@qntx+spark-wasm@0.5.2+6548168f04baafa8/node_modules/@qntx/spark-wasm/dist/integrity-DPI575pP.mjs
10
10
  var OUT_PARAMS_LEN = 8;
11
11
  var INTERNAL_ERROR = 8;
12
12
  function requireMemory(value) {
@@ -175,9 +175,9 @@ function bindSparkExports(exports) {
175
175
  splitSecretWithProofs: (secret, threshold, shares, random) => run(() => wasmSplitSecretWithProofs(exports, secret, threshold, shares, random))
176
176
  };
177
177
  }
178
- var SPARK_WASM_INTEGRITY = "sha256-PFqThpVpNZHbmIuR13MZqLAupIIhfVcd3nMtB9QMTTE=";
178
+ var SPARK_WASM_INTEGRITY = "sha256-OBAVP0QjuY/JyeQh6Ay3XnczAKsMYrWoHdCJ73RSPeQ=";
179
179
 
180
- // ../../node_modules/.bun/@qntx+spark-wasm@0.5.0+8f9f9cc94002ed5a/node_modules/@qntx/spark-wasm/dist/browser.mjs
180
+ // ../../node_modules/.bun/@qntx+spark-wasm@0.5.2+6548168f04baafa8/node_modules/@qntx/spark-wasm/dist/browser.mjs
181
181
  async function fetchWasmUrl(url) {
182
182
  const res = await fetch(url);
183
183
  if (!res.ok)
package/dist/browser.mjs CHANGED
@@ -207,7 +207,7 @@ async function createWebWallet(options) {
207
207
  },
208
208
  onExit: (handler) => {
209
209
  const listener = () => {
210
- handler();
210
+ handler("crashed");
211
211
  };
212
212
  worker.addEventListener("error", listener);
213
213
  worker.addEventListener("messageerror", listener);
@@ -222,6 +222,7 @@ async function createWebWallet(options) {
222
222
  };
223
223
  };
224
224
  const wallet = await connectKernel({
225
+ factors: "software",
225
226
  spawn,
226
227
  passkey: options.passkey,
227
228
  ceremony,
@@ -1 +1 @@
1
- {"version":3,"file":"browser.mjs","names":[],"sources":["../src/browser/passkey.ts","../src/browser/index.ts"],"sourcesContent":["/**\n * The WebAuthn passkey ceremony (main thread only): `navigator.credentials` with the PRF extension.\n * A wallet passkey derives the wallet seed from `PRF(walletSalt)`; the vault factor derives the\n * vault KEK from `PRF(vaultSalt)` on the same credential. Every ceremony output is checked for\n * `prf.enabled`, PRF results and the user-verification flag — a credential that cannot prove UV\n * never reaches the kernel.\n */\nimport { WalletError } from \"@qntx/wallet\";\nimport { PASSKEY_PURPOSE_VAULT, PASSKEY_PURPOSE_WALLET } from \"@qntx/wallet/host\";\nimport type { PasskeyCeremony, PrfResult, PrfSalts } from \"@qntx/wallet/host\";\n\nexport type WebAuthnRp = {\n /** RP id — the origin's effective domain, e.g. `location.hostname`. */\n readonly id: string;\n readonly name: string;\n};\n\nconst UV_FLAG = 0x04; // authenticator-data flags byte (offset 32), bit 2 = user verified\nconst BE_FLAG = 0x08; // bit 3 = credential is backup-eligible (multi-device capable)\nconst BS_FLAG = 0x10; // bit 4 = credential is currently backed up (synced)\n\nfunction factorUnavailable(message: string): WalletError {\n return new WalletError(\"factor-unavailable\", message);\n}\n\nfunction cancelled(message: string): WalletError {\n return new WalletError(\"cancelled\", message);\n}\n\nfunction mapCeremonyError(error: unknown): never {\n if (\n error instanceof DOMException &&\n (error.name === \"NotAllowedError\" || error.name === \"AbortError\")\n ) {\n throw cancelled(\"the passkey ceremony was cancelled\");\n }\n if (error instanceof WalletError) {\n throw error;\n }\n throw factorUnavailable(\"the passkey ceremony failed\");\n}\n\n/** The UV bit of an authenticator-data blob (flags byte at offset 32, bit 0x04). */\nfunction userVerified(authenticatorData: ArrayBuffer): boolean {\n return (\n authenticatorData.byteLength > 32 &&\n // oxlint-disable-next-line eslint/no-bitwise -- flags bit test\n (new DataView(authenticatorData).getUint8(32) & UV_FLAG) !== 0\n );\n}\n\n/**\n * Whether the credential is backed up off-device (flags byte at offset 32): synced requires both BE\n * (the credential is eligible for backup) and BS (the authenticator actually backs it up).\n */\nfunction credentialSynced(authenticatorData: ArrayBuffer): boolean {\n return (\n authenticatorData.byteLength > 32 &&\n // oxlint-disable-next-line eslint/no-bitwise -- flags bit test\n (new DataView(authenticatorData).getUint8(32) & (BE_FLAG | BS_FLAG)) === (BE_FLAG | BS_FLAG)\n );\n}\n\nfunction prfEval(salts: PrfSalts): AuthenticationExtensionsPRFValues {\n // The constructor copy narrows Uint8Array<ArrayBufferLike> to Uint8Array<ArrayBuffer> —\n // the DOM BufferSource does not admit shared-array-buffer views.\n return salts.second === undefined\n ? { first: new Uint8Array(salts.first) }\n : { first: new Uint8Array(salts.first), second: new Uint8Array(salts.second) };\n}\n\n/** A detached copy of a BufferSource — the returned Uint8Array owns its own fresh buffer. */\nfunction copyBufferSource(source: BufferSource): Uint8Array {\n const view =\n source instanceof ArrayBuffer\n ? new Uint8Array(source)\n : new Uint8Array(source.buffer, source.byteOffset, source.byteLength);\n return new Uint8Array(view);\n}\n\nfunction readPrfResult(\n credential: PublicKeyCredential,\n results: AuthenticationExtensionsPRFValues | undefined,\n wantSecond: boolean,\n synced: boolean,\n userHandle?: ArrayBuffer,\n): PrfResult {\n const first = results?.first;\n const second = results?.second;\n if (first === undefined || (wantSecond && second === undefined)) {\n throw factorUnavailable(\"the passkey produced no PRF output\");\n }\n // Copy into fresh Uint8Arrays and drop every reference to the extension's ArrayBuffers.\n return {\n credentialId: copyBufferSource(credential.rawId),\n first: copyBufferSource(first),\n ...(second === undefined ? {} : { second: copyBufferSource(second) }),\n ...(userHandle === undefined ? {} : { userHandle: copyBufferSource(userHandle) }),\n synced,\n };\n}\n\nfunction checkCredential(credential: unknown): PublicKeyCredential {\n if (!(credential instanceof PublicKeyCredential)) {\n throw factorUnavailable(\"the authenticator returned no public-key credential\");\n }\n return credential;\n}\n\n/**\n * The WebAuthn ceremony. `create` registers a resident credential with `prf.eval` over the salts;\n * when the extension returns no results a single assertion over the new credential produces them.\n * `assert` runs a discoverable (or allowCredentials-scoped) get with the same eval shape.\n */\nexport function createWebAuthnPasskey(rp: WebAuthnRp): PasskeyCeremony {\n async function assert(input: {\n readonly credentialIds?: ReadonlyArray<Uint8Array>;\n readonly salts: PrfSalts;\n }): Promise<PrfResult> {\n let credential: PublicKeyCredential;\n try {\n credential = checkCredential(\n await navigator.credentials.get({\n publicKey: {\n rpId: rp.id,\n challenge: crypto.getRandomValues(new Uint8Array(32)),\n userVerification: \"required\",\n ...(input.credentialIds === undefined\n ? {}\n : {\n allowCredentials: input.credentialIds.map((id) => ({\n type: \"public-key\" as const,\n id: new Uint8Array(id),\n })),\n }),\n extensions: { prf: { eval: prfEval(input.salts) } },\n },\n }),\n );\n } catch (error) {\n mapCeremonyError(error);\n }\n const { response } = credential;\n if (!(response instanceof AuthenticatorAssertionResponse)) {\n throw factorUnavailable(\"the authenticator returned no assertion response\");\n }\n if (!userVerified(response.authenticatorData)) {\n throw factorUnavailable(\"the passkey assertion was not user-verified\");\n }\n return readPrfResult(\n credential,\n credential.getClientExtensionResults().prf?.results,\n input.salts.second !== undefined,\n credentialSynced(response.authenticatorData),\n response.userHandle ?? undefined,\n );\n }\n\n return {\n create: async (input) => {\n const userId = new Uint8Array(16);\n userId[0] = input.purpose === \"wallet\" ? PASSKEY_PURPOSE_WALLET : PASSKEY_PURPOSE_VAULT;\n crypto.getRandomValues(userId.subarray(1));\n let credential: PublicKeyCredential;\n try {\n credential = checkCredential(\n await navigator.credentials.create({\n publicKey: {\n rp: { id: rp.id, name: rp.name },\n user: {\n id: userId,\n name: input.name,\n displayName: input.name,\n },\n challenge: crypto.getRandomValues(new Uint8Array(32)),\n pubKeyCredParams: [\n { type: \"public-key\", alg: -7 },\n { type: \"public-key\", alg: -257 },\n ],\n authenticatorSelection: {\n residentKey: \"required\",\n userVerification: \"required\",\n },\n extensions: { prf: { eval: prfEval(input.salts) } },\n },\n }),\n );\n } catch (error) {\n mapCeremonyError(error);\n }\n const { response } = credential;\n if (!(response instanceof AuthenticatorAttestationResponse)) {\n throw factorUnavailable(\"the authenticator returned no attestation response\");\n }\n const extensions = credential.getClientExtensionResults();\n if (extensions.prf?.enabled !== true) {\n throw factorUnavailable(\"the authenticator does not support the PRF extension\");\n }\n if (extensions.prf.results === undefined) {\n // Some authenticators omit creation-time PRF outputs: one assertion over the new\n // credential produces them (it is that response whose UV and BE/BS flags get read).\n return assert({\n credentialIds: [copyBufferSource(credential.rawId)],\n salts: input.salts,\n });\n }\n const authenticatorData = response.getAuthenticatorData();\n if (!userVerified(authenticatorData)) {\n throw factorUnavailable(\"the passkey creation was not user-verified\");\n }\n return readPrfResult(\n credential,\n extensions.prf.results,\n input.salts.second !== undefined,\n credentialSynced(authenticatorData),\n );\n },\n assert,\n };\n}\n","/**\n * The Web host: `createWebWallet` spawns the bundled kernel worker (dist/kernel.worker.js) whose\n * vault lives in the worker's own IndexedDB. Lock destroys the worker; the vault survives in\n * IndexedDB, so the respawned worker reloads it. `pagehide` locks immediately; the idle timer is\n * refreshed by user-presence signals, not request traffic. The WebAuthn ceremony runs on the main\n * thread.\n */\nimport { WalletError } from \"@qntx/wallet\";\nimport type { Wallet, WalletOptions } from \"@qntx/wallet\";\nimport { connectKernel } from \"@qntx/wallet/host\";\nimport type { MainPort } from \"@qntx/wallet/host\";\nimport type { ProtocolValue } from \"@qntx/wallet/protocol\";\n\nimport type { BrowserWorkerInit } from \"../shared.ts\";\nimport { createWebAuthnPasskey } from \"./passkey.ts\";\n\nconst LISTENER_OPTIONS = { capture: true, passive: true };\nconst ACTIVITY_EVENTS = [\"pointerdown\", \"pointermove\", \"keydown\", \"wheel\", \"touchstart\"];\n\n/**\n * The browser's user-presence source: direct input on the page, or returning to a visible tab.\n * Capture-phase passive listeners do a single boolean check per event — high-frequency signals\n * (scroll, pointermove) cost nothing. Returns the unsubscribe the wallet owns.\n */\nfunction subscribePresence(onActivity: () => void): () => void {\n for (const type of ACTIVITY_EVENTS) {\n globalThis.addEventListener(type, onActivity, LISTENER_OPTIONS);\n }\n const onVisible = (): void => {\n if (document.visibilityState === \"visible\") {\n onActivity();\n }\n };\n document.addEventListener(\"visibilitychange\", onVisible);\n return () => {\n for (const type of ACTIVITY_EVENTS) {\n globalThis.removeEventListener(type, onActivity, LISTENER_OPTIONS);\n }\n document.removeEventListener(\"visibilitychange\", onVisible);\n };\n}\n\nasync function bestEffort(task: () => Promise<unknown>): Promise<void> {\n try {\n await task();\n } catch {\n // Lifecycle hooks and persistence hints must never surface failures.\n }\n}\n\nexport async function createWebWallet(options: WalletOptions): Promise<Wallet> {\n const { autoLock } = options;\n if (autoLock.platform !== \"web\") {\n throw new WalletError(\"invalid-input\", \"createWebWallet accepts only the web autoLock policy\");\n }\n // The page sends only the idle choice; the worker owns the maximum session duration.\n const init: BrowserWorkerInit = { t: \"init\", idleMinutes: autoLock.idleMinutes };\n const ceremony = createWebAuthnPasskey({\n id: options.passkey.rpId,\n name: options.passkey.rpName,\n });\n\n const spawn = (): MainPort => {\n // Keep this exact `new Worker(new URL(\"…\", import.meta.url))` shape: bundlers (Vite,\n // webpack, Parcel, esbuild-based tools) recognize only this literal form as a worker entry and\n // bundle the kernel with its chunks and spark.wasm. A precomputed URL is copied as one opaque\n // asset, and the worker then dies loading its first chunk.\n const worker = new Worker(new URL(\"kernel.worker.js\", import.meta.url), {\n type: \"module\",\n name: \"qntx-wallet\",\n });\n // oxlint-disable-next-line unicorn/require-post-message-target-origin -- Worker.postMessage takes a transfer list, not an origin\n worker.postMessage(init); // the kernel validates it strictly before startKernel\n return {\n post: (message: ProtocolValue, transfer: ArrayBuffer[]) => {\n worker.postMessage(message, transfer);\n },\n listen: (handler) => {\n const listener = (event: MessageEvent): void => {\n handler(event.data);\n };\n worker.addEventListener(\"message\", listener);\n return () => {\n worker.removeEventListener(\"message\", listener);\n };\n },\n onExit: (handler) => {\n // Web Workers have no exit event; error/messageerror are the only death signals.\n const listener = (): void => {\n handler();\n };\n worker.addEventListener(\"error\", listener);\n worker.addEventListener(\"messageerror\", listener);\n return () => {\n worker.removeEventListener(\"error\", listener);\n worker.removeEventListener(\"messageerror\", listener);\n };\n },\n terminate: () => {\n worker.terminate();\n },\n };\n };\n\n const wallet = await connectKernel({\n spawn,\n passkey: options.passkey,\n ceremony,\n setTimer: (delayMs, fn) => {\n const timer = globalThis.setTimeout(fn, delayMs);\n return () => {\n globalThis.clearTimeout(timer);\n };\n },\n presence: subscribePresence,\n onDestroy: () => {\n globalThis.removeEventListener(\"pagehide\", onPageHide);\n },\n });\n\n // Page lifecycle: lock on pagehide. A BFCache-frozen page still runs the queued locked-event\n // handler on restore, so the wallet respawns then — terminating the worker directly would\n // leave the wallet pointing at a dead port.\n const onPageHide = (): void => {\n void bestEffort(async () => wallet.vault.lock());\n };\n globalThis.addEventListener(\"pagehide\", onPageHide);\n\n // Ask the browser to persist the vault once it exists — a denied request is fine.\n let persisted = false;\n const unsubscribe = wallet.subscribe(() => {\n if (!persisted && wallet.getSnapshot().vault !== \"uninitialized\") {\n persisted = true;\n unsubscribe();\n void bestEffort(async () => navigator.storage.persist());\n }\n });\n\n return wallet;\n}\n\nexport { createWebAuthnPasskey } from \"./passkey.ts\";\nexport type { WebAuthnRp } from \"./passkey.ts\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"browser.mjs","names":[],"sources":["../src/browser/passkey.ts","../src/browser/index.ts"],"sourcesContent":["/**\n * The WebAuthn passkey ceremony (main thread only): `navigator.credentials` with the PRF extension.\n * A wallet passkey derives the wallet seed from `PRF(walletSalt)`; the vault factor derives the\n * vault KEK from `PRF(vaultSalt)` on the same credential. Every ceremony output is checked for\n * `prf.enabled`, PRF results and the user-verification flag — a credential that cannot prove UV\n * never reaches the kernel.\n */\nimport { WalletError } from \"@qntx/wallet\";\nimport { PASSKEY_PURPOSE_VAULT, PASSKEY_PURPOSE_WALLET } from \"@qntx/wallet/host\";\nimport type { PasskeyCeremony, PrfResult, PrfSalts } from \"@qntx/wallet/host\";\n\nexport type WebAuthnRp = {\n /** RP id — the origin's effective domain, e.g. `location.hostname`. */\n readonly id: string;\n readonly name: string;\n};\n\nconst UV_FLAG = 0x04; // authenticator-data flags byte (offset 32), bit 2 = user verified\nconst BE_FLAG = 0x08; // bit 3 = credential is backup-eligible (multi-device capable)\nconst BS_FLAG = 0x10; // bit 4 = credential is currently backed up (synced)\n\nfunction factorUnavailable(message: string): WalletError {\n return new WalletError(\"factor-unavailable\", message);\n}\n\nfunction cancelled(message: string): WalletError {\n return new WalletError(\"cancelled\", message);\n}\n\nfunction mapCeremonyError(error: unknown): never {\n if (\n error instanceof DOMException &&\n (error.name === \"NotAllowedError\" || error.name === \"AbortError\")\n ) {\n throw cancelled(\"the passkey ceremony was cancelled\");\n }\n if (error instanceof WalletError) {\n throw error;\n }\n throw factorUnavailable(\"the passkey ceremony failed\");\n}\n\n/** The UV bit of an authenticator-data blob (flags byte at offset 32, bit 0x04). */\nfunction userVerified(authenticatorData: ArrayBuffer): boolean {\n return (\n authenticatorData.byteLength > 32 &&\n // oxlint-disable-next-line eslint/no-bitwise -- flags bit test\n (new DataView(authenticatorData).getUint8(32) & UV_FLAG) !== 0\n );\n}\n\n/**\n * Whether the credential is backed up off-device (flags byte at offset 32): synced requires both BE\n * (the credential is eligible for backup) and BS (the authenticator actually backs it up).\n */\nfunction credentialSynced(authenticatorData: ArrayBuffer): boolean {\n return (\n authenticatorData.byteLength > 32 &&\n // oxlint-disable-next-line eslint/no-bitwise -- flags bit test\n (new DataView(authenticatorData).getUint8(32) & (BE_FLAG | BS_FLAG)) === (BE_FLAG | BS_FLAG)\n );\n}\n\nfunction prfEval(salts: PrfSalts): AuthenticationExtensionsPRFValues {\n // The constructor copy narrows Uint8Array<ArrayBufferLike> to Uint8Array<ArrayBuffer> —\n // the DOM BufferSource does not admit shared-array-buffer views.\n return salts.second === undefined\n ? { first: new Uint8Array(salts.first) }\n : { first: new Uint8Array(salts.first), second: new Uint8Array(salts.second) };\n}\n\n/** A detached copy of a BufferSource — the returned Uint8Array owns its own fresh buffer. */\nfunction copyBufferSource(source: BufferSource): Uint8Array {\n const view =\n source instanceof ArrayBuffer\n ? new Uint8Array(source)\n : new Uint8Array(source.buffer, source.byteOffset, source.byteLength);\n return new Uint8Array(view);\n}\n\nfunction readPrfResult(\n credential: PublicKeyCredential,\n results: AuthenticationExtensionsPRFValues | undefined,\n wantSecond: boolean,\n synced: boolean,\n userHandle?: ArrayBuffer,\n): PrfResult {\n const first = results?.first;\n const second = results?.second;\n if (first === undefined || (wantSecond && second === undefined)) {\n throw factorUnavailable(\"the passkey produced no PRF output\");\n }\n // Copy into fresh Uint8Arrays and drop every reference to the extension's ArrayBuffers.\n return {\n credentialId: copyBufferSource(credential.rawId),\n first: copyBufferSource(first),\n ...(second === undefined ? {} : { second: copyBufferSource(second) }),\n ...(userHandle === undefined ? {} : { userHandle: copyBufferSource(userHandle) }),\n synced,\n };\n}\n\nfunction checkCredential(credential: unknown): PublicKeyCredential {\n if (!(credential instanceof PublicKeyCredential)) {\n throw factorUnavailable(\"the authenticator returned no public-key credential\");\n }\n return credential;\n}\n\n/**\n * The WebAuthn ceremony. `create` registers a resident credential with `prf.eval` over the salts;\n * when the extension returns no results a single assertion over the new credential produces them.\n * `assert` runs a discoverable (or allowCredentials-scoped) get with the same eval shape.\n */\nexport function createWebAuthnPasskey(rp: WebAuthnRp): PasskeyCeremony {\n async function assert(input: {\n readonly credentialIds?: ReadonlyArray<Uint8Array>;\n readonly salts: PrfSalts;\n }): Promise<PrfResult> {\n let credential: PublicKeyCredential;\n try {\n credential = checkCredential(\n await navigator.credentials.get({\n publicKey: {\n rpId: rp.id,\n challenge: crypto.getRandomValues(new Uint8Array(32)),\n userVerification: \"required\",\n ...(input.credentialIds === undefined\n ? {}\n : {\n allowCredentials: input.credentialIds.map((id) => ({\n type: \"public-key\" as const,\n id: new Uint8Array(id),\n })),\n }),\n extensions: { prf: { eval: prfEval(input.salts) } },\n },\n }),\n );\n } catch (error) {\n mapCeremonyError(error);\n }\n const { response } = credential;\n if (!(response instanceof AuthenticatorAssertionResponse)) {\n throw factorUnavailable(\"the authenticator returned no assertion response\");\n }\n if (!userVerified(response.authenticatorData)) {\n throw factorUnavailable(\"the passkey assertion was not user-verified\");\n }\n return readPrfResult(\n credential,\n credential.getClientExtensionResults().prf?.results,\n input.salts.second !== undefined,\n credentialSynced(response.authenticatorData),\n response.userHandle ?? undefined,\n );\n }\n\n return {\n create: async (input) => {\n const userId = new Uint8Array(16);\n userId[0] = input.purpose === \"wallet\" ? PASSKEY_PURPOSE_WALLET : PASSKEY_PURPOSE_VAULT;\n crypto.getRandomValues(userId.subarray(1));\n let credential: PublicKeyCredential;\n try {\n credential = checkCredential(\n await navigator.credentials.create({\n publicKey: {\n rp: { id: rp.id, name: rp.name },\n user: {\n id: userId,\n name: input.name,\n displayName: input.name,\n },\n challenge: crypto.getRandomValues(new Uint8Array(32)),\n pubKeyCredParams: [\n { type: \"public-key\", alg: -7 },\n { type: \"public-key\", alg: -257 },\n ],\n authenticatorSelection: {\n residentKey: \"required\",\n userVerification: \"required\",\n },\n extensions: { prf: { eval: prfEval(input.salts) } },\n },\n }),\n );\n } catch (error) {\n mapCeremonyError(error);\n }\n const { response } = credential;\n if (!(response instanceof AuthenticatorAttestationResponse)) {\n throw factorUnavailable(\"the authenticator returned no attestation response\");\n }\n const extensions = credential.getClientExtensionResults();\n if (extensions.prf?.enabled !== true) {\n throw factorUnavailable(\"the authenticator does not support the PRF extension\");\n }\n if (extensions.prf.results === undefined) {\n // Some authenticators omit creation-time PRF outputs: one assertion over the new\n // credential produces them (it is that response whose UV and BE/BS flags get read).\n return assert({\n credentialIds: [copyBufferSource(credential.rawId)],\n salts: input.salts,\n });\n }\n const authenticatorData = response.getAuthenticatorData();\n if (!userVerified(authenticatorData)) {\n throw factorUnavailable(\"the passkey creation was not user-verified\");\n }\n return readPrfResult(\n credential,\n extensions.prf.results,\n input.salts.second !== undefined,\n credentialSynced(authenticatorData),\n );\n },\n assert,\n };\n}\n","/**\n * The Web host: `createWebWallet` spawns the bundled kernel worker (dist/kernel.worker.js) whose\n * vault lives in the worker's own IndexedDB. Lock destroys the worker; the vault survives in\n * IndexedDB, so the respawned worker reloads it. `pagehide` locks immediately; the idle timer is\n * refreshed by user-presence signals, not request traffic. The WebAuthn ceremony runs on the main\n * thread.\n */\nimport { WalletError } from \"@qntx/wallet\";\nimport type { Wallet, WalletOptions } from \"@qntx/wallet\";\nimport { connectKernel } from \"@qntx/wallet/host\";\nimport type { MainPort } from \"@qntx/wallet/host\";\nimport type { ProtocolValue } from \"@qntx/wallet/protocol\";\n\nimport type { BrowserWorkerInit } from \"../shared.ts\";\nimport { createWebAuthnPasskey } from \"./passkey.ts\";\n\nconst LISTENER_OPTIONS = { capture: true, passive: true };\nconst ACTIVITY_EVENTS = [\"pointerdown\", \"pointermove\", \"keydown\", \"wheel\", \"touchstart\"];\n\n/**\n * The browser's user-presence source: direct input on the page, or returning to a visible tab.\n * Capture-phase passive listeners do a single boolean check per event — high-frequency signals\n * (scroll, pointermove) cost nothing. Returns the unsubscribe the wallet owns.\n */\nfunction subscribePresence(onActivity: () => void): () => void {\n for (const type of ACTIVITY_EVENTS) {\n globalThis.addEventListener(type, onActivity, LISTENER_OPTIONS);\n }\n const onVisible = (): void => {\n if (document.visibilityState === \"visible\") {\n onActivity();\n }\n };\n document.addEventListener(\"visibilitychange\", onVisible);\n return () => {\n for (const type of ACTIVITY_EVENTS) {\n globalThis.removeEventListener(type, onActivity, LISTENER_OPTIONS);\n }\n document.removeEventListener(\"visibilitychange\", onVisible);\n };\n}\n\nasync function bestEffort(task: () => Promise<unknown>): Promise<void> {\n try {\n await task();\n } catch {\n // Lifecycle hooks and persistence hints must never surface failures.\n }\n}\n\nexport async function createWebWallet(options: WalletOptions): Promise<Wallet> {\n const { autoLock } = options;\n if (autoLock.platform !== \"web\") {\n throw new WalletError(\"invalid-input\", \"createWebWallet accepts only the web autoLock policy\");\n }\n // The page sends only the idle choice; the worker owns the maximum session duration.\n const init: BrowserWorkerInit = { t: \"init\", idleMinutes: autoLock.idleMinutes };\n const ceremony = createWebAuthnPasskey({\n id: options.passkey.rpId,\n name: options.passkey.rpName,\n });\n\n const spawn = (): MainPort => {\n // Keep this exact `new Worker(new URL(\"…\", import.meta.url))` shape: bundlers (Vite,\n // webpack, Parcel, esbuild-based tools) recognize only this literal form as a worker entry and\n // bundle the kernel with its chunks and spark.wasm. A precomputed URL is copied as one opaque\n // asset, and the worker then dies loading its first chunk.\n const worker = new Worker(new URL(\"kernel.worker.js\", import.meta.url), {\n type: \"module\",\n name: \"qntx-wallet\",\n });\n // oxlint-disable-next-line unicorn/require-post-message-target-origin -- Worker.postMessage takes a transfer list, not an origin\n worker.postMessage(init); // the kernel validates it strictly before startKernel\n return {\n post: (message: ProtocolValue, transfer: ArrayBuffer[]) => {\n worker.postMessage(message, transfer);\n },\n listen: (handler) => {\n const listener = (event: MessageEvent): void => {\n handler(event.data);\n };\n worker.addEventListener(\"message\", listener);\n return () => {\n worker.removeEventListener(\"message\", listener);\n };\n },\n onExit: (handler) => {\n // Web Workers have no exit event; error/messageerror are the only death signals.\n const listener = (): void => {\n handler(\"crashed\");\n };\n worker.addEventListener(\"error\", listener);\n worker.addEventListener(\"messageerror\", listener);\n return () => {\n worker.removeEventListener(\"error\", listener);\n worker.removeEventListener(\"messageerror\", listener);\n };\n },\n terminate: () => {\n worker.terminate();\n },\n };\n };\n\n const wallet = await connectKernel({\n factors: \"software\",\n spawn,\n passkey: options.passkey,\n ceremony,\n setTimer: (delayMs, fn) => {\n const timer = globalThis.setTimeout(fn, delayMs);\n return () => {\n globalThis.clearTimeout(timer);\n };\n },\n presence: subscribePresence,\n onDestroy: () => {\n globalThis.removeEventListener(\"pagehide\", onPageHide);\n },\n });\n\n // Page lifecycle: lock on pagehide. A BFCache-frozen page still runs the queued locked-event\n // handler on restore, so the wallet respawns then — terminating the worker directly would\n // leave the wallet pointing at a dead port.\n const onPageHide = (): void => {\n void bestEffort(async () => wallet.vault.lock());\n };\n globalThis.addEventListener(\"pagehide\", onPageHide);\n\n // Ask the browser to persist the vault once it exists — a denied request is fine.\n let persisted = false;\n const unsubscribe = wallet.subscribe(() => {\n if (!persisted && wallet.getSnapshot().vault !== \"uninitialized\") {\n persisted = true;\n unsubscribe();\n void bestEffort(async () => navigator.storage.persist());\n }\n });\n\n return wallet;\n}\n\nexport { createWebAuthnPasskey } from \"./passkey.ts\";\nexport type { WebAuthnRp } from \"./passkey.ts\";\n"],"mappings":"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"}
@@ -4,7 +4,7 @@
4
4
  "backends": {
5
5
  "spark": {
6
6
  "abiVersion": 1,
7
- "wasmIntegrity": "sha256-PFqThpVpNZHbmIuR13MZqLAupIIhfVcd3nMtB9QMTTE="
7
+ "wasmIntegrity": "sha256-OBAVP0QjuY/JyeQh6Ay3XnczAKsMYrWoHdCJ73RSPeQ="
8
8
  }
9
9
  },
10
10
  "libraries": [
@@ -25,18 +25,18 @@
25
25
  },
26
26
  {
27
27
  "name": "@qntx/nostr",
28
- "version": "0.15.0",
29
- "integrity": "sha512-bXgvNSlj4p/uw996x8rlB9bI50wcY+uH9rq+2lJBn6hZAUetBrf6ujTrKRQCpzg5DS2MwBWl4kmV4aXLzPUFRQ=="
28
+ "version": "0.15.3",
29
+ "integrity": "sha512-AefcRXe/3basIgDA0gBGzzQ2Y9k131SfrU0hZ+eJqfLzCSzsVJyum5XKzffJ74VU6FroQPbS/fSS7W5mGd22ow=="
30
30
  },
31
31
  {
32
32
  "name": "@qntx/spark",
33
- "version": "0.5.0",
34
- "integrity": "sha512-nRJzrACUqRkKPwZP676dtYHXLWDTb2AzhVKLzN8PLlrcF7IEDYjQvlAMV0USC22vcQh1EqtWPvLGG6X/dCQ4Pw=="
33
+ "version": "0.5.2",
34
+ "integrity": "sha512-x5J1vJCuUdW5ANldVuRJRu5qczxkENhnZqEWH5PQt7dKKGqJbuLvsV6EyphKbf0MoYYM2kxWalW0NCzoXojamQ=="
35
35
  },
36
36
  {
37
37
  "name": "@qntx/spark-wasm",
38
- "version": "0.5.0",
39
- "integrity": "sha512-C+TQc3xr7TtIF/jX+gcgAKOgvxVqKR70fVZQHzSd6ulnjwXGqS/CmfjgH59kqEvTrsl6vyMu1r7p4cOweTTVWQ=="
38
+ "version": "0.5.2",
39
+ "integrity": "sha512-0ZgjgOeIAMvWNmFq/StiG6ymQKvFRJkTBTjkfda04ooeBv5/32p+u39CFeN1G0hxnCBiDYe5FOAkrrGXVZ5VxQ=="
40
40
  },
41
41
  {
42
42
  "name": "@scure/base",
@@ -52,40 +52,50 @@
52
52
  "name": "@scure/bip39",
53
53
  "version": "2.4.0",
54
54
  "integrity": "sha512-82dxFbZUYboyOf0AXiydsQrFQ5Q4h9mX+O2UkE91ROYmsc0BKMGZLwDmy96Jpa2+vrtoxomjUhy1RPIgH/r2nA=="
55
+ },
56
+ {
57
+ "name": "@scure/btc-signer",
58
+ "version": "2.4.1",
59
+ "integrity": "sha512-ch6h66o9nPhEoe/KciMMUJeH2MBPB8UAJI1if35Fxl6885lKTsc9YoCRqTW0/RjI0Vv9GSg1tmKtHHODyAw81g=="
60
+ },
61
+ {
62
+ "name": "micro-key-producer",
63
+ "version": "0.10.2",
64
+ "integrity": "sha512-ApVEYeOhHwC4fsf54Jdt3Cf21eYfib/9GANrZCjlMCl1Sa71cXncS4J6wQ3IlbchBCvulOjQa2UoWf6/aE9XgA=="
55
65
  }
56
66
  ],
57
67
  "bundles": [
58
68
  {
59
69
  "file": "kernel.worker.js",
60
- "sha256": "f6bbcd9c410672061398fba3b1aa0b407667c2ab5828fe00a69ceba270113d1b"
70
+ "sha256": "50ca92701265a22ae4206c83aa4f1885fd4124b9e35d94983ed5ce2b387f58b9"
61
71
  },
62
72
  {
63
- "file": "node-gxb54jcq.js",
64
- "sha256": "e58ca528749b9191f761652a3ad7dedc961e2ea2e7fc61987c9ed1afa588eb94"
73
+ "file": "browser-1vpxmqwn.js",
74
+ "sha256": "e5afdeec6f071a87cb6a1040ddeb1b9995d53ed2cea239f8f9424dc1d3df601b"
65
75
  },
66
76
  {
67
- "file": "worker-gxb54jcq.js",
68
- "sha256": "e58ca528749b9191f761652a3ad7dedc961e2ea2e7fc61987c9ed1afa588eb94"
77
+ "file": "kernel.node.js",
78
+ "sha256": "e2f99d5c064f2627925d679d4d3a4420749d2c0a70b5f49e3916e17125683a35"
69
79
  },
70
80
  {
71
- "file": "kernel.node.js",
72
- "sha256": "ed259c5cc067602e8344a731403b5a05c37d46d234978a30f6b8a577936791c4"
81
+ "file": "node-gsare3mw.js",
82
+ "sha256": "163a8aeb01c8d699a7567eecb90c6d5c859151d1e7322c3be0487eb13940b9c7"
73
83
  },
74
84
  {
75
- "file": "browser-dy5h3c5e.js",
76
- "sha256": "31c1cc26ff286d492668c5b4bf60833283cf8f17b252bdc234fbba94c67db155"
85
+ "file": "worker-366p4fej.js",
86
+ "sha256": "449410efad818a8f1dcfd02840375c1748c61294ad172e4c6c57e9d1d7c81283"
77
87
  },
78
88
  {
79
- "file": "node-nhrchcf5.js",
80
- "sha256": "55fdd5775b473d0f283502976f68a3413b8a5587ca6293b1021fe008fcbf8302"
89
+ "file": "signer-gs57sjgx.js",
90
+ "sha256": "e83ab645d9efc41a821e572768e4b28a7902336ccfa692c2101755284b2a7d42"
81
91
  },
82
92
  {
83
- "file": "signer-yectsdty.js",
84
- "sha256": "6f26acebc7bb9b40b0074ef3efea88c1126d416b710e8e04e3deb297364aa82a"
93
+ "file": "node-366p4fej.js",
94
+ "sha256": "449410efad818a8f1dcfd02840375c1748c61294ad172e4c6c57e9d1d7c81283"
85
95
  },
86
96
  {
87
- "file": "worker-nhrchcf5.js",
88
- "sha256": "55fdd5775b473d0f283502976f68a3413b8a5587ca6293b1021fe008fcbf8302"
97
+ "file": "worker-gsare3mw.js",
98
+ "sha256": "163a8aeb01c8d699a7567eecb90c6d5c859151d1e7322c3be0487eb13940b9c7"
89
99
  }
90
100
  ]
91
101
  }