@seams/voiceid 0.26.0 → 0.27.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.
- package/README.md +19 -1
- package/dist/esm/browser/microphoneInput.js +13 -0
- package/dist/esm/capture/prompt.js +41 -2
- package/dist/public/sdk/voiceid/{addVoiceId-Bz9BDPI4.js → addVoiceId-DOpYRrar.js} +1 -1
- package/dist/public/sdk/voiceid/approval-DNlI4I0d.js +1 -0
- package/dist/public/sdk/voiceid/{deviceBridge-DDviNxL6.js → deviceBridge-L6ZKqifG.js} +1 -1
- package/dist/public/sdk/voiceid/enrollment-Cs7Tzcho.js +1 -0
- package/dist/public/sdk/voiceid/host.js +2 -2
- package/dist/public/sdk/voiceid/prompt-g_wE9gYv.js +614 -0
- package/dist/public/sdk/voiceid/{unlock-CGnhhdt4.js → unlock-B9WYZcqy.js} +1 -1
- package/dist/types/browser/inference.d.ts +5 -0
- package/dist/types/browser/microphone.d.ts +6 -6
- package/dist/types/browser/microphoneInput.d.ts +27 -0
- package/dist/types/browser/progress.d.ts +1 -1
- package/dist/types/capture/approval.d.ts +1 -0
- package/dist/types/capture/prompt.d.ts +4 -0
- package/dist/types/host.d.ts +1 -1
- package/package.json +2 -2
- package/dist/public/sdk/voiceid/approval-CME3DxNc.js +0 -1
- package/dist/public/sdk/voiceid/enrollment-D8oQnM_e.js +0 -1
- package/dist/public/sdk/voiceid/prompt-DEkI70F2.js +0 -606
|
@@ -1 +1 @@
|
|
|
1
|
-
import"./prompt-
|
|
1
|
+
import"./prompt-g_wE9gYv.js";import{a as e,n as t}from"./deviceBridge-L6ZKqifG.js";import{f as n,o as r,s as i}from"./enrollment-Cs7Tzcho.js";import{IndexedDBManager as a,fetchWithWalletHomeDiscovery as o,readAuthMethodEnabled as s}from"../voiceid-host.js";async function c(e){let{authMethod:t,challenge:n}=e;if(n.walletId!==t.walletId||n.walletAuthMethodId!==t.walletAuthMethodId||n.walletAuthorityId!==t.walletAuthorityId||n.deviceKeyId!==t.deviceKeyId)throw Error(`VoiceID challenge does not match the selected device method.`);let r=await e.capture({challenge:n,signal:e.signal,onCapture:e.onCapture,phrase:e.phrase});try{if(e.signal.aborted)throw Error(`VoiceID unlock was cancelled.`);let n=await e.http.verify({assertion:r.assertion,signal:e.signal});if(e.signal.aborted)throw Error(`VoiceID unlock was cancelled.`);if(n.walletSession.walletId!==t.walletId||n.walletSession.authMethodId!==t.walletAuthMethodId||n.walletSession.authorityId!==t.walletAuthorityId||n.custodyEnvelope.factor.deviceKeyId!==t.deviceKeyId)throw Error(`VoiceID server verified a different device method.`);return await e.install(n,r.factorSecret32)}finally{r.factorSecret32.fill(0)}}async function l(t,c,l){if(!await s(t.walletId,t.walletAuthMethodId))throw Error(`VoiceID is turned off in this browser. Enable it in Authentication methods.`);let u=await t.prepareSession(c),f=await a.getWalletAuthMethodV2(t.walletAuthMethodId);if(!f||f.kind!==`voiceid`||f.status!==`active`||f.walletId!==t.walletId||f.walletAuthMethodId!==t.walletAuthMethodId)throw Error(`Activate VoiceID on this device before unlocking this wallet.`);let p=new e(t.relayerUrl,location.origin,o);if(!await r(await i(t.walletId,t.walletAuthMethodId)))throw Error(`Unlock with a passkey, then open Manage voice to import or record your enrollment.`);await n(l,c),c.throwIfAborted(),t.assertActive();let m=await p.challenge({walletId:t.walletId,walletAuthMethodId:t.walletAuthMethodId,signal:AbortSignal.any([c,AbortSignal.timeout(15e3)])});t.assertActive();let h={authMethod:f,challenge:m,http:p,install:u};return c.throwIfAborted(),{kind:`recording`,expiresAtMs:m.expiresAtMs,action:d.bind(null,h)}}function u(e,t,n,r){return e({walletSession:n.walletSession,operationCredential:n.operationCredential,custodyEnvelope:n.custodyEnvelope,factorSecret32:r,signal:t,near:n.near})}async function d(e,n,r,i){return await c({authMethod:e.authMethod,challenge:e.challenge,http:e.http,capture:t,install:u.bind(null,e.install,n),signal:n,onCapture:r,phrase:i})}export{l as prepareUnlockSurface};
|
|
@@ -13,6 +13,10 @@ export type VoiceAssetProgress = {
|
|
|
13
13
|
} | {
|
|
14
14
|
kind: 'ready';
|
|
15
15
|
};
|
|
16
|
+
export declare class VoiceIdLoadError extends Error {
|
|
17
|
+
readonly kind: 'connection' | 'unavailable' | 'integrity' | 'initialization';
|
|
18
|
+
constructor(kind: 'connection' | 'unavailable' | 'integrity' | 'initialization', message: string, options?: ErrorOptions);
|
|
19
|
+
}
|
|
16
20
|
declare class BrowserInferenceAsset {
|
|
17
21
|
private readonly asset;
|
|
18
22
|
private pending;
|
|
@@ -25,6 +29,7 @@ declare class BrowserInferenceAsset {
|
|
|
25
29
|
private publish;
|
|
26
30
|
private valid;
|
|
27
31
|
private bytes;
|
|
32
|
+
private fetchBytes;
|
|
28
33
|
}
|
|
29
34
|
export declare const voiceInferenceEngine: BrowserInferenceAsset;
|
|
30
35
|
export declare class BrowserInferenceModel {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type VoiceMicrophone } from './microphoneInput';
|
|
1
2
|
/** One 32 ms frame as the recorder judged it. No capture event carries the stream. */
|
|
2
3
|
export type VoiceActivityEvent = Readonly<{
|
|
3
4
|
kind: 'activity';
|
|
@@ -7,6 +8,9 @@ export type VoiceActivityEvent = Readonly<{
|
|
|
7
8
|
stream?: never;
|
|
8
9
|
}>;
|
|
9
10
|
export type VoiceCaptureEvent = {
|
|
11
|
+
readonly kind: 'opening';
|
|
12
|
+
readonly stream?: never;
|
|
13
|
+
} | {
|
|
10
14
|
readonly kind: 'listening';
|
|
11
15
|
readonly stream?: never;
|
|
12
16
|
} | VoiceActivityEvent | {
|
|
@@ -29,12 +33,8 @@ export type VoiceCaptureQuality = Readonly<{
|
|
|
29
33
|
levelThreshold: number;
|
|
30
34
|
requiredVoicedMs: number;
|
|
31
35
|
microphone: string;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
echoCancellation: boolean | string | null;
|
|
35
|
-
autoGainControl: boolean | null;
|
|
36
|
-
sampleRate: number | null;
|
|
37
|
-
}>;
|
|
36
|
+
microphoneSelection: VoiceMicrophone['selection'];
|
|
37
|
+
processing: VoiceMicrophone['processing'];
|
|
38
38
|
backgroundRms: number;
|
|
39
39
|
speechToBackgroundDb: number | null;
|
|
40
40
|
noise: 'high' | 'normal' | 'unknown';
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
type MicrophonePreference = {
|
|
2
|
+
readonly kind: 'system_default';
|
|
3
|
+
readonly deviceId?: never;
|
|
4
|
+
} | {
|
|
5
|
+
readonly kind: 'device';
|
|
6
|
+
readonly deviceId: string;
|
|
7
|
+
};
|
|
8
|
+
export type VoiceMicrophone = Readonly<{
|
|
9
|
+
stream: MediaStream;
|
|
10
|
+
preference: MicrophonePreference;
|
|
11
|
+
selection: 'default' | 'preferred' | 'replacement';
|
|
12
|
+
label: string;
|
|
13
|
+
processing: Readonly<{
|
|
14
|
+
noiseSuppression: boolean | null;
|
|
15
|
+
echoCancellation: boolean | string | null;
|
|
16
|
+
autoGainControl: boolean | null;
|
|
17
|
+
sampleRate: number | null;
|
|
18
|
+
}>;
|
|
19
|
+
}>;
|
|
20
|
+
export declare function openVoiceMicrophone(signal: AbortSignal): Promise<VoiceMicrophone>;
|
|
21
|
+
export declare function rememberVoiceMicrophone(microphone: VoiceMicrophone): Promise<void>;
|
|
22
|
+
export declare function useSystemVoiceMicrophone(): Promise<void>;
|
|
23
|
+
export declare class VoiceMicrophoneInterruptedError extends Error {
|
|
24
|
+
constructor();
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=microphoneInput.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare function prepareVoiceModelWithProgress(parent: HTMLElement): Promise<void>;
|
|
1
|
+
export declare function prepareVoiceModelWithProgress(parent: HTMLElement, signal: AbortSignal): Promise<void>;
|
|
2
2
|
//# sourceMappingURL=progress.d.ts.map
|
|
@@ -27,6 +27,7 @@ export declare class VoiceIdApproval {
|
|
|
27
27
|
private state;
|
|
28
28
|
private constructor();
|
|
29
29
|
static forReview(review: TransactionReviewWire, operationHash: string, walletId: WalletId, confirmation: VoiceConfirmation): VoiceIdApproval;
|
|
30
|
+
prepare(signal: AbortSignal): Promise<void>;
|
|
30
31
|
approve(signal: AbortSignal): Promise<AuthMethodApprovalResult>;
|
|
31
32
|
/** Binds one listening attempt to the operation and to what remains of the review. */
|
|
32
33
|
private beginAttempt;
|
|
@@ -9,6 +9,8 @@ export declare class VoicePrompt {
|
|
|
9
9
|
readonly record: HTMLButtonElement;
|
|
10
10
|
readonly cancel: HTMLButtonElement;
|
|
11
11
|
readonly progress: HTMLElement;
|
|
12
|
+
private readonly microphone;
|
|
13
|
+
private microphoneRequested;
|
|
12
14
|
private readonly visual;
|
|
13
15
|
private disposed;
|
|
14
16
|
constructor(document: Document, label: string, phrase: SpokenPhrase, checking?: VoiceCheckingStyle);
|
|
@@ -22,6 +24,8 @@ export declare class VoicePrompt {
|
|
|
22
24
|
*/
|
|
23
25
|
succeed(headline: string, message: string, area?: SuccessArea): Promise<void>;
|
|
24
26
|
ready(message: string): void;
|
|
27
|
+
private readonly changeMicrophone;
|
|
28
|
+
private useSystemMicrophone;
|
|
25
29
|
dispose(): void;
|
|
26
30
|
/** The ribbon turns into the ring and the copy may change, so the card's rows hold their size. */
|
|
27
31
|
private lockLayout;
|
package/dist/types/host.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seams/voiceid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Optional browser VoiceID authentication for Seams Wallet",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"voice-glow": "0.2.1"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
|
-
"@seams/wallet": "0.
|
|
53
|
+
"@seams/wallet": "0.27.0",
|
|
54
54
|
"react": "^18.0.0 || ^19.0.0"
|
|
55
55
|
},
|
|
56
56
|
"peerDependenciesMeta": {
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{_ as e,c as t,f as n,t as r,u as i}from"./prompt-DEkI70F2.js";import{f as a,n as o,s}from"./enrollment-D8oQnM_e.js";import{ActionType as c,IndexedDBManager as l,TransactionReviewError as u,isPlainObject as d,parseTransactionReviewWire as f,resolveNearNetwork as p,walletForHostRequest as m,walletHostExecutionFingerprint as h}from"../voiceid-host.js";function g(e){if(e===void 0)return{kind:`random`};if(!d(e)||Object.keys(e).length!==1||typeof e.confirmationCommand!=`string`||e.confirmationCommand.length>256)throw _();let t=e.confirmationCommand.normalize(`NFC`).trim();if(!t||/[\u0000-\u001f\u007f]/.test(t))throw _();switch(/^[a-z]+\b/i.exec(t)?.[0].toLowerCase()){case`sell`:return{kind:`action`,action:`sale`};case`buy`:return{kind:`action`,action:`purchase`};case`swap`:return{kind:`action`,action:`swap`};case`send`:case`transfer`:return{kind:`action`,action:`transfer`};default:return{kind:`random`}}}function _(){return new u(`review_invalid_input`,`voiceId requires only a nonempty confirmationCommand of at most 256 characters without control characters.`)}function v(e,t){return e.attemptId===t.attemptId&&e.operationHash===t.operationHash&&e.expiresAtMs===t.expiresAtMs}async function y(n,r,a,s,c,l){let u=Math.min(r.expiresAtMs,Date.now()+a);if(s.aborted||u<=Date.now())throw Error(`VoiceID review expired or was cancelled.`);let d=await o({kind:`active`,scope:n},s,c,l);if(s.aborted||u<=Date.now())throw Error(`VoiceID review expired or was cancelled.`);switch(d.kind){case`matched`:return{kind:`approved`};case`insufficient_audio`:return{kind:`retry`,message:t(d.quality)};case`mismatch`:return{kind:`retry`,message:new e(d.quality).message};case`phrase_mismatch`:return{kind:`retry`,message:new i().message};default:return b(d)}}function b(e){throw Error(`Unexpected voice result: ${String(e)}`)}function x(e){throw Error(`Unexpected voice outcome: ${String(e)}`)}function S(e){if(!(!d(e)&&!Array.isArray(e))){for(let t of Object.values(e))S(t);Object.freeze(e)}}async function C(e,t){switch(e.type){case`PM_SIGN_TX_WITH_ACTIONS`:case`PM_SIGN_DELEGATE_ACTION`:case`PM_SIGN_NEP413`:throw D();case`PM_SIGN_TEMPO`:{let n=e.payload;if(!n||n.operationKind!==`evm_transaction`)throw D();let{tx:r}=n.request,i=n.chainTarget;if(!t.config.network.chains.some(w)||i.kind!==`evm`||i.namespace!==`eip155`||i.networkSlug!==`arc-testnet`||i.chainId!==5042002||n.request.chain!==`evm`||n.request.kind!==`eip1559`||n.request.senderSignatureAlgorithm!==`secp256k1`||r.chainId!==i.chainId||typeof r.to!=`string`||!/^0x[0-9a-fA-F]{40}$/.test(r.to)||r.data!==void 0&&r.data!==`0x`||r.accessList!==void 0&&r.accessList.length!==0||r.abi!==void 0)throw D();return await E(e,t,g(n.options?.voiceId))}case`PM_SIGN_AND_SEND_TX`:{if(p(t.config.network.chains)!==`testnet`)throw new u(`review_unsupported_mode`,`This VoiceID prototype requires NEAR testnet.`);let n=e.payload;if(!n||n.transaction.actions.length!==1||n.transaction.actions[0].type!==c.Transfer||!(`transactionReview`in e))throw D();return await E(e,t,g(n.options.voiceId))}default:return{kind:`disabled`}}}function w(e){return e.network===`arc-testnet`&&e.chainId===5042002}function T(e,t){return typeof t==`bigint`?{bigint:t.toString()}:t}async function E(e,t,n){let r=f(e.transactionReview);if(r.validity.kind!==`expires_at`)throw new u(`review_invalid_input`,`VoiceID requires a review deadline.`);S(e);let i=new TextEncoder().encode(JSON.stringify({domain:`seams-voiceid-operation-v1`,origin:location.origin,executionFingerprint:h(t.config),request:e},T)),a=await crypto.subtle.digest(`SHA-256`,i),o=``;for(let e of new Uint8Array(a))o+=e.toString(16).padStart(2,`0`);let s=m(e,null);if(!s)throw Error(`VoiceID review requires a wallet identity.`);return{kind:`required`,approval:O.forReview(r,o,s,n)}}function D(){return new u(`review_unsupported_mode`,`VoiceID requires a reviewed NEAR or Arc testnet transfer.`)}var O=class e{phrase;prompt;surface;get messages(){return{confirm:`Confirm with VoiceID`,approved:`Voice approved this operation.`}}state={kind:`pending`};constructor(e,t,i,a){this.reviewExpiresAtMs=e,this.operationHash=t,this.walletId=i,this.confirmation=a,this.phrase=n(a,null),this.prompt=new r(document,`Approve transfer with VoiceID`,this.phrase,`segments`),this.prompt.record.hidden=!0,this.prompt.cancel.hidden=!0,this.surface=this.prompt.element}static forReview(t,n,r,i){if(t.validity.kind!==`expires_at`||!/^[0-9a-f]{64}$/.test(n))throw new u(`review_invalid_input`,`VoiceID requires a bounded operation.`);return new e(t.validity.atMs,n,r,i)}async approve(e){if(this.state.kind===`approved`){if(e.aborted||performance.now()>=this.state.deadline||Date.now()>=this.state.binding.expiresAtMs)throw Error(`Voice approval expired or closed.`);return{kind:`approved`}}if(this.state.kind!==`pending`)throw Error(`Voice approval already started.`);if(!navigator.userActivation.isActive||document.visibilityState!==`visible`||!document.hasFocus())throw Error(`Open the Wallet review and select Confirm to start voice capture.`);this.state={kind:`preparing`},this.prompt.status.textContent=`Loading VoiceID…`;try{let t=await l.resolveSelectedWalletAuthority(this.walletId);if(t.kind!==`resolved`||t.authMethod.kind!==`voiceid`||t.selection.lockState!==`unlocked`)throw Error(`Unlock this wallet with VoiceID before voice approval.`);let r=await s(this.walletId,t.authMethod.walletAuthMethodId);await a(this.prompt.progress),this.prompt.progress.replaceChildren();let i=this.beginAttempt(e);for(;;){let{binding:t,deadline:a,remaining:o}=i,s=await y(r,t,o,e,this.prompt.capture,this.phrase);if(this.assertListening(t),e.aborted||performance.now()>=a||Date.now()>=t.expiresAtMs)throw Error(`Voice approval expired or closed.`);switch(s.kind){case`approved`:return this.state={kind:`approved`,binding:t,deadline:a,signal:e},s;case`retry`:this.phrase=n(this.confirmation,this.phrase),this.prompt.setPhrase(this.phrase),this.prompt.record.textContent=`Try again`,this.prompt.ready(s.message),await this.waitForRetry(e),i=this.beginAttempt(e);break;default:return x(s)}}}catch(e){throw this.state={kind:`closed`},e}}beginAttempt(e){let t=Math.min(25e3,this.reviewExpiresAtMs-Date.now());if(t<250||e.aborted)throw Error(`This voice review has expired or closed.`);let n=Object.freeze({attemptId:crypto.randomUUID(),operationHash:this.operationHash,expiresAtMs:Date.now()+t}),r=performance.now()+t;return this.prompt.record.hidden=!0,this.prompt.status.textContent=`Opening microphone…`,this.state={kind:`listening`,binding:n,deadline:r,signal:e},{binding:n,deadline:r,remaining:t}}waitForRetry(e){return new Promise((t,n)=>{let r=this.prompt.record,i=i=>{clearTimeout(s),r.removeEventListener(`click`,a),e.removeEventListener(`abort`,o),i?t():n(Error(`This voice review has expired or closed.`))},a=i.bind(null,!0),o=i.bind(null,!1),s=setTimeout(o,Math.max(0,this.reviewExpiresAtMs-Date.now()));r.addEventListener(`click`,a),e.addEventListener(`abort`,o),e.aborted&&o(),r.focus()})}consume(){let e=this.state;if(this.state={kind:`consumed`},e.kind!==`approved`||e.signal.aborted||performance.now()>=e.deadline||Date.now()>=e.binding.expiresAtMs)throw Error(`A current voice approval is required before signing.`)}assertListening(e){if(this.state.kind!==`listening`||!v(e,this.state.binding))throw Error(`This voice attempt has closed.`)}};export{C as voiceIdPolicyForRequest};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{a as e,c as t,d as n,g as r,h as i,l as a,m as o,p as s,s as c,t as l,u}from"./prompt-DEkI70F2.js";import{IndexedDBManager as d,SEAMS_WALLET_STORES as f,WorkerDeferred as ee,availabilityKey as te,base64UrlDecode as p,base64UrlEncode as m,buildDelegatedWalletAuthorityV1 as h,buildFullOwnerPermissionsV1 as ne,isFiniteNumber as re,isPlainObject as g,parseStoredAuthMethodAvailability as _,parseVoiceIdDevicePublicKeyB64u as v,parseWalletAuthMethodV2StorageRow as y,parseWalletAuthorityStorageRow as ie,parseWalletSelectionStorageRow as ae,requireRecord as b,sameDelegatedWalletAuthorityV1 as oe,sha256DigestB64u as x,toError as S}from"../voiceid-host.js";const C=`e04dd7a6a1e23fc7b89e47682ea9d0a3946cf7bc59d07922c3db3efc52ae4269`,w=`redimnet-b1:${C}`,T=new i({path:`/sdk/voiceid/redimnet-b1.onnx`,sha256:C,bytes:8859593,label:`Speaker verification`});async function E(e){let t=await T.prepare(),n=new(await(import(`./ort.wasm.bundle.min-7y-hlRN8.js`))).Tensor(`float32`,e,[1,e.length]);try{let e=await t.run({samples:n});try{let t=e.embedding;if(!t||!(t.data instanceof Float32Array))throw Error(`VoiceID model returned an invalid embedding.`);return D(Array.from(t.data))}finally{for(let t of Object.values(e))t.dispose()}}finally{n.dispose()}}function D(e){if(e.length!==192)throw Error(`VoiceID template dimensions are invalid.`);let t=0;for(let n of e){if(!Number.isFinite(n))throw Error(`VoiceID template is invalid.`);t+=n*n}if(t<1e-12)throw Error(`VoiceID template contains no signal.`);let n=Math.sqrt(t),r=[];for(let t of e)r.push(t/n);return r}function O(e,t){if(e.length!==192||t.length!==192)throw Error(`VoiceID templates are incompatible.`);let n=0;for(let r=0;r<e.length;r+=1)n+=e[r]*t[r];return n}const k=[T,o,a],A=[r,...k.map(se)];function se(e){return e.asset}function j(e,t){return e+t.size}var M=class{element=document.createElement(`section`);status=document.createElement(`p`);meter=document.createElement(`progress`);reveal;received=new Map;total=A.reduce(j,0);constructor(e){this.element.className=`seams-voiceid-model-download`,this.status.setAttribute(`role`,`status`),this.status.setAttribute(`aria-live`,`polite`),this.meter.setAttribute(`aria-label`,`VoiceID download`),this.meter.style.width=`100%`,this.element.append(this.status,this.meter),this.status.textContent=`Loading saved VoiceID…`,this.reveal=setTimeout(e.append.bind(e,this.element),300)}settle=()=>{clearTimeout(this.reveal)};update=(e,t)=>{switch(t.kind){case`checking_cache`:return;case`downloading`:this.received.set(e,t.received),this.element.dataset.state=`downloading`;break;case`ready`:this.received.set(e,e.size);break;default:}if(this.element.dataset.state!==`downloading`)return;let n=0;for(let e of this.received.values())n+=e;this.meter.max=this.total,this.meter.value=n,this.status.textContent=`Downloading VoiceID: ${(n/1e6).toFixed(1)} of ${(this.total/1e6).toFixed(1)} MB · ${Math.floor(n/this.total*100)}%`};preparing(){this.element.dataset.state=`preparing`,this.meter.removeAttribute(`value`),this.status.textContent=`Preparing VoiceID…`}ready(){this.element.dataset.state=`ready`,this.meter.hidden=!0,this.status.textContent=`VoiceID is ready.`}};async function N(e){let t=new M(e);try{for(let e of A)await e.download(t.update.bind(t,e));t.preparing();for(let e of k)await e.prepare();t.ready()}finally{t.settle()}}function P(e,t){return`voiceid-browser-device-v1:${encodeURIComponent(e)}:${encodeURIComponent(t)}`}function F(e,t,n){return new TextEncoder().encode(JSON.stringify([`seams-voiceid-browser-factor-v1`,location.origin,e,t,n]))}async function I(e,t){let n=await d.getAppState(P(e,t));if(n===void 0)return null;let r=b(n,`VoiceID device record`);if(r.kind!==`browser_voiceid_device_v1`||r.walletId!==e||r.walletAuthMethodId!==t)throw Error(`VoiceID device belongs to another wallet method.`);let{privateKey:i,wrappingKey:a,nonce:o,ciphertext:s}=r;if(!(i instanceof CryptoKey)||i.type!==`private`||i.extractable||i.algorithm.name!==`Ed25519`||!i.usages.includes(`sign`)||!(a instanceof CryptoKey)||a.type!==`secret`||a.extractable||a.algorithm.name!==`AES-GCM`||!a.usages.includes(`decrypt`)||!(o instanceof Uint8Array)||o.length!==12||!(s instanceof ArrayBuffer)||s.byteLength!==48)throw Error(`VoiceID device storage is invalid.`);let c=v(r.publicKeyB64u),l=await x(p(c));if(r.deviceKeyId!==l)throw Error(`VoiceID device key binding is invalid.`);return{kind:`browser_voiceid_device_v1`,walletId:e,walletAuthMethodId:t,privateKey:i,wrappingKey:a,nonce:new Uint8Array(o),ciphertext:s,publicKeyB64u:c,deviceKeyId:l}}async function ce(e,t){return await navigator.locks.request(P(e,t),le.bind(null,e,t))}async function le(e,t){let n=await I(e,t);if(n)return n;let r=await crypto.subtle.generateKey({name:`Ed25519`},!1,[`sign`,`verify`]),i=v(m(new Uint8Array(await crypto.subtle.exportKey(`raw`,r.publicKey)))),a=await x(p(i)),o=await crypto.subtle.generateKey({name:`AES-GCM`,length:256},!1,[`encrypt`,`decrypt`]),s=crypto.getRandomValues(new Uint8Array(32)),c=crypto.getRandomValues(new Uint8Array(12));try{let n=await crypto.subtle.encrypt({name:`AES-GCM`,iv:c,additionalData:F(e,t,a)},o,s),l={kind:`browser_voiceid_device_v1`,walletId:e,walletAuthMethodId:t,privateKey:r.privateKey,publicKeyB64u:i,deviceKeyId:a,wrappingKey:o,nonce:c,ciphertext:n};return await d.setAppState(P(e,t),l),l}finally{s.fill(0)}}async function L(e){let t=await crypto.subtle.decrypt({name:`AES-GCM`,iv:e.nonce,additionalData:F(e.walletId,e.walletAuthMethodId,e.deviceKeyId)},e.wrappingKey,e.ciphertext);return new Uint8Array(t)}const R=`voiceid-browser-enrollment-v1`,z=.2,B=[f.appState,f.walletSelections,f.walletAuthMethods,f.walletAuthorities];function V(e,t){return{walletId:e.walletId,walletAuthMethodId:e.walletAuthMethodId,deviceKeyId:e.deviceKeyId,authorityId:t}}async function H(e,t){let n=await d.getWalletAuthMethodV2(t),r=await I(e,t);if(!n||n.kind!==`voiceid`||n.status!==`active`||n.walletId!==e||!r||n.deviceKeyId!==r.deviceKeyId)throw Error(`VoiceID is unavailable for this wallet method.`);return V(r,n.walletAuthorityId)}function U(e){return`voiceid-browser-enrollment-v2:${[e.walletId,e.authorityId,e.walletAuthMethodId,e.deviceKeyId].map(encodeURIComponent).join(`:`)}`}function W(e){if(!Array.isArray(e)||!e.every(re))throw Error(`VoiceID template is invalid.`);let t=D(e);for(let n=0;n<e.length;n+=1)if(Math.abs(e[n]-t[n])>1e-6)throw Error(`VoiceID template is not normalized.`);return e.slice()}function G(e,t){if(e===void 0)return null;let n=b(e,`VoiceID enrollment`),r=b(n.scope,`VoiceID enrollment scope`);if(n.kind!==`browser_voiceid_enrollment_v2`||r.walletId!==t.walletId||r.authorityId!==t.authorityId||r.walletAuthMethodId!==t.walletAuthMethodId||r.deviceKeyId!==t.deviceKeyId||typeof n.generation!=`string`||!n.generation||typeof n.revision!=`number`||!Number.isSafeInteger(n.revision)||n.revision<0)throw Error(`VoiceID enrollment identity is invalid.`);if(n.model!==w)return null;let i=W(n.baseline),a={kind:`baseline`};try{let e=b(n.adaptation,`VoiceID improvements`);if(e.kind===`adapted`&&Array.isArray(e.samples)&&e.samples.length===3){let t=e.samples.map(W);q(i,t),a={kind:`adapted`,samples:t}}}catch{a={kind:`baseline`}}return{kind:`browser_voiceid_enrollment_v2`,scope:t,model:w,generation:n.generation,revision:n.revision,baseline:i,adaptation:a}}function K(e,t){let n=Array(192).fill(0);for(let e of t)for(let r=0;r<n.length;r+=1)n[r]+=e[r]/t.length;let r=D(n);for(let t=0;t<n.length;t+=1)n[t]=(1-z)*e[t]+z*r[t];return D(n)}function q(e,t){if(t.length!==3)throw Error(`Record three samples before saving voice improvements.`);for(let n of t)if(O(e,n)<.5)throw Error(`This sample differs from your original voice. Try again or re-record your enrollment.`);if(O(e,K(e,t))<.98)throw Error(`These samples differ too much from your original voice. Re-record your enrollment instead.`)}async function ue(e){let t=(await d.seamsWalletDB.getDB()).transaction(f.appState,`readwrite`);try{let n=await t.store.get(U(e)),r=G(n?.value,e);return r&&JSON.stringify(n?.value?.adaptation)!==JSON.stringify(r.adaptation)&&(r=Z(r,r.adaptation),await t.store.put({key:U(e),value:r})),await t.done,r}catch(e){try{t.abort()}catch{}throw await t.done.catch(X),e}}async function de(){let e=await d.getAppState(R);if(e===void 0)return null;let t=b(e,`Previous VoiceID enrollment`);return t.kind!==`browser_voiceid_enrollment_v1`||t.model!==w?null:W(t.template)}async function J(e,t,n){let r=ae(await e.objectStore(f.walletSelections).get(t.walletId)),i=ie(await e.objectStore(f.walletAuthorities).get(t.authorityId)),a=await e.objectStore(f.walletAuthMethods).get(t.walletAuthMethodId),o=y(a);if(a!==void 0&&!o)throw Error(`VoiceID method storage is invalid.`);if(!i||i.record.state!==`active`||i.record.walletId!==t.walletId)throw Error(`VoiceID authority changed. Start again.`);if(o&&(o.record.kind!==`voiceid`||o.record.status!==`active`||o.record.walletId!==t.walletId||o.record.walletAuthorityId!==t.authorityId||o.record.deviceKeyId!==t.deviceKeyId)||(n===null||n.target===`enrolled`)&&!o)throw Error(`VoiceID method is unavailable.`);if((await e.objectStore(f.appState).get(P(t.walletId,t.walletAuthMethodId)))?.value?.deviceKeyId!==t.deviceKeyId)throw Error(`VoiceID device changed. Start again.`);let s=_((await e.objectStore(f.appState).get(te(t.walletId,t.walletAuthMethodId)))?.value);if(!s.enabled)throw Error(`VoiceID is turned off.`);if(n){if(!oe(h({permissions:i.record.permissions}),h({permissions:ne()})))throw Error(`Full owner authority is required to change VoiceID enrollment.`);let a=y(await e.objectStore(f.walletAuthMethods).get(n.sessionMethodId));if(n.expiresAtMs<=Date.now()||s.revision!==n.availabilityRevision||!r||r.record.lockState!==`unlocked`||r.record.lockGeneration!==n.lockGeneration||r.record.walletAuthMethodId!==n.sessionMethodId||!a||a.record.status!==`active`||n.target===`activating`&&a.record.kind===`voiceid`||a.record.walletId!==t.walletId||a.record.walletAuthorityId!==t.authorityId)throw Error(`The owner session changed or expired. Start again.`);if(n.target===`enrolled`){let r=y(await e.objectStore(f.walletAuthMethods).get(n.confirmationMethodId));if(!r||r.record.kind!==`passkey`||r.record.status!==`active`||r.record.walletId!==t.walletId||r.record.walletAuthorityId!==t.authorityId)throw Error(`The confirming passkey changed or was removed. Start again.`)}}}async function Y(e,t){let n=(await d.seamsWalletDB.getDB()).transaction(B,`readwrite`);try{await J(n,e,t),await n.done}catch(e){throw $(n),await n.done.catch(X),e}}function X(){}async function fe(e,t){let n=e.scope,r=e.kind===`activation`?e.owner:null;if(r&&r.target!==`activating`)throw Error(`VoiceID activation requires its owner scope.`);let i=(await d.seamsWalletDB.getDB()).transaction(B,`readwrite`);try{await J(i,n,r);let e=i.objectStore(f.appState),a=await e.get(U(n)),o=G(a?.value,n);if(!o)throw Error(`Unlock with a passkey, then open Manage voice to import or record your enrollment.`);t.throwIfAborted();let s=o.adaptation,c=s.kind===`adapted`?K(o.baseline,s.samples):[...o.baseline];return JSON.stringify(a?.value?.adaptation)!==JSON.stringify(s)&&await e.put({key:U(n),value:Z(o,s)}),await i.done,t.throwIfAborted(),{template:c,baseline:[...o.baseline],kind:s.kind}}catch(e){try{i.abort()}catch{}throw await i.done.catch(X),e}}function Z(e,t){return{kind:`browser_voiceid_enrollment_v2`,scope:e.scope,model:e.model,generation:e.generation,revision:e.revision+1,baseline:e.baseline,adaptation:t}}async function Q(e,t,n,r,i){let a;if(r.kind===`activate`){if(t.target!==`activating`||n.kind!==`missing`)throw Error(`VoiceID activation cannot replace an enrollment.`)}else{if(t.target!==`enrolled`)throw Error(`VoiceID changes require a confirmation guard.`);await r.confirmation.consume(e,t,n,r.kind,i)}if(r.kind===`import`&&(t.target!==`enrolled`||n.kind!==`missing`))throw Error(`Import requires an existing method without a scoped enrollment.`);if(r.kind===`replace`||r.kind===`activate`||r.kind===`import`)a={kind:`browser_voiceid_enrollment_v2`,scope:e,model:w,generation:crypto.randomUUID(),revision:0,baseline:W(r.baseline),adaptation:{kind:`baseline`}};else{if(n.kind!==`present`)throw Error(`Record or import your original voice first.`);r.kind===`improve`?(q(n.enrollment.baseline,r.samples),a=Z(n.enrollment,{kind:`adapted`,samples:r.samples})):a=Z(n.enrollment,{kind:`baseline`})}if(new TextEncoder().encode(JSON.stringify(a)).length>32768)throw Error(`VoiceID enrollment exceeds its storage limit.`);let o=await d.seamsWalletDB.getDB();i.throwIfAborted();let s=o.transaction(B,`readwrite`),c=$.bind(null,s);i.addEventListener(`abort`,c,{once:!0});try{await J(s,e,t);let o=s.objectStore(f.appState),c=G((await o.get(U(e)))?.value,e);if(n.kind===`missing`?c!==null:!c||c.generation!==n.enrollment.generation||c.revision!==n.enrollment.revision)throw Error(`VoiceID enrollment changed in another operation. Start again.`);i.throwIfAborted(),await o.put({key:U(e),value:a}),r.kind===`import`&&await pe(s),await s.done}catch(e){throw c(),await s.done.catch(X),e}finally{i.removeEventListener(`abort`,c)}}function $(e){try{e.abort()}catch{}}async function pe(e){let t=e.objectStore(f.appState),n=await e.objectStore(f.walletAuthMethods).getAll();for(let e of n){if(!g(e)||e.kind!==`voiceid`)continue;let n=y(e);if(!n)return;let r=n.record;if(r.kind!==`voiceid`||r.status!==`active`)continue;let i={walletId:r.walletId,authorityId:r.walletAuthorityId,walletAuthMethodId:r.walletAuthMethodId,deviceKeyId:r.deviceKeyId};try{if(!G((await t.get(U(i)))?.value,i))return}catch{return}}await t.delete(R)}async function me(e,t){let n=document.querySelector(`.seams-voiceid-activation-surface`);if(!(n instanceof HTMLElement))throw Error(`Open VoiceID activation in the Wallet.`);await N(n),t.throwIfAborted();let r=new he(n,e,t);try{await r.result}finally{r.dispose()}}var he=class{completion=new ee;result=this.completion.promise;status;prompt;phrase=s(null);record;cancel;samples=[];phase=`ready`;get closed(){return this.phase===`closed`}controller=new AbortController;guardTimer;checkingGuard=!1;constructor(e,t,n){this.plan=t,this.signal=n,this.prompt=new l(document,`Enroll your voice in this browser`,this.phrase),this.prompt.title.textContent=`Read aloud · 0 of 3 accepted`,this.status=this.prompt.status,this.record=this.prompt.record,this.cancel=this.prompt.cancel,this.record.textContent=`Record sample 1`,this.record.addEventListener(`click`,this.capture),this.cancel.setAttribute(`aria-label`,`Cancel enrollment`),this.cancel.addEventListener(`click`,this.cancelled),this.prompt.ready(t.kind===`improve`?`Read naturally. Your original enrollment stays unchanged.`:`Read the full phrase at your normal pace.`),e.replaceChildren(this.prompt.element),n.addEventListener(`abort`,this.cancelled,{once:!0}),this.guardTimer=setInterval(this.checkGuard,250),n.aborted?this.cancelled():this.record.focus()}capture=()=>{this.collect()};async collect(){if(this.phase!==`ready`)return;let r=this.samples.length+1;try{c(),this.phase=`recording`,this.record.disabled=!0,this.status.textContent=`Opening microphone…`;let r=await e(this.controller.signal,this.prompt.capture);if(this.signal.aborted||this.closed){r.kind===`captured`&&r.samples.fill(0);return}if(r.kind===`insufficient_audio`){this.status.textContent=t(r.quality);return}if(this.plan.kind===`improve`&&r.quality.noise!==`normal`){r.samples.fill(0),this.status.textContent=`Use a quiet place to improve your voice enrollment.`;return}let i;try{if(await Y(this.plan.scope,this.plan.guard),this.status.textContent=`Checking your voice…`,(await n(r.samples,this.phrase)).kind===`mismatch`)throw new u;i=await E(r.samples)}finally{r.samples.fill(0)}if(this.signal.aborted||this.closed){i.fill(0);return}if(this.plan.kind===`improve`&&O(this.plan.snapshot.enrollment.baseline,i)<.5)throw i.fill(0),Error(`Your voice did not match the original enrollment. To replace it, return to Manage voice and choose Record voice again.`);let a=1;for(let e of this.samples)a=Math.min(a,O(e,i));this.status.dataset.voiceCapture=JSON.stringify({attemptId:r.quality.attemptId,capturedAtMs:r.quality.capturedAtMs,sample:this.samples.length+1,durationMs:r.quality.durationMs,recordingMs:r.quality.recordingMs,endedBy:r.quality.endedBy,voicedMs:r.quality.voicedMs,microphone:r.quality.microphone,processing:r.quality.processing,backgroundRms:r.quality.backgroundRms,speechToBackgroundDb:r.quality.speechToBackgroundDb,noise:r.quality.noise,minimumSimilarity:a});for(let e of this.samples)if(O(e,i)<.5)throw i.fill(0),Error(`This sample differs from the earlier samples. Use the same microphone and try again.`);if(this.samples.push(i),this.samples.length<3){this.prompt.accepted(),this.status.textContent=`Sample ${this.samples.length} accepted. Record the next sample.`,this.record.textContent=`Record sample ${this.samples.length+1}`;return}let o=Array(192).fill(0);for(let e of this.samples)for(let t=0;t<o.length;t+=1)o[t]+=e[t]/3;this.phase=`committing`,this.prompt.title.textContent=`3 of 3 accepted`,this.status.textContent=`Saving voice enrollment…`;let s=this.plan;switch(s.kind){case`activate`:await Q(s.scope,s.guard,s.snapshot,{kind:`activate`,baseline:D(o)},this.controller.signal);break;case`replace`:await Q(s.scope,s.guard,s.snapshot,{kind:`replace`,baseline:D(o),confirmation:s.confirmation},this.controller.signal);break;case`improve`:await Q(s.scope,s.guard,s.snapshot,{kind:`improve`,samples:this.samples,confirmation:s.confirmation},this.controller.signal);break}if(this.signal.aborted||this.closed)return;this.phase=`closed`,this.prompt.accepted(),this.status.textContent=`Voice enrollment saved.`,this.completion.resolve()}catch(e){if(!this.closed&&this.phase===`committing`){this.phase=`closed`,this.controller.abort(),this.completion.reject(S(e));return}!this.closed&&this.samples.length===3&&this.samples.pop()?.fill(0),this.closed||(this.status.textContent=e instanceof Error?e.message:`VoiceID enrollment failed.`)}finally{this.closed||(this.samples.length<r&&(this.record.textContent=`Retry sample ${r}`,this.status.textContent=`Sample ${r} was not accepted. ${this.status.textContent??``}`),this.phrase=s(this.phrase),this.prompt.setPhrase(this.phrase),this.prompt.title.textContent=`Read aloud · ${this.samples.length} of 3 accepted`,this.prompt.ready(this.status.textContent??``),this.phase=`ready`,this.record.disabled=!1)}}cancelled=()=>{this.closed||(this.phase=`closed`,this.controller.abort(),this.completion.reject(Error(`VoiceID enrollment was cancelled.`)))};checkGuard=()=>{this.closed||this.checkingGuard||(this.checkingGuard=!0,this.validateGuard())};async validateGuard(){try{await Y(this.plan.scope,this.plan.guard)}catch(e){this.closed||(this.phase=`closed`,this.controller.abort(),this.completion.reject(S(e)))}finally{this.checkingGuard=!1}}dispose(){this.phase=`closed`,this.controller.abort(),clearInterval(this.guardTimer),this.signal.removeEventListener(`abort`,this.cancelled),this.prompt.dispose(),this.record.removeEventListener(`click`,this.capture),this.cancel.removeEventListener(`click`,this.cancelled);for(let e of this.samples)e.fill(0)}};async function ge(t,r,i,a){delete document.body.dataset.voiceVerification;let o=await e(r,i);if(o.kind===`insufficient_audio`)return r.throwIfAborted(),o;try{r.throwIfAborted();let e=await n(o.samples,a),i=e.kind===`matched`,s=o.samples.length/16,c=await E(o.samples),l,u,d;try{r.throwIfAborted();let e=await fe(t,r);try{l=O(c,e.template),u=O(c,e.baseline),d=e.kind}finally{e.template.fill(0),e.baseline.fill(0)}}finally{c.fill(0)}return document.body.dataset.voiceVerification=JSON.stringify({attemptId:o.quality.attemptId,capturedAtMs:o.quality.capturedAtMs,durationMs:o.quality.durationMs,recordingMs:o.quality.recordingMs,endedBy:o.quality.endedBy,voicedMs:o.quality.voicedMs,microphone:o.quality.microphone,processing:o.quality.processing,backgroundRms:o.quality.backgroundRms,speechToBackgroundDb:o.quality.speechToBackgroundDb,noise:o.quality.noise,similarity:l,baselineSimilarity:u,referenceKind:d,modelInputMs:s,threshold:.5,phraseMatched:i,phraseRecognition:e.recognition,expectedPhrase:a}),l<.5?{kind:`mismatch`,quality:o.quality}:e.kind===`matched`?{kind:`matched`}:{kind:`phrase_mismatch`}}finally{o.samples.fill(0)}}export{de as a,V as c,I as d,N as f,Q as i,ce as l,ge as n,ue as o,Y as r,H as s,me as t,L as u};
|