@seams/voiceid 0.43.0 → 0.44.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 +12 -38
- package/dist/public/sdk/voiceid/addVoiceId-B2XmPljL.js +1 -0
- package/dist/public/sdk/voiceid/deviceBridge-vf25pKzp.js +1 -0
- package/dist/public/sdk/voiceid/host.js +4 -3
- package/dist/public/sdk/voiceid/prompt-DsRKGfyb.js +614 -0
- package/dist/public/sdk/voiceid/unlock-DdzR0kE9.js +1 -0
- package/dist/types/browser/phrase.d.ts +0 -2
- package/dist/types/host.d.ts +1 -1
- package/package.json +2 -2
- package/dist/public/sdk/voiceid/addVoiceId-DAtqPP1y.js +0 -1
- package/dist/public/sdk/voiceid/approval-CQfImado.js +0 -1
- package/dist/public/sdk/voiceid/deviceBridge-BKjLg0gA.js +0 -1
- package/dist/public/sdk/voiceid/prompt-yshUSnwN.js +0 -614
- package/dist/public/sdk/voiceid/unlock-CjaOTqR2.js +0 -1
- package/dist/public/sdk/voiceid/verification-Ddj-M_EN.js +0 -1
- package/dist/types/capture/approval.d.ts +0 -27
- package/dist/types/capture/bridge.d.ts +0 -13
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.
|
|
13
|
+
pnpm add @seams/wallet@0.44.0 @seams/voiceid@0.44.0
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
```ts
|
|
@@ -26,8 +26,8 @@ const config = defineSeamsConfig({
|
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
Choose `transcriptionModel: 'aurora'` for the smaller speech model. Citrinet is
|
|
29
|
-
the default when this option is omitted. The choice applies to enrollment
|
|
30
|
-
|
|
29
|
+
the default when this option is omitted. The choice applies to enrollment
|
|
30
|
+
and unlock. Each action keeps its chosen model throughout
|
|
31
31
|
preparation and recording.
|
|
32
32
|
|
|
33
33
|
| Speech model | Uncompressed | Gzip |
|
|
@@ -47,35 +47,13 @@ loading in the trusted Wallet iframe. Passkey and Email OTP flows remain indepen
|
|
|
47
47
|
The optional `VoiceBeam` and `VoiceAssistantCard` visuals are exported from
|
|
48
48
|
`@seams/voiceid/react`. Import `@seams/voiceid/styles` when using the card.
|
|
49
49
|
|
|
50
|
-
##
|
|
50
|
+
## Signing after a VoiceID unlock
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
voiceId: {
|
|
58
|
-
confirmationCommand: 'Sell 100 NEAR for USDC at $5.5',
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
The Wallet selects a short action-only challenge, such as “Approve this sale at
|
|
64
|
-
once” or “Let the sale go through.” It omits amounts, assets, and prices from the
|
|
65
|
-
spoken phrase. The full transaction review remains authoritative.
|
|
66
|
-
|
|
67
|
-
The leading English verb selects the phrase family: `sell`, `buy`, `swap`, or
|
|
68
|
-
`send`/`transfer`. Other wording uses the default random challenges. Commands
|
|
69
|
-
must be nonempty strings of at most 256 characters without control characters.
|
|
70
|
-
The application cannot supply the final phrase or change verification thresholds.
|
|
71
|
-
Retries choose a different phrase within the selected family.
|
|
72
|
-
|
|
73
|
-
This option changes wording only. It does not enable VoiceID for unsupported
|
|
74
|
-
transactions or interpret a trading command. VoiceID currently supports reviewed
|
|
75
|
-
native transfers on NEAR testnet and Arc testnet. Approval remains bound to the
|
|
76
|
-
complete signing request and review deadline, including the supplied command.
|
|
77
|
-
Enrollment and unlock retain the default phrase bank. This adds no model or API
|
|
78
|
-
request and keeps audio in the Wallet iframe.
|
|
52
|
+
VoiceID works like a passkey. A VoiceID unlock opens a normal signing session.
|
|
53
|
+
Each operation on any chain shows the ordinary **Confirm**, with no further voice
|
|
54
|
+
check, within the session's uses and expiry. When the session needs fresh
|
|
55
|
+
authentication, the owner unlocks with VoiceID again. Key export needs a passkey
|
|
56
|
+
or Email OTP.
|
|
79
57
|
|
|
80
58
|
## Install on the Wallet host
|
|
81
59
|
|
|
@@ -122,12 +100,8 @@ same card. Retry keeps verified assets and voice enrollment. Close the card to
|
|
|
122
100
|
choose a passkey instead. A missing or incompatible host package requires an
|
|
123
101
|
operator fix; other module failures include connection and reload instructions.
|
|
124
102
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
in force while loading or retrying, so an expired transaction needs a fresh review.
|
|
128
|
-
|
|
129
|
-
VoiceID remembers the microphone used for usable audio in this browser. Enrollment,
|
|
130
|
-
unlock, and signing request that input even when the system default changes.
|
|
103
|
+
VoiceID remembers the microphone used for usable audio in this browser. Enrollment
|
|
104
|
+
and unlock request that input even when the system default changes.
|
|
131
105
|
If it is unavailable, VoiceID can use the current default. Reconnecting the saved
|
|
132
106
|
microphone restores it. Interrupted recordings require a new attempt. Voice
|
|
133
107
|
enrollment and verification thresholds stay unchanged.
|
|
@@ -140,5 +114,5 @@ Splitting it removes that payload from Wallet-only installations. It does not
|
|
|
140
114
|
reduce the bytes needed for the first VoiceID operation.
|
|
141
115
|
|
|
142
116
|
Use the [public VoiceID guide](https://docs.wallet.seams.sh/concepts/auth-methods/voiceid)
|
|
143
|
-
for enrollment, unlock,
|
|
117
|
+
for enrollment, unlock, signing, and storage behavior.
|
|
144
118
|
See [implementation notes](src/README.md) for code boundaries and enrollment storage.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./prompt-DsRKGfyb.js";import{a as e,i as t,o as n,r,t as i}from"./deviceBridge-vf25pKzp.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 n(this.scope),r=t?null:await e();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(e){try{let i=await o(this.scope,`enrolled`),a=await n(this.scope),s=await f(e,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(t){if(this.closed)return;this.parent.replaceChildren(this.element),await this.refresh(),this.closed||this.show(e,`error`,t instanceof Error?t.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(t,n,r,i){switch(t){case`replace`:return{kind:t,guard:n,snapshot:r,signal:i};case`import`:{if(r.kind!==`missing`)throw Error(`The previous enrollment is unavailable.`);let a=await e();if(!a)throw Error(`The previous enrollment is unavailable.`);return{kind:t,guard:n,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:t,guard:n,snapshot:r,signal:i};default:return p(t)}}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};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{_ as e,a as t,c as n,d as r,f as i,g as a,h as o,i as s,m as c,o as l,p as u,s as d,t as f,u as p,v as m}from"./prompt-DsRKGfyb.js";import{WorkerDeferred as h,assertVoiceOwner as g,base64UrlEncode as _,createBrowserVoiceDevice as v,isFiniteNumber as ee,openBrowserVoiceFactor as y,parseVoiceIdDeviceAssertion as b,prepareVoiceOwner as x,readBrowserVoiceDevice as S,requireRecord as C,resolveActivatingVoiceEnrollmentScope as w,resolveVoiceEnrollmentScope as T,toError as E,voiceEnrollmentStorage as D,voiceIdWalletSessionChallengeBytes as O}from"../voiceid-host.js";const k=`e04dd7a6a1e23fc7b89e47682ea9d0a3946cf7bc59d07922c3db3efc52ae4269`,A=`redimnet-b1:${k}`,j=new o({path:`/sdk/voiceid/redimnet-b1.onnx`,sha256:k,bytes:8859593,label:`Speaker verification`});async function M(e){let t=await j.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 N(Array.from(t.data))}finally{for(let t of Object.values(e))t.dispose()}}finally{n.dispose()}}function N(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 P(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}function F(e){return e.asset}function I(e,t){return e+t.size}var L=class{element=document.createElement(`section`);status=document.createElement(`p`);meter=document.createElement(`progress`);retryButton=document.createElement(`button`);retry=null;cancelled=new h;reveal;received=new Map;models;assets;total;constructor(t,n,r){this.parent=t,this.signal=n,this.models=[j,u(r),p],this.assets=[e,...this.models.map(F)],this.total=this.assets.reduce(I,0),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.retryButton.type=`button`,this.retryButton.className=`seams-voiceid-primary`,this.retryButton.textContent=`Retry loading`,this.retryButton.hidden=!0,this.retryButton.addEventListener(`click`,this.retryLoading),this.element.append(this.status,this.meter,this.retryButton),this.status.textContent=`Loading saved VoiceID…`,this.reveal=setTimeout(t.append.bind(t,this.element),300),n.addEventListener(`abort`,this.abort,{once:!0})}settle=()=>{clearTimeout(this.reveal),this.signal.removeEventListener(`abort`,this.abort),this.retryButton.removeEventListener(`click`,this.retryLoading)};abort=()=>{let e=new DOMException(`VoiceID preparation was cancelled.`,`AbortError`);this.cancelled.reject(e),this.retry?.reject(e)};retryLoading=()=>{this.retry?.resolve(),this.retry=null};async prepare(){this.signal.throwIfAborted(),await Promise.race([this.prepareUntilReady(),this.cancelled.promise])}async prepareUntilReady(){for(;;){this.signal.throwIfAborted(),this.retryButton.hidden=!0,this.meter.hidden=!1;try{for(let e of this.assets)await e.download(this.update.bind(this,e)),this.signal.throwIfAborted();this.preparing();for(let e of this.models)await e.prepare(),this.signal.throwIfAborted();this.ready();return}catch(e){if(this.signal.throwIfAborted(),!(e instanceof a))throw e;clearTimeout(this.reveal),this.parent.append(this.element),this.element.dataset.state=`failed`,this.meter.hidden=!0,this.status.textContent=e.message,this.retry=new h,this.retryButton.hidden=!1,this.retryButton.focus(),await this.retry.promise}}}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 R(e,t,n){let r=new L(e,t,n);try{await r.prepare()}finally{r.settle()}}const z=.2,B=D({model:A,parseEnrollment:H});function V(e){if(!Array.isArray(e)||!e.every(ee))throw Error(`VoiceID template is invalid.`);let t=N(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 H(e,t){if(e===void 0)return null;let n=C(e,`VoiceID enrollment`),r=C(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!==A)return null;let i=V(n.baseline),a={kind:`baseline`};try{let e=C(n.adaptation,`VoiceID improvements`);if(e.kind===`adapted`&&Array.isArray(e.samples)&&e.samples.length===3){let t=e.samples.map(V);W(i,t),a={kind:`adapted`,samples:t}}}catch{a={kind:`baseline`}}return{kind:`browser_voiceid_enrollment_v2`,scope:t,model:A,generation:n.generation,revision:n.revision,baseline:i,adaptation:a}}function U(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=N(n);for(let t=0;t<n.length;t+=1)n[t]=(1-z)*e[t]+z*r[t];return N(n)}function W(e,t){if(t.length!==3)throw Error(`Record three samples before saving voice improvements.`);for(let n of t)if(P(e,n)<.45)throw Error(`This sample differs from your original voice. Try again or re-record your enrollment.`);if(P(e,U(e,t))<.98)throw Error(`These samples differ too much from your original voice. Re-record your enrollment instead.`)}function G(e){return B.read(e)}async function K(e,t){let n=await B.readTemplate(e,t),r=n.adaptation;return{template:r.kind===`adapted`?U(n.baseline,r.samples):[...n.baseline],baseline:[...n.baseline],kind:r.kind}}async function q(){let e=await B.readImportable();if(e===void 0)return null;let t=C(e,`Previous VoiceID enrollment`);return t.kind!==`browser_voiceid_enrollment_v1`||t.model!==A?null:V(t.template)}async function J(e){let{scope:t,signal:n}=e;switch(e.kind){case`activate`:return B.commit({kind:`activate`,scope:t,signal:n,guard:e.guard,snapshot:e.snapshot,baseline:V(e.baseline)});case`import`:return B.commit({kind:`import`,scope:t,signal:n,guard:e.guard,snapshot:e.snapshot,confirmation:e.confirmation,baseline:V(e.baseline)});case`replace`:return B.commit({kind:`replace`,scope:t,signal:n,guard:e.guard,snapshot:e.snapshot,confirmation:e.confirmation,baseline:V(e.baseline)});case`improve`:return W(e.snapshot.enrollment.baseline,e.samples),B.commit({kind:`improve`,scope:t,signal:n,guard:e.guard,snapshot:e.snapshot,confirmation:e.confirmation,samples:e.samples});case`reset`:return B.commit({kind:`reset`,scope:t,signal:n,guard:e.guard,snapshot:e.snapshot,confirmation:e.confirmation});default:return Y(e)}}function Y(e){throw Error(`Unsupported VoiceID enrollment change.`)}async function X(e,t,n,r){await R(e,n,r),n.throwIfAborted();let i=new Z(e,t,n,r);try{await i.result}finally{i.dispose()}}var Z=class{completion=new h;result=this.completion.promise;status;prompt;phrase=c(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 f(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 e=this.samples.length+1;try{d(),this.phase=`recording`,this.record.disabled=!0,this.status.textContent=`Opening microphone…`;let e=await t(this.controller.signal,this.prompt.capture);if(this.signal.aborted||this.closed){e.kind===`captured`&&e.samples.fill(0);return}if(e.kind===`insufficient_audio`){this.status.textContent=n(e.quality);return}if(this.plan.kind===`improve`&&e.quality.noise!==`normal`){e.samples.fill(0),this.status.textContent=`Use a quiet place to improve your voice enrollment.`;return}let a;try{if(await g(this.plan.scope,this.plan.guard),this.status.textContent=`Checking your voice…`,(await i(e.samples,this.phrase,this.model)).kind===`mismatch`)throw new r;a=await M(e.samples)}finally{e.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 s=this.plan;switch(s.kind){case`activate`:await J({kind:`activate`,scope:s.scope,guard:s.guard,snapshot:s.snapshot,baseline:N(o),signal:this.controller.signal});break;case`replace`:await J({kind:`replace`,scope:s.scope,guard:s.guard,snapshot:s.snapshot,baseline:N(o),confirmation:s.confirmation,signal:this.controller.signal});break;case`improve`:await J({kind:`improve`,scope:s.scope,guard:s.guard,snapshot:s.snapshot,samples:this.samples,confirmation:s.confirmation,signal:this.controller.signal});break;default:return Q(s)}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(E(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<e&&(this.record.textContent=`Retry sample ${e}`,this.status.textContent=`Sample ${e} was not accepted. ${this.status.textContent??``}`),this.phrase=c(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 g(this.plan.scope,this.plan.guard)}catch(e){this.closed||(this.phase=`closed`,this.controller.abort(),this.completion.reject(E(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 Q(e){throw Error(`Unsupported VoiceID enrollment capture.`)}async function te(e,n,r,a,o){let s=await t(n,r);if(s.kind===`insufficient_audio`)return n.throwIfAborted(),s;try{n.throwIfAborted();let t=await i(s.samples,a,o),r=await M(s.samples),c;try{n.throwIfAborted();let t=await K(e,n);try{c=P(r,t.template)}finally{t.template.fill(0),t.baseline.fill(0)}}finally{r.fill(0)}return c<.45?{kind:`mismatch`,quality:s.quality}:t.kind===`matched`?{kind:`matched`}:{kind:`phrase_mismatch`}}finally{s.samples.fill(0)}}async function ne(e,t,n){d();let r=await v(n.walletId,n.walletAuthMethodId),i=await w(r);if(await G(i))await $({kind:`activation`,scope:i,owner:await x(i,`activating`)},n.signal,t.onCapture,t.phrase,e);else{let r=await x(i,`activating`);await X(t.parent,{kind:`activate`,scope:i,guard:r,snapshot:{kind:`missing`}},n.signal,e)}n.signal.throwIfAborted();let a=await y(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 re(e,t,n){l();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 S(r.walletId,r.walletAuthMethodId);if(!i||i.deviceKeyId!==r.deviceKeyId)throw Error(`VoiceID device key is unavailable in this browser.`);if(await $({kind:`active`,scope:await T(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)),o=await y(i);if(n.signal.aborted||r.expiresAtMs<=Date.now())throw o.fill(0),Error(`VoiceID unlock expired or was cancelled.`);return{kind:`unlocked`,assertion:b({kind:`voiceid_device_assertion_v1`,challenge:r,signatureB64u:_(new Uint8Array(a))}),factorSecret32:o}}async function $(e,t,n,i,a){let o=await te(e,t,n,i,a);switch(o.kind){case`matched`:return;case`insufficient_audio`:throw new s(o.quality);case`mismatch`:throw new m(o.quality);case`phrase_mismatch`:throw new r;default:return ie(o)}}function ie(e){throw Error(`Unexpected voice result: ${String(e)}`)}export{q as a,J as i,re as n,G as o,X as r,R as s,ne as t};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{d as e,
|
|
1
|
+
import{d as e,i as t,l as n,m as r,n as i,r as a,t as o,v as s}from"./prompt-DsRKGfyb.js";import{SeamsWordmark as c,VOICE_ID_HOST_API_VERSION as l,WorkerDeferred as u,authMenuSuccessCopy as d,backIcon as f,createCspStylesheetManager as p,createWalletIframeSurfaceMeasurementReporter as m,getDefaultCspNonce as h,toError as g}from"../voiceid-host.js";const _=new WeakMap;var v=class{footer;constructor(e,t){this.prompt=t;let n=e.ownerDocument,r=_.get(n);r||(r=p({doc:n,baseCss:`
|
|
2
2
|
.seams-voiceid-activation-surface.seams-voiceid-unlock {
|
|
3
3
|
box-sizing: border-box;
|
|
4
4
|
overflow: hidden;
|
|
@@ -52,7 +52,8 @@ 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
|
|
|
52
52
|
padding: 0.75rem 1.5rem 1.5rem;
|
|
53
53
|
grid-template-rows: auto minmax(6rem, auto) 5rem minmax(3.375rem, auto) auto auto;
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
/* Only a hidden record button empties the slot; the microphone choice is hidden by default. */
|
|
56
|
+
.seams-voiceid-unlock .seams-voiceid-record-slot:has(> .seams-voiceid-primary[hidden]) {
|
|
56
57
|
display: none;
|
|
57
58
|
}
|
|
58
59
|
.seams-voiceid-unlock-footer {
|
|
@@ -77,4 +78,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
78
|
color: var(--seams-colors-textPrimary, #eff2f3);
|
|
78
79
|
}
|
|
79
80
|
}
|
|
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=
|
|
81
|
+
`,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=r(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 t||a instanceof e||a instanceof n||a instanceof s){if(i.expiresAtMs!==null&&Date.now()>=i.expiresAtMs){this.expire();return}let e=this.phrase;this.phrase=r(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=7;if(l!==7)throw Error(`VoiceID requires a matching Wallet host API.`);function T(e){return{unlock:E.bind(null,e),activate:O.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-DdzR0kE9.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-B2XmPljL.js`);return n.throwIfAborted(),i.prepareActivationSurface(t,n,r.parent,e)}export{T as createVoiceIdHostPlugin,w as hostApiVersion};
|