@seams/voiceid 0.43.0 → 0.45.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.
@@ -0,0 +1 @@
1
+ import{s as e}from"./prompt-DsRKGfyb.js";import{c as t,r as n,s as r}from"./deviceBridge-C8Jp8coS.js";async function i(e,n,i,o){let s=await e.prepareUnlock(n),c=s.scope;if(!await r(c))throw Error(`Unlock with a passkey, then open Manage voice to import or record your enrollment.`);await t(i,n,o),n.throwIfAborted();let l=await s.challenge(n),u={challenge:l,initiation:e.mountCapture?`hosted_menu`:`sdk_request`};return n.throwIfAborted(),{kind:`recording`,expiresAtMs:l.expiresAtMs,action:a.bind(null,o,u)}}async function a(t,r,i,a,s){switch(r.initiation){case`sdk_request`:e();break;case`hosted_menu`:break;default:return o(r.initiation)}return r.challenge.complete(n.bind(null,t,{onCapture:a,phrase:s}),i)}function o(e){throw Error(`Unexpected VoiceID initiation: ${String(e)}`)}export{i as prepareUnlockSurface};
@@ -1,11 +1,9 @@
1
1
  import { BrowserInferenceModel } from './inference';
2
2
  import { SPOKEN_PHRASES } from './phraseVocabulary';
3
- import type { VoiceConfirmation } from '@seams/wallet/voiceid-host';
4
3
  import type { VoiceIdTranscriptionModel } from '@seams/wallet/voiceid-host';
5
4
  export type SpokenPhrase = (typeof SPOKEN_PHRASES)[number];
6
5
  export declare function getPhraseModel(model: VoiceIdTranscriptionModel): BrowserInferenceModel;
7
6
  export declare function newSpokenPhrase(previous: SpokenPhrase | null): SpokenPhrase;
8
- export declare function newConfirmationPhrase(confirmation: VoiceConfirmation, previous: SpokenPhrase | null): SpokenPhrase;
9
7
  type PhraseRecognition = {
10
8
  kind: 'recognized';
11
9
  phrase: SpokenPhrase;
@@ -1,7 +1,7 @@
1
1
  import type { VoiceIdTranscriptionModel } from '@seams/wallet/voiceid-host';
2
2
  import type { VoiceCaptureObserver } from '../browser/microphone';
3
3
  import { type SpokenPhrase } from '../browser/phrase';
4
- import type { VoiceIdActivationCapture, VoiceIdUnlockCapture } from '@seams/wallet/voiceid-host';
4
+ import type { VoiceIdActivationCapture, VoiceIdOperationCapture, VoiceIdUnlockCapture } from '@seams/wallet/voiceid-host';
5
5
  type VoiceCapturePresentation = Readonly<{
6
6
  onCapture: VoiceCaptureObserver;
7
7
  phrase: SpokenPhrase;
@@ -10,5 +10,6 @@ export declare function captureVoiceIdActivation(model: VoiceIdTranscriptionMode
10
10
  parent: HTMLElement;
11
11
  }>, input: Parameters<VoiceIdActivationCapture>[0]): ReturnType<VoiceIdActivationCapture>;
12
12
  export declare function captureVoiceIdUnlock(model: VoiceIdTranscriptionModel, presentation: VoiceCapturePresentation, input: Parameters<VoiceIdUnlockCapture>[0]): ReturnType<VoiceIdUnlockCapture>;
13
+ export declare function captureVoiceIdOperation(model: VoiceIdTranscriptionModel, presentation: VoiceCapturePresentation, input: Parameters<VoiceIdOperationCapture>[0]): ReturnType<VoiceIdOperationCapture>;
13
14
  export {};
14
15
  //# sourceMappingURL=deviceBridge.d.ts.map
@@ -18,12 +18,13 @@ export type VoiceCaptureElements = Readonly<{
18
18
  progress: HTMLElement;
19
19
  }>;
20
20
  type CapturePreparation<T> = (signal: AbortSignal, elements: VoiceCaptureElements) => Promise<PreparedVoiceCapture<T>>;
21
+ type VoiceCapturePurpose = 'activation' | 'unlock' | 'export';
21
22
  export declare class VoiceIdCaptureCancelledError extends Error {
22
23
  constructor();
23
24
  }
24
25
  export declare function showVoiceIdCaptureSurface<T>(input: {
25
26
  readonly prepare: CapturePreparation<T>;
26
- readonly purpose: 'activation' | 'unlock';
27
+ readonly purpose: VoiceCapturePurpose;
27
28
  readonly measurement: UiConfirmSurfaceMeasurementBinding;
28
29
  readonly mountCapture?: AuthMethodCaptureMount;
29
30
  }): Promise<T>;
@@ -1,5 +1,5 @@
1
1
  import type { WalletAuthMethodHostPlugin } from '@seams/wallet/voiceid-host';
2
2
  import type { VoiceIdTranscriptionModel } from '@seams/wallet/voiceid-host';
3
- export declare const hostApiVersion = 6;
3
+ export declare const hostApiVersion = 8;
4
4
  export declare function createVoiceIdHostPlugin(model: VoiceIdTranscriptionModel): WalletAuthMethodHostPlugin;
5
5
  //# sourceMappingURL=host.d.ts.map
@@ -0,0 +1,4 @@
1
+ import type { VoiceIdTranscriptionModel, VoiceIdOperationProof, WalletAuthMethodHostPlugin } from '@seams/wallet/voiceid-host';
2
+ import type { PreparedVoiceCapture } from '../capture/surface';
3
+ export declare function prepareOperationSurface(input: Parameters<WalletAuthMethodHostPlugin['confirmOperation']>[0], signal: AbortSignal, parent: HTMLElement, model: VoiceIdTranscriptionModel): Promise<PreparedVoiceCapture<VoiceIdOperationProof>>;
4
+ //# sourceMappingURL=operation.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seams/voiceid",
3
- "version": "0.43.0",
3
+ "version": "0.45.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.43.0",
53
+ "@seams/wallet": "0.45.0",
54
54
  "react": ">=19.0.0",
55
55
  "react-dom": ">=19.0.0"
56
56
  },
