@seams/voiceid 0.42.0 → 0.43.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 CHANGED
@@ -10,7 +10,7 @@ and the trusted host API. Existing voice enrollments keep their storage identity
10
10
  Install matching Wallet and VoiceID versions, then import the descriptor:
11
11
 
12
12
  ```sh
13
- pnpm add @seams/wallet@0.42.0 @seams/voiceid@0.42.0
13
+ pnpm add @seams/wallet@0.43.0 @seams/voiceid@0.43.0
14
14
  ```
15
15
 
16
16
  ```ts
@@ -139,6 +139,6 @@ The cold payload before transfer compression is roughly 27 MB with Aurora or
139
139
  Splitting it removes that payload from Wallet-only installations. It does not
140
140
  reduce the bytes needed for the first VoiceID operation.
141
141
 
142
- Use the [public VoiceID guide](https://wallet.seams.sh/docs/concepts/auth-methods/voiceid)
142
+ Use the [public VoiceID guide](https://docs.wallet.seams.sh/concepts/auth-methods/voiceid)
143
143
  for enrollment, unlock, transfer approval, and storage behavior.
144
144
  See [implementation notes](src/README.md) for code boundaries and enrollment storage.
@@ -0,0 +1 @@
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};
@@ -77,4 +77,4 @@ import{d as e,h as t,i as n,l as r,n as i,r as a,t as o,y as s}from"./prompt-ysh
77
77
  color: var(--seams-colors-textPrimary, #eff2f3);
78
78
  }
79
79
  }
80
- `,nonce:h}),_.set(n,r)),r.ensureBase(),e.classList.add(`seams-voiceid-unlock`);let o=n.createElement(`div`);o.className=`seams-voiceid-unlock-header`,t.cancel.className=`seams-voiceid-unlock-back`,t.cancel.setAttribute(`aria-label`,`Back`),t.cancel.replaceChildren(),i(f(),t.cancel),o.append(t.cancel),e.prepend(o),this.footer=n.createElement(`div`),this.footer.className=`seams-voiceid-unlock-footer`,i([a(`span`,null,`Secured by`),` `,a(c,{})],this.footer),e.append(this.footer)}succeed(){let e=this.prompt.cancel;return e.animate([{opacity:1},{opacity:0}],{duration:150,easing:`ease-in`,fill:`forwards`}),{top:e.getBoundingClientRect().top,bottom:this.footer.getBoundingClientRect().top}}dispose(){i(null,this.prompt.cancel),i(null,this.footer)}},y=class{result;completion=new u;controller=new AbortController;element;activateButton;cancelButton;status;prompt;unlockFrame;autoStartUnlock;unmountCapture;measurement;phrase=t(null);state={kind:`preparing`};settlement=`closed`;expiryTimer=null;constructor(e,t,n,r){let i=t===`activation`?`Activate VoiceID`:`Unlock with VoiceID`,a=document.getElementById(`root`)??document.body;if(!a)throw Error(`Wallet activation surface has no mount root.`);this.element=document.createElement(`section`),this.element.className=`seams-wallet-ui seams-auth-method-activation-surface seams-voiceid-activation-surface`,this.element.setAttribute(`role`,`dialog`),this.element.setAttribute(`aria-modal`,`true`),this.element.setAttribute(`aria-label`,i),this.prompt=new o(document,i,this.phrase),this.element.append(this.prompt.element),this.unlockFrame=t===`unlock`?new v(this.element,this.prompt):null,this.autoStartUnlock=this.unlockFrame!==null&&r!==void 0,this.status=this.prompt.status,this.status.textContent=`Loading VoiceID…`,this.prompt.title.textContent=`VoiceID`,this.element.dataset.preparation=`loading`,this.activateButton=this.prompt.record,this.activateButton.textContent=i,this.activateButton.disabled=!0,this.activateButton.hidden=this.autoStartUnlock,this.activateButton.addEventListener(`click`,this.begin),this.cancelButton=this.prompt.cancel,this.cancelButton.addEventListener(`click`,this.cancel),this.element.addEventListener(`cancel`,this.cancel),this.unmountCapture=r?r(this.element):null,r||a.appendChild(this.element),this.measurement=!r&&n.kind===`wallet_iframe`?m({kind:`request_surface`,element:this.element,requestId:n.requestId,postMeasurement:n.postMeasurement}):null,this.cancelButton.focus(),this.result=this.completion.promise,this.initialize(e)}dispose(){this.measurement?.disconnect(),this.expiryTimer!==null&&clearTimeout(this.expiryTimer),this.element.removeEventListener(`cancel`,this.cancel),this.activateButton.removeEventListener(`click`,this.begin),this.cancelButton.removeEventListener(`click`,this.cancel),this.unlockFrame?.dispose(),this.prompt.dispose(),this.unmountCapture?this.unmountCapture(this.settlement):this.element.remove()}async initialize(e){try{await new Promise(S),this.controller.signal.throwIfAborted();let t=await e(this.controller.signal,{parent:this.element,progress:this.prompt.progress});if(this.state.kind===`closed`)return;if(t.kind===`management`){this.element.setAttribute(`aria-label`,`Manage voice`),this.element.dataset.preparation=`ready`,this.state={kind:`capturing`};let e=await t.action(this.controller.signal);this.controller.signal.throwIfAborted(),this.state={kind:`closed`},this.completion.resolve(e);return}let n=t.expiresAtMs===null?null:t.expiresAtMs-Date.now();if(n!==null&&n<=0)throw Error(`VoiceID challenge expired. Start unlock again.`);this.state={kind:`ready`,capture:t},this.expiryTimer=n===null?null:setTimeout(this.expire,n);for(let e of this.element.querySelectorAll(`.seams-voiceid-model-download`))e.remove();this.element.dataset.preparation=`ready`,this.prompt.title.textContent=`Read aloud`,this.status.textContent=`Read naturally, then pause.`,this.activateButton.disabled=!1,this.autoStartUnlock?this.begin():this.activateButton.focus()}catch(e){if(this.state.kind===`closed`)return;this.state={kind:`closed`},this.completion.reject(g(e))}}begin=()=>{if(this.state.kind!==`ready`)return;let e=this.state.capture;this.state={kind:`capturing`},this.perform(e)};async perform(i){this.activateButton.disabled=!0,this.status.textContent=`Opening microphone…`;try{let e=await i.action(this.controller.signal,this.prompt.capture,this.phrase);if(this.state.kind===`closed`)return;this.unlockFrame&&await this.showUnlocked(this.unlockFrame),this.state={kind:`closed`},this.completion.resolve(e)}catch(a){if(this.state.kind===`closed`)return;if(a instanceof n||a instanceof e||a instanceof r||a instanceof s){if(i.expiresAtMs!==null&&Date.now()>=i.expiresAtMs){this.expire();return}let e=this.phrase;this.phrase=t(e),this.prompt.setPhrase(this.phrase),this.state={kind:`ready`,capture:i},this.activateButton.hidden=!1,this.activateButton.textContent=`Try again`,this.prompt.ready(a.message),this.activateButton.focus();return}this.state={kind:`closed`},this.completion.reject(g(a))}}async showUnlocked(e){this.state={kind:`succeeding`},this.settlement=`succeeded`,this.expiryTimer!==null&&clearTimeout(this.expiryTimer),this.expiryTimer=null,this.cancelButton.disabled=!0;let t=performance.now(),n=d(`authenticated`);await this.prompt.succeed(n.headline,n.message,e.succeed());let r=Math.max(0,1300-(performance.now()-t));await this.element.animate([{opacity:1},{opacity:1,offset:r/(r+240),easing:`ease-in`},{opacity:0}],{duration:r+240,fill:`forwards`}).finished}cancel=()=>{this.state.kind===`closed`||this.state.kind===`succeeding`||(this.state={kind:`closed`},this.controller.abort(),this.completion.reject(new b))};expire=()=>{this.state.kind===`closed`||this.state.kind===`succeeding`||(this.state={kind:`closed`},this.controller.abort(),this.completion.reject(Error(`VoiceID challenge expired. Start unlock again.`)))}},b=class extends Error{constructor(){super(`VoiceID capture was cancelled.`),this.name=`VoiceIdCaptureCancelledError`}};async function x(e){let t=new y(e.prepare,e.purpose,e.measurement,e.mountCapture);try{return await t.result}finally{t.dispose()}}function S(e){requestAnimationFrame(C.bind(null,e))}function C(e){setTimeout(e,0)}const w=6;if(l!==6)throw Error(`VoiceID requires a matching Wallet host API.`);function T(e){return{unlock:E.bind(null,e),activate:O.bind(null,e),review:A.bind(null,e)}}function E(e,t){return x({purpose:`unlock`,measurement:t.surfaceMeasurementBinding,mountCapture:t.mountCapture,prepare:D.bind(null,e,t)})}async function D(e,t,n,r){let i=await import(`./unlock-CjaOTqR2.js`);return n.throwIfAborted(),i.prepareUnlockSurface(t,n,r.progress,e)}async function O(e,t){try{return await x({purpose:`activation`,measurement:t.surfaceMeasurementBinding,prepare:k.bind(null,e,t)})}catch(e){if(e instanceof b)return{kind:`cancelled`};throw e}}async function k(e,t,n,r){let i=await import(`./addVoiceId-DAtqPP1y.js`);return n.throwIfAborted(),i.prepareActivationSurface(t,n,r.parent,e)}async function A(e,t){return(await import(`./approval-DHvEDAip.js`)).VoiceIdApproval.forReview(t,e)}export{T as createVoiceIdHostPlugin,w as hostApiVersion};
80
+ `,nonce:h}),_.set(n,r)),r.ensureBase(),e.classList.add(`seams-voiceid-unlock`);let o=n.createElement(`div`);o.className=`seams-voiceid-unlock-header`,t.cancel.className=`seams-voiceid-unlock-back`,t.cancel.setAttribute(`aria-label`,`Back`),t.cancel.replaceChildren(),i(f(),t.cancel),o.append(t.cancel),e.prepend(o),this.footer=n.createElement(`div`),this.footer.className=`seams-voiceid-unlock-footer`,i([a(`span`,null,`Secured by`),` `,a(c,{})],this.footer),e.append(this.footer)}succeed(){let e=this.prompt.cancel;return e.animate([{opacity:1},{opacity:0}],{duration:150,easing:`ease-in`,fill:`forwards`}),{top:e.getBoundingClientRect().top,bottom:this.footer.getBoundingClientRect().top}}dispose(){i(null,this.prompt.cancel),i(null,this.footer)}},y=class{result;completion=new u;controller=new AbortController;element;activateButton;cancelButton;status;prompt;unlockFrame;autoStartUnlock;unmountCapture;measurement;phrase=t(null);state={kind:`preparing`};settlement=`closed`;expiryTimer=null;constructor(e,t,n,r){let i=t===`activation`?`Activate VoiceID`:`Unlock with VoiceID`,a=document.getElementById(`root`)??document.body;if(!a)throw Error(`Wallet activation surface has no mount root.`);this.element=document.createElement(`section`),this.element.className=`seams-wallet-ui seams-auth-method-activation-surface seams-voiceid-activation-surface`,this.element.setAttribute(`role`,`dialog`),this.element.setAttribute(`aria-modal`,`true`),this.element.setAttribute(`aria-label`,i),this.prompt=new o(document,i,this.phrase),this.element.append(this.prompt.element),this.unlockFrame=t===`unlock`?new v(this.element,this.prompt):null,this.autoStartUnlock=this.unlockFrame!==null&&r!==void 0,this.status=this.prompt.status,this.status.textContent=`Loading VoiceID…`,this.prompt.title.textContent=`VoiceID`,this.element.dataset.preparation=`loading`,this.activateButton=this.prompt.record,this.activateButton.textContent=i,this.activateButton.disabled=!0,this.activateButton.hidden=this.autoStartUnlock,this.activateButton.addEventListener(`click`,this.begin),this.cancelButton=this.prompt.cancel,this.cancelButton.addEventListener(`click`,this.cancel),this.element.addEventListener(`cancel`,this.cancel),this.unmountCapture=r?r(this.element):null,r||a.appendChild(this.element),this.measurement=!r&&n.kind===`wallet_iframe`?m({kind:`request_surface`,element:this.element,requestId:n.requestId,postMeasurement:n.postMeasurement}):null,this.cancelButton.focus(),this.result=this.completion.promise,this.initialize(e)}dispose(){this.measurement?.disconnect(),this.expiryTimer!==null&&clearTimeout(this.expiryTimer),this.element.removeEventListener(`cancel`,this.cancel),this.activateButton.removeEventListener(`click`,this.begin),this.cancelButton.removeEventListener(`click`,this.cancel),this.unlockFrame?.dispose(),this.prompt.dispose(),this.unmountCapture?this.unmountCapture(this.settlement):this.element.remove()}async initialize(e){try{await new Promise(S),this.controller.signal.throwIfAborted();let t=await e(this.controller.signal,{parent:this.element,progress:this.prompt.progress});if(this.state.kind===`closed`)return;if(t.kind===`management`){this.element.setAttribute(`aria-label`,`Manage voice`),this.element.dataset.preparation=`ready`,this.state={kind:`capturing`};let e=await t.action(this.controller.signal);this.controller.signal.throwIfAborted(),this.state={kind:`closed`},this.completion.resolve(e);return}let n=t.expiresAtMs===null?null:t.expiresAtMs-Date.now();if(n!==null&&n<=0)throw Error(`VoiceID challenge expired. Start unlock again.`);this.state={kind:`ready`,capture:t},this.expiryTimer=n===null?null:setTimeout(this.expire,n);for(let e of this.element.querySelectorAll(`.seams-voiceid-model-download`))e.remove();this.element.dataset.preparation=`ready`,this.prompt.title.textContent=`Read aloud`,this.status.textContent=`Read naturally, then pause.`,this.activateButton.disabled=!1,this.autoStartUnlock?this.begin():this.activateButton.focus()}catch(e){if(this.state.kind===`closed`)return;this.state={kind:`closed`},this.completion.reject(g(e))}}begin=()=>{if(this.state.kind!==`ready`)return;let e=this.state.capture;this.state={kind:`capturing`},this.perform(e)};async perform(i){this.activateButton.disabled=!0,this.status.textContent=`Opening microphone…`;try{let e=await i.action(this.controller.signal,this.prompt.capture,this.phrase);if(this.state.kind===`closed`)return;this.unlockFrame&&await this.showUnlocked(this.unlockFrame),this.state={kind:`closed`},this.completion.resolve(e)}catch(a){if(this.state.kind===`closed`)return;if(a instanceof n||a instanceof e||a instanceof r||a instanceof s){if(i.expiresAtMs!==null&&Date.now()>=i.expiresAtMs){this.expire();return}let e=this.phrase;this.phrase=t(e),this.prompt.setPhrase(this.phrase),this.state={kind:`ready`,capture:i},this.activateButton.hidden=!1,this.activateButton.textContent=`Try again`,this.prompt.ready(a.message),this.activateButton.focus();return}this.state={kind:`closed`},this.completion.reject(g(a))}}async showUnlocked(e){this.state={kind:`succeeding`},this.settlement=`succeeded`,this.expiryTimer!==null&&clearTimeout(this.expiryTimer),this.expiryTimer=null,this.cancelButton.disabled=!0;let t=performance.now(),n=d(`authenticated`);await this.prompt.succeed(n.headline,n.message,e.succeed());let r=Math.max(0,1300-(performance.now()-t));await this.element.animate([{opacity:1},{opacity:1,offset:r/(r+240),easing:`ease-in`},{opacity:0}],{duration:r+240,fill:`forwards`}).finished}cancel=()=>{this.state.kind===`closed`||this.state.kind===`succeeding`||(this.state={kind:`closed`},this.controller.abort(),this.completion.reject(new b))};expire=()=>{this.state.kind===`closed`||this.state.kind===`succeeding`||(this.state={kind:`closed`},this.controller.abort(),this.completion.reject(Error(`VoiceID challenge expired. Start unlock again.`)))}},b=class extends Error{constructor(){super(`VoiceID capture was cancelled.`),this.name=`VoiceIdCaptureCancelledError`}};async function x(e){let t=new y(e.prepare,e.purpose,e.measurement,e.mountCapture);try{return await t.result}finally{t.dispose()}}function S(e){requestAnimationFrame(C.bind(null,e))}function C(e){setTimeout(e,0)}const w=6;if(l!==6)throw Error(`VoiceID requires a matching Wallet host API.`);function T(e){return{unlock:E.bind(null,e),activate:O.bind(null,e),review:A.bind(null,e)}}function E(e,t){return x({purpose:`unlock`,measurement:t.surfaceMeasurementBinding,mountCapture:t.mountCapture,prepare:D.bind(null,e,t)})}async function D(e,t,n,r){let i=await import(`./unlock-CjaOTqR2.js`);return n.throwIfAborted(),i.prepareUnlockSurface(t,n,r.progress,e)}async function O(e,t){try{return await x({purpose:`activation`,measurement:t.surfaceMeasurementBinding,prepare:k.bind(null,e,t)})}catch(e){if(e instanceof b)return{kind:`cancelled`};throw e}}async function k(e,t,n,r){let i=await import(`./addVoiceId-DAtqPP1y.js`);return n.throwIfAborted(),i.prepareActivationSurface(t,n,r.parent,e)}async function A(e,t){return(await import(`./approval-CQfImado.js`)).VoiceIdApproval.forReview(t,e)}export{T as createVoiceIdHostPlugin,w as hostApiVersion};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seams/voiceid",
3
- "version": "0.42.0",
3
+ "version": "0.43.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.42.0",
53
+ "@seams/wallet": "0.43.0",
54
54
  "react": ">=19.0.0",
55
55
  "react-dom": ">=19.0.0"
56
56
  },
@@ -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 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){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};