@@ -1 +0,0 @@
1
- import"./prompt-yshUSnwN.js";import{i as e,n as t,r as n}from"./verification-Ddj-M_EN.js";import{r,t as i}from"./deviceBridge-BKjLg0gA.js";import{WorkerDeferred as a,prepareVoiceOwner as o,toError as s}from"../voiceid-host.js";const c=`http://www.w3.org/2000/svg`;async function l(e,t,n,r,i){let a=new u(e,t,n,r,i);try{await a.result}finally{a.dispose()}}var u=class{completion=new a;result=this.completion.promise;document;element;enrollment;enrollmentValue;enrollmentSummary;startOver;status;actions;busy=!1;closed=!1;constructor(e,t,n,r,i){this.parent=e,this.confirmChange=t,this.scope=n,this.signal=r,this.model=i,this.document=e.ownerDocument,this.element=this.document.createElement(`div`),this.enrollment=this.document.createElement(`div`),this.enrollmentValue=this.document.createElement(`span`),this.enrollmentSummary=this.document.createElement(`span`),this.startOver=this.document.createElement(`p`),this.status=this.document.createElement(`p`),this.element.className=`seams-voiceid-prompt seams-voiceid-management`;let a=this.document.createElement(`div`);a.className=`seams-voiceid-management-header`;let o=this.document.createElement(`button`);o.type=`button`,o.className=`seams-voiceid-management-back`,o.setAttribute(`aria-label`,`Back`),o.append(d(this.document,`M15 6l-6 6 6 6`,20)),o.addEventListener(`click`,this.cancel);let s=this.document.createElement(`h2`);s.textContent=`Manage voice`,a.append(o,s);let c=this.document.createElement(`p`);c.className=`seams-voiceid-management-intro`,c.textContent=`Voice unlock on this device. Each change asks for your passkey.`,this.element.append(a,c,this.enrollmentRow()),this.actions={reset:this.resetAction(),improve:this.action(`improve`,`Improve recognition`,!0),import:this.action(`import`,`Use saved enrollment`,!1),replace:this.startOverAction()},this.status.setAttribute(`role`,`status`),this.element.append(this.status),this.enrollmentValue.textContent=`Checking…`,this.actions.import.button.hidden=!0,this.render(null,!1,!1),this.parent.replaceChildren(this.element),this.signal.addEventListener(`abort`,this.cancel,{once:!0}),this.refresh(),o.focus()}enrollmentRow(){this.enrollment.className=`seams-voiceid-management-enrollment`;let e=this.document.createElement(`span`);return e.textContent=`Enrollment`,this.enrollmentValue.className=`seams-voiceid-management-value`,this.enrollmentSummary.className=`seams-voiceid-management-summary`,this.enrollmentSummary.append(this.enrollmentValue),this.enrollment.append(e,this.enrollmentSummary),this.enrollment}resetAction(){let e=this.document.createElement(`button`);e.type=`button`,e.className=`seams-voiceid-management-reset`,e.textContent=`Reset`,e.setAttribute(`aria-label`,`Reset improvements`),e.addEventListener(`click`,this.begin.bind(this,`reset`));let t=this.document.createElement(`span`);return t.className=`seams-voiceid-management-enrollment-detail`,this.enrollmentSummary.append(t),this.enrollment.append(e),{button:e,title:e,detail:t,frame:this.enrollment}}startOverAction(){return this.startOver.className=`seams-voiceid-management-section`,this.startOver.textContent=`Start over`,this.element.append(this.startOver),this.action(`replace`,`Record voice again`,!1)}action(e,t,n){let r=this.document.createElement(`button`);r.type=`button`,r.className=`seams-voiceid-management-action`;let i=this.document.createElement(`span`),a=this.document.createElement(`span`);a.className=`seams-voiceid-management-action-heading`;let o=this.document.createElement(`span`);o.id=`seams-voiceid-management-${e}-title`,o.textContent=t,a.append(o);let s=[];if(n){let t=this.document.createElement(`span`);t.id=`seams-voiceid-management-${e}-chip`,t.className=`seams-voiceid-management-chip`,t.textContent=`Experimental`,a.append(t),s.push(t.id)}let c=this.document.createElement(`span`);return c.id=`seams-voiceid-management-${e}-detail`,c.className=`seams-voiceid-management-action-detail`,s.push(c.id),i.append(a,c),r.append(i,d(this.document,`M9 6l6 6-6 6`,16)),r.setAttribute(`aria-labelledby`,o.id),r.setAttribute(`aria-describedby`,s.join(` `)),r.disabled=!0,r.addEventListener(`click`,this.begin.bind(this,e)),this.element.append(r),{button:r,title:o,detail:c,frame:r}}async refresh(){try{let t=await e(this.scope),r=t?null:await n();if(this.closed)return;this.render(t,r!==null,!0)}catch(e){this.completion.reject(s(e))}}render(e,t,n){let r=e?.adaptation??null,{improve:i,reset:a,import:o,replace:s}=this.actions;n&&(this.enrollment.dataset.enrollment=r?.kind??`missing`,this.enrollmentValue.textContent=r===null?`Not on this device`:r.kind===`adapted`?`Improved`:`Original`,i.button.hidden=e===null,o.button.hidden=!t),a.button.hidden=r?.kind!==`adapted`,a.detail.textContent=r?.kind===`adapted`?`Saved on this device`:``,i.title.textContent=r?.kind===`adapted`?`Improve again`:`Improve recognition`,i.detail.textContent=r?.kind===`adapted`?`Replace the added samples. Your original enrollment stays.`:`Add three samples. They stay until you replace or reset them.`,o.detail.textContent=`Import the voice enrollment saved in this browser.`,s.detail.textContent=r?.kind===`adapted`?`Replace your enrollment and remove improvements.`:`Replace your enrollment with three new samples.`,this.startOver.hidden=i.button.hidden&&o.button.hidden;for(let e of Object.values(this.actions))delete e.frame.dataset.state,e.button.disabled=!n;this.status.textContent=``,delete this.status.dataset.placement}show(e,t,n){let r=this.actions[e];if(this.status.textContent=n,r.button.hidden){this.status.dataset.placement=`dialog`;return}r.frame.dataset.state=t,r.detail.textContent=n}begin(e){if(!(this.busy||this.closed)){this.busy=!0;for(let e of Object.values(this.actions))e.button.disabled=!0;this.show(e,`pending`,`Confirm with your passkey to continue.`),this.perform(e)}}async perform(n){try{let i=await o(this.scope,`enrolled`),a=await e(this.scope),s=await f(n,i,a?{kind:`present`,enrollment:a}:{kind:`missing`},this.signal),c=await this.confirmChange(s);switch(this.signal.throwIfAborted(),s.kind){case`improve`:await r(this.parent,{kind:`improve`,scope:this.scope,guard:s.guard,snapshot:s.snapshot,confirmation:c},this.signal,this.model);break;case`replace`:await r(this.parent,{kind:`replace`,scope:this.scope,guard:s.guard,snapshot:s.snapshot,confirmation:c},this.signal,this.model);break;case`reset`:await t({kind:`reset`,scope:this.scope,guard:s.guard,snapshot:s.snapshot,confirmation:c,signal:this.signal});break;case`import`:await t({kind:`import`,scope:this.scope,guard:s.guard,snapshot:s.snapshot,baseline:s.baseline,confirmation:c,signal:this.signal});break;default:return p(s)}this.closed||this.completion.resolve()}catch(e){if(this.closed)return;this.parent.replaceChildren(this.element),await this.refresh(),this.closed||this.show(n,`error`,e instanceof Error?e.message:`VoiceID could not be updated.`)}finally{this.busy=!1}}cancel=()=>{this.closed||(this.closed=!0,this.parent.dispatchEvent(new Event(`cancel`,{bubbles:!0})),this.completion.reject(Error(`VoiceID management was cancelled.`)))};dispose(){this.closed=!0,this.signal.removeEventListener(`abort`,this.cancel),this.element.remove()}};function d(e,t,n){let r=e.createElementNS(c,`svg`);r.setAttribute(`viewBox`,`0 0 24 24`),r.setAttribute(`width`,String(n)),r.setAttribute(`height`,String(n)),r.setAttribute(`aria-hidden`,`true`);let i=e.createElementNS(c,`path`);return i.setAttribute(`d`,t),i.setAttribute(`fill`,`none`),i.setAttribute(`stroke`,`currentColor`),i.setAttribute(`stroke-width`,`1.75`),i.setAttribute(`stroke-linecap`,`round`),i.setAttribute(`stroke-linejoin`,`round`),r.append(i),r}async function f(e,t,r,i){switch(e){case`replace`:return{kind:e,guard:t,snapshot:r,signal:i};case`import`:{if(r.kind!==`missing`)throw Error(`The previous enrollment is unavailable.`);let a=await n();if(!a)throw Error(`The previous enrollment is unavailable.`);return{kind:e,guard:t,snapshot:r,signal:i,baseline:a}}case`improve`:case`reset`:if(r.kind!==`present`)throw Error(`Record or import your original voice first.`);return{kind:e,guard:t,snapshot:r,signal:i};default:return p(e)}}function p(e){throw Error(`Unsupported VoiceID enrollment management action.`)}async function m(e,t,n,r){t.throwIfAborted();let i=await e.prepareActivation();return t.throwIfAborted(),i.kind===`enroll_existing`?{kind:`management`,action:h.bind(null,r,i,n)}:{kind:`recording`,action:g.bind(null,r,i,n),expiresAtMs:null}}async function h(e,t,n,r){return await l(n,t.confirmChange,t.scope,r,e),r.throwIfAborted(),{kind:`activated`,walletId:t.scope.walletId,walletAuthMethodId:t.scope.walletAuthMethodId}}function g(e,t,n,r,a,o){return t.activate(i.bind(null,e,{parent:n,onCapture:a,phrase:o}),r)}export{m as prepareActivationSurface};
@@ -1 +0,0 @@
1
- import{c as e,d as t,l as n,m as r,t as i,y as a}from"./prompt-yshUSnwN.js";import{a as o,t as s}from"./verification-Ddj-M_EN.js";import{TransactionReviewError as c,resolveSelectedVoiceEnrollmentScope as l}from"../voiceid-host.js";function u(e,t){return e.attemptId===t.attemptId&&e.operationHash===t.operationHash&&e.expiresAtMs===t.expiresAtMs}async function d(r,i,o,c,l,u,d){let p=Math.min(i.expiresAtMs,Date.now()+o);if(c.aborted||p<=Date.now())throw Error(`VoiceID review expired or was cancelled.`);let m;try{m=await s({kind:`active`,scope:r},c,l,u,d)}catch(e){if(!(e instanceof n))throw e;if(c.aborted||p<=Date.now())throw Error(`VoiceID review expired or was cancelled.`);return{kind:`retry`,message:e.message}}if(c.aborted||p<=Date.now())throw Error(`VoiceID review expired or was cancelled.`);switch(m.kind){case`matched`:return{kind:`approved`};case`insufficient_audio`:return{kind:`retry`,message:e(m.quality)};case`mismatch`:return{kind:`retry`,message:new a(m.quality).message};case`phrase_mismatch`:return{kind:`retry`,message:new t().message};default:return f(m)}}function f(e){throw Error(`Unexpected voice result: ${String(e)}`)}function p(e){throw Error(`Unexpected voice outcome: ${String(e)}`)}var m=class e{phrase;prompt;surface;get messages(){return{confirm:`Confirm with VoiceID`,approved:`Voice approved this operation.`}}state={kind:`pending`};constructor(e,t,n,a,o){this.reviewExpiresAtMs=e,this.operationHash=t,this.walletId=n,this.confirmation=a,this.model=o,this.phrase=r(a,null),this.prompt=new i(document,`Approve with VoiceID`,this.phrase,`segments`),this.prompt.record.hidden=!0,this.prompt.cancel.hidden=!0,this.surface=this.prompt.element}static forReview(t,n){return new e(t.expiresAtMs,t.operationHash,t.walletId,t.confirmation,n)}async prepare(e){if(this.state.kind!==`pending`)throw Error(`VoiceID preparation already started.`);this.state={kind:`preparing`},this.prompt.title.textContent=`VoiceID`,this.prompt.status.textContent=`Loading VoiceID…`;try{if(await o(this.prompt.progress,e,this.model),e.throwIfAborted(),this.state.kind!==`preparing`)throw Error(`VoiceID preparation was interrupted.`);if(Date.now()>=this.reviewExpiresAtMs)throw new c(`review_expired`,`This review has expired.`);this.prompt.progress.replaceChildren(),this.prompt.title.textContent=`Read aloud`,this.state={kind:`prepared`}}catch(e){throw this.state={kind:`closed`},e}}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!==`prepared`)throw Error(`Prepare VoiceID before voice approval.`);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:`authorizing`};try{let t=await l(this.walletId),n=this.beginAttempt(e);for(;;){let{binding:i,deadline:a,remaining:o}=n,s=await d(t,i,o,e,this.prompt.capture,this.phrase,this.model);if(this.assertListening(i),e.aborted||performance.now()>=a||Date.now()>=i.expiresAtMs)throw Error(`Voice approval expired or closed.`);switch(s.kind){case`approved`:return this.state={kind:`approved`,binding:i,deadline:a,signal:e},s;case`retry`:this.phrase=r(this.confirmation,this.phrase),this.prompt.setPhrase(this.phrase),this.prompt.record.textContent=`Try again`,this.prompt.ready(s.message),await this.waitForRetry(e),n=this.beginAttempt(e);break;default:return p(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`||!u(e,this.state.binding))throw Error(`This voice attempt has closed.`)}};export{m as VoiceIdApproval};
@@ -1 +0,0 @@
1
- import{a as e,c as t,d as n,f as r,h as i,i as a,o,s,t as c,y as l}from"./prompt-yshUSnwN.js";import{a as u,c as d,i as f,l as p,n as m,s as h,t as g}from"./verification-Ddj-M_EN.js";import{WorkerDeferred as _,assertVoiceOwner as v,base64UrlEncode as y,createBrowserVoiceDevice as b,openBrowserVoiceFactor as x,parseVoiceIdDeviceAssertion as S,prepareVoiceOwner as C,readBrowserVoiceDevice as w,resolveActivatingVoiceEnrollmentScope as T,resolveVoiceEnrollmentScope as E,toError as D,voiceIdWalletSessionChallengeBytes as O}from"../voiceid-host.js";async function k(e,t,n,r){await u(e,n,r),n.throwIfAborted();let i=new A(e,t,n,r);try{await i.result}finally{i.dispose()}}var A=class{completion=new _;result=this.completion.promise;status;prompt;phrase=i(null);record;cancel;samples=[];phase=`ready`;get closed(){return this.phase===`closed`}controller=new AbortController;guardTimer;checkingGuard=!1;constructor(e,t,n,r){this.plan=t,this.signal=n,this.model=r,this.prompt=new c(e.ownerDocument,`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 a=this.samples.length+1;try{s(),this.phase=`recording`,this.record.disabled=!0,this.status.textContent=`Opening microphone…`;let i=await e(this.controller.signal,this.prompt.capture);if(this.signal.aborted||this.closed){i.kind===`captured`&&i.samples.fill(0);return}if(i.kind===`insufficient_audio`){this.status.textContent=t(i.quality);return}if(this.plan.kind===`improve`&&i.quality.noise!==`normal`){i.samples.fill(0),this.status.textContent=`Use a quiet place to improve your voice enrollment.`;return}let a;try{if(await v(this.plan.scope,this.plan.guard),this.status.textContent=`Checking your voice…`,(await r(i.samples,this.phrase,this.model)).kind===`mismatch`)throw new n;a=await d(i.samples)}finally{i.samples.fill(0)}if(this.signal.aborted||this.closed){a.fill(0);return}if(this.plan.kind===`improve`&&p(this.plan.snapshot.enrollment.baseline,a)<.45)throw a.fill(0),Error(`Your voice did not match the original enrollment. To replace it, return to Manage voice and choose Record voice again.`);for(let e of this.samples)if(p(e,a)<.45)throw a.fill(0),Error(`This sample differs from the earlier samples. Use the same microphone and try again.`);if(this.samples.push(a),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 c=this.plan;switch(c.kind){case`activate`:await m({kind:`activate`,scope:c.scope,guard:c.guard,snapshot:c.snapshot,baseline:h(o),signal:this.controller.signal});break;case`replace`:await m({kind:`replace`,scope:c.scope,guard:c.guard,snapshot:c.snapshot,baseline:h(o),confirmation:c.confirmation,signal:this.controller.signal});break;case`improve`:await m({kind:`improve`,scope:c.scope,guard:c.guard,snapshot:c.snapshot,samples:this.samples,confirmation:c.confirmation,signal:this.controller.signal});break;default:return j(c)}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(D(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<a&&(this.record.textContent=`Retry sample ${a}`,this.status.textContent=`Sample ${a} was not accepted. ${this.status.textContent??``}`),this.phrase=i(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 v(this.plan.scope,this.plan.guard)}catch(e){this.closed||(this.phase=`closed`,this.controller.abort(),this.completion.reject(D(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)}};function j(e){throw Error(`Unsupported VoiceID enrollment capture.`)}async function M(e,t,n){s();let r=await b(n.walletId,n.walletAuthMethodId),i=await T(r);if(await f(i))await P({kind:`activation`,scope:i,owner:await C(i,`activating`)},n.signal,t.onCapture,t.phrase,e);else{let r=await C(i,`activating`);await k(t.parent,{kind:`activate`,scope:i,guard:r,snapshot:{kind:`missing`}},n.signal,e)}n.signal.throwIfAborted();let a=await x(r);if(n.signal.aborted)throw a.fill(0),Error(`VoiceID activation was cancelled.`);return{kind:`activated`,publicKeyB64u:r.publicKeyB64u,deviceKeyId:r.deviceKeyId,factorSecret32:a}}async function N(e,t,n){o();let{challenge:r}=n;if(r.requestOrigin!==location.origin||r.expiresAtMs<=Date.now())throw Error(`VoiceID challenge is expired or belongs to another Wallet origin.`);let i=await w(r.walletId,r.walletAuthMethodId);if(!i||i.deviceKeyId!==r.deviceKeyId)throw Error(`VoiceID device key is unavailable in this browser.`);if(await P({kind:`active`,scope:await E(r.walletId,r.walletAuthMethodId)},n.signal,t.onCapture,t.phrase,e),n.signal.throwIfAborted(),r.expiresAtMs<=Date.now())throw Error(`VoiceID challenge expired. Start unlock again.`);let a=await crypto.subtle.sign(`Ed25519`,i.privateKey,O(r)),s=await x(i);if(n.signal.aborted||r.expiresAtMs<=Date.now())throw s.fill(0),Error(`VoiceID unlock expired or was cancelled.`);return{kind:`unlocked`,assertion:S({kind:`voiceid_device_assertion_v1`,challenge:r,signatureB64u:y(new Uint8Array(a))}),factorSecret32:s}}async function P(e,t,r,i,o){let s=await g(e,t,r,i,o);switch(s.kind){case`matched`:return;case`insufficient_audio`:throw new a(s.quality);case`mismatch`:throw new l(s.quality);case`phrase_mismatch`:throw new n;default:return F(s)}}function F(e){throw Error(`Unexpected voice result: ${String(e)}`)}export{N as n,k as r,M as t};