@glydi/passkey-core 0.1.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 ADDED
@@ -0,0 +1,152 @@
1
+ # @glydi/passkey-core
2
+
3
+ Framework-agnostic Passkeys/WebAuthn Web Component. Tiny, Shadow-DOM-isolated, themeable via CSS Parts.
4
+
5
+ ## Install
6
+
7
+ Glide is not yet published to npm. Install from a packed tarball or via `pnpm link`.
8
+
9
+ **Tarball (recommended):**
10
+
11
+ ```json
12
+ {
13
+ "dependencies": {
14
+ "@glydi/passkey-core": "file:../glide/dist-packs/glydi-passkey-core-0.1.0.tgz"
15
+ },
16
+ "pnpm": {
17
+ "overrides": {
18
+ "@glydi/passkey-core": "file:../glide/dist-packs/glydi-passkey-core-0.1.0.tgz"
19
+ }
20
+ }
21
+ }
22
+ ```
23
+
24
+ The `pnpm.overrides` entry is required so pnpm never tries to fetch the package from
25
+ the npm registry. See [docs/DISTRIBUTION.md](../../docs/DISTRIBUTION.md) for full tarball
26
+ and `pnpm link` instructions, including how to produce the tarballs.
27
+
28
+ > **Forthcoming:** `npm install @glydi/passkey-core` will be the public form once the
29
+ > package is published. It is not yet available on npm.
30
+
31
+ ## Minimal Usage
32
+
33
+ Import the `./define` subpath to register the `<biometric-auth-button>` custom element,
34
+ then drop the element into your HTML:
35
+
36
+ ```html
37
+ <script type="module">
38
+ import "@glydi/passkey-core/define"; <!-- registers <biometric-auth-button> -->
39
+ </script>
40
+
41
+ <biometric-auth-button mode="auto" label="Sign in with passkey"></biometric-auth-button>
42
+
43
+ <script type="module">
44
+ const el = document.querySelector("biometric-auth-button");
45
+ el.addEventListener("glide:success", (e) => console.log("user", e.detail.user));
46
+ el.addEventListener("glide:error", (e) => console.warn(e.detail.code));
47
+ </script>
48
+ ```
49
+
50
+ > **SSR warning:** The `./define` subpath calls `customElements.define()` — a browser API
51
+ > that does not exist in Node.js or Next.js server runtime. Only import
52
+ > `@glydi/passkey-core/define` in browser/client-side code. Framework adapters (React, Vue,
53
+ > Svelte) do this safely via dynamic `import()` inside `useEffect`/`onMount`/`onMounted`.
54
+
55
+ ## API
56
+
57
+ ### Package exports
58
+
59
+ This package has two entry points:
60
+
61
+ | Subpath | Description |
62
+ |---------|-------------|
63
+ | `@glydi/passkey-core` | Side-effect-free. Exports the class and utilities. Safe in SSR. |
64
+ | `@glydi/passkey-core/define` | Side-effectful. Imports and immediately calls `defineBiometricAuthButton()`. Browser-only. |
65
+
66
+ ### Key exports
67
+
68
+ **`BiometricAuthButton`** — the custom element class (`HTMLElement` subclass). Use this if
69
+ you need a reference to the class itself (e.g. to extend it or inspect its type).
70
+
71
+ **`defineBiometricAuthButton(tagName?)`** — idempotently registers the element under
72
+ `tagName` (defaults to `"biometric-auth-button"`). Safe to call multiple times. Use this
73
+ instead of the `./define` subpath when you want explicit control over when registration
74
+ happens.
75
+
76
+ **`TAG_NAME`** — the string `"biometric-auth-button"`.
77
+
78
+ **`startConditionalUI(options)`** — starts passkey autofill (conditional mediation) for a
79
+ login page that has an `<input autocomplete="username webauthn">`. Returns a `ConditionalUIHandle`
80
+ with a `started` promise and an `abort()` method. Types: `ConditionalUIOptions`,
81
+ `ConditionalUIHandle`.
82
+
83
+ **`base64urlToBuffer(str)`** / **`bufferToBase64url(buf)`** — base64url codec exported for
84
+ consumers doing custom WebAuthn flows.
85
+
86
+ **`DEFAULT_ENDPOINTS`** — the canonical API endpoint paths:
87
+
88
+ ```ts
89
+ const DEFAULT_ENDPOINTS: GlideEndpoints = {
90
+ registerBegin: "/api/passkey/register-begin",
91
+ registerFinish: "/api/passkey/register-finish",
92
+ authenticateBegin: "/api/passkey/authenticate-begin",
93
+ authenticateFinish: "/api/passkey/authenticate-finish",
94
+ };
95
+ ```
96
+
97
+ ### Re-exported types
98
+
99
+ `AuthMode`, `AuthPhase`, `GlideUser`, `AuthResult`, `GlideError`, `GlideEndpoints`,
100
+ `GlideRunConfig`, `GlideEventMap`
101
+
102
+ ### `<biometric-auth-button>` element
103
+
104
+ **HTML attributes:**
105
+
106
+ | Attribute | Type | Description |
107
+ |-----------|------|-------------|
108
+ | `mode` | `"auto" \| "signin" \| "signup"` | Auth mode. Default `"auto"`. |
109
+ | `username` | `string` | Username hint for registration/conditional UI. |
110
+ | `label` | `string` | Button label text. |
111
+ | `disabled` | boolean attr | Disables the button. |
112
+ | `prime` | boolean attr | Show a pre-prompt explainer before the OS biometric sheet. |
113
+ | `prime-title` | `string` | Title for the priming panel. |
114
+ | `prime-body` | `string` | Body text for the priming panel. |
115
+ | `prime-continue` | `string` | Continue button label in the priming panel. |
116
+ | `prime-cancel` | `string` | Cancel button label in the priming panel. |
117
+ | `fallback-href` | `string` | URL of a recovery page (e.g. magic-link). Shown on unsupported/error. |
118
+ | `fallback-label` | `string` | Label for the fallback link. |
119
+ | `register-begin` | `string` | Override endpoint for `registerBegin`. |
120
+ | `register-finish` | `string` | Override endpoint for `registerFinish`. |
121
+ | `authenticate-begin` | `string` | Override endpoint for `authenticateBegin`. |
122
+ | `authenticate-finish` | `string` | Override endpoint for `authenticateFinish`. |
123
+
124
+ > **Valid `mode` values are `"auto"`, `"signin"`, and `"signup"` only.** Using `"register"`
125
+ > or `"authenticate"` is invalid and will silently no-op.
126
+
127
+ **JS properties (not settable as attributes):**
128
+
129
+ - `endpoints: Partial<GlideEndpoints>` — override one or more endpoint paths.
130
+ - `fetchOptions: RequestInit` — merged into every `fetch` call (e.g. custom headers).
131
+ - `phase: AuthPhase` — current phase, reflected (read-only).
132
+
133
+ **Phase values:** `"idle"` | `"priming"` | `"unsupported"` | `"loading"` | `"authenticating"` | `"success"` | `"error"`
134
+
135
+ **Events** (all `bubbles + composed`, cross the shadow boundary):
136
+
137
+ | Event | Detail type | Description |
138
+ |-------|-------------|-------------|
139
+ | `glide:success` | `AuthResult` (`{ user: GlideUser, accessToken?: string }`) | Auth succeeded. |
140
+ | `glide:error` | `GlideError` (`{ code, message, cause? }`) | Auth failed. |
141
+ | `glide:phasechange` | `{ phase: AuthPhase }` | Phase transitioned. |
142
+
143
+ **Error codes:** `"unsupported"` | `"aborted"` | `"no_credentials"` | `"network"` | `"server_rejected"` | `"unknown"`
144
+
145
+ **CSS Shadow Parts** (`::part()`): `button`, `icon`, `label`, `spinner`, `error`,
146
+ `prime`, `prime-title`, `prime-body`, `prime-continue`, `prime-cancel`, `fallback`
147
+
148
+ ## Links
149
+
150
+ - [Root README](../../README.md) — architecture overview and styling API
151
+ - [Quickstart](../../docs/QUICKSTART.md) — full Next.js App Router integration walkthrough
152
+ - [Distribution guide](../../docs/DISTRIBUTION.md) — tarball and pnpm link install details
@@ -0,0 +1,139 @@
1
+ import { b as b$1, a, d, c } from './chunk-DEJPUCAZ.js';
2
+
3
+ var oe=`
4
+ :host {
5
+ /* ---- Public theming API: override these from your global CSS ---- */
6
+ --glide-font: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial,
7
+ sans-serif;
8
+ --glide-radius: 10px;
9
+ --glide-bg: #111827;
10
+ --glide-fg: #ffffff;
11
+ --glide-bg-hover: #1f2937;
12
+ --glide-border: transparent;
13
+ --glide-focus-ring: #6366f1;
14
+ --glide-gap: 0.5rem;
15
+ --glide-padding: 0.7rem 1.1rem;
16
+ --glide-font-size: 0.95rem;
17
+ --glide-font-weight: 600;
18
+ --glide-error-fg: #b91c1c;
19
+
20
+ /* Priming pre-prompt panel theming. */
21
+ --glide-prime-bg: #f9fafb;
22
+ --glide-prime-fg: #111827;
23
+ --glide-prime-muted: #4b5563;
24
+ --glide-prime-border: #e5e7eb;
25
+ --glide-prime-radius: 12px;
26
+
27
+ --glide-fallback-fg: #4b5563;
28
+
29
+ display: inline-block;
30
+ font-family: var(--glide-font);
31
+ }
32
+
33
+ :host([hidden]) { display: none; }
34
+
35
+ button {
36
+ font: inherit;
37
+ font-size: var(--glide-font-size);
38
+ font-weight: var(--glide-font-weight);
39
+ display: inline-flex;
40
+ align-items: center;
41
+ justify-content: center;
42
+ gap: var(--glide-gap);
43
+ width: 100%;
44
+ box-sizing: border-box;
45
+ padding: var(--glide-padding);
46
+ color: var(--glide-fg);
47
+ background: var(--glide-bg);
48
+ border: 1px solid var(--glide-border);
49
+ border-radius: var(--glide-radius);
50
+ cursor: pointer;
51
+ transition: background-color 120ms ease, transform 80ms ease;
52
+ -webkit-tap-highlight-color: transparent;
53
+ }
54
+
55
+ button:hover:not(:disabled) { background: var(--glide-bg-hover); }
56
+ button:active:not(:disabled) { transform: translateY(1px); }
57
+ button:focus-visible {
58
+ outline: 2px solid var(--glide-focus-ring);
59
+ outline-offset: 2px;
60
+ }
61
+ button:disabled { opacity: 0.65; cursor: progress; }
62
+ /* The element-selector display above out-specifies the UA [hidden] rule, so
63
+ restore hide-ability explicitly (used while the priming panel is shown). */
64
+ button[hidden] { display: none; }
65
+
66
+ .icon { display: inline-flex; width: 1.15em; height: 1.15em; }
67
+ .icon svg { width: 100%; height: 100%; }
68
+
69
+ /* Spinner shown while authenticating. */
70
+ .spinner {
71
+ width: 1.05em;
72
+ height: 1.05em;
73
+ border: 2px solid currentColor;
74
+ border-top-color: transparent;
75
+ border-radius: 50%;
76
+ animation: glide-spin 0.7s linear infinite;
77
+ }
78
+ @keyframes glide-spin { to { transform: rotate(360deg); } }
79
+ @media (prefers-reduced-motion: reduce) {
80
+ .spinner { animation-duration: 1.6s; }
81
+ button:active:not(:disabled) { transform: none; }
82
+ }
83
+
84
+ .error {
85
+ margin: var(--glide-gap) 0 0;
86
+ color: var(--glide-error-fg);
87
+ font-size: 0.825rem;
88
+ line-height: 1.3;
89
+ }
90
+ .error[hidden] { display: none; }
91
+
92
+ /* ---- Priming pre-prompt: the explainer shown before the OS sheet ---- */
93
+ .prime {
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: 0.6rem;
97
+ padding: 1rem;
98
+ background: var(--glide-prime-bg);
99
+ color: var(--glide-prime-fg);
100
+ border: 1px solid var(--glide-prime-border);
101
+ border-radius: var(--glide-prime-radius);
102
+ text-align: left;
103
+ }
104
+ .prime[hidden] { display: none; }
105
+ .prime-head { display: flex; align-items: center; gap: 0.5rem; }
106
+ .prime-head .icon { width: 1.4em; height: 1.4em; flex: none; }
107
+ .prime-title { margin: 0; font-size: 0.95rem; font-weight: 650; }
108
+ .prime-body {
109
+ margin: 0;
110
+ font-size: 0.85rem;
111
+ line-height: 1.45;
112
+ color: var(--glide-prime-muted);
113
+ }
114
+ .prime-actions { display: flex; gap: var(--glide-gap); margin-top: 0.25rem; }
115
+ .prime-actions button { width: auto; flex: 1; }
116
+ .prime-cancel {
117
+ color: var(--glide-prime-fg);
118
+ background: transparent;
119
+ border-color: var(--glide-prime-border);
120
+ }
121
+ .prime-cancel:hover:not(:disabled) { background: rgba(0, 0, 0, 0.04); }
122
+
123
+ /* ---- Recovery fallback: the lockout escape hatch (e.g. a magic link) ---- */
124
+ .fallback {
125
+ display: block;
126
+ margin: var(--glide-gap) 0 0;
127
+ font-size: 0.82rem;
128
+ text-align: center;
129
+ color: var(--glide-fallback-fg);
130
+ text-decoration: underline;
131
+ text-underline-offset: 2px;
132
+ cursor: pointer;
133
+ }
134
+ .fallback[hidden] { display: none; }
135
+ `,J=null;function me(){return typeof CSSStyleSheet>"u"||!("replaceSync"in CSSStyleSheet.prototype)?null:(J||(J=new CSSStyleSheet,J.replaceSync(oe)),J)}var v={registerBegin:"/api/passkey/register-begin",registerFinish:"/api/passkey/register-finish",authenticateBegin:"/api/passkey/authenticate-begin",authenticateFinish:"/api/passkey/authenticate-finish"};var ge=null;function be(){return ge??=import('./engine-JXRDZTPB.js'),ge}var ye=["mode","username","label","disabled","register-begin","register-finish","authenticate-begin","authenticate-finish","prime","prime-title","prime-body","prime-continue","prime-cancel","fallback-href","fallback-label"],Q={title:"Use your device to sign in",body:"You'll see a Face ID, Touch ID, or Windows Hello prompt from your device \u2014 that's how you sign in. No password to remember.",continueLabel:"Continue",cancelLabel:"Not now"},Ae=typeof HTMLElement<"u"?HTMLElement:class{},m,l,k,y,h,g,w,b,S,N,U,f,A,x,te,fe,L,X,T,Z,F,le,P,$,G,O,ie,Ee,R,de,D,z,_,he,j,ce,u,p,ne,ve,q,ue,M,ee,I=class extends Ae{constructor(){super();b$1(this,te);b$1(this,L);b$1(this,T);b$1(this,F);b$1(this,P);b$1(this,ie);b$1(this,R);b$1(this,_);b$1(this,j);b$1(this,u);b$1(this,ne);b$1(this,q);b$1(this,M);b$1(this,m,{});this.fetchOptions={};b$1(this,l,"idle");b$1(this,k,false);b$1(this,y,void 0);b$1(this,h,void 0);b$1(this,g,void 0);b$1(this,w,void 0);b$1(this,b,void 0);b$1(this,S,void 0);b$1(this,N,void 0);b$1(this,U,void 0);b$1(this,f,void 0);b$1(this,A,void 0);b$1(this,x,void 0);b$1(this,G,()=>{be().catch(()=>{});});b$1(this,O,()=>{if(!(a(this,k)||a(this,l)==="unsupported")){if(this.prime){d(this,ie,Ee).call(this);return}d(this,_,he).call(this);}});b$1(this,D,()=>{d(this,R,de).call(this),d(this,_,he).call(this);});b$1(this,z,()=>{d(this,R,de).call(this),d(this,u,p).call(this,"idle"),a(this,h).focus();});c(this,y,this.attachShadow({mode:"open"})),d(this,te,fe).call(this);}static get observedAttributes(){return ye}get mode(){return this.getAttribute("mode")??"auto"}set mode(t){this.setAttribute("mode",t);}get username(){return this.getAttribute("username")??void 0}set username(t){t==null?this.removeAttribute("username"):this.setAttribute("username",t);}get label(){return this.getAttribute("label")??"Continue with passkey"}set label(t){this.setAttribute("label",t);}get prime(){return this.hasAttribute("prime")}set prime(t){t?this.setAttribute("prime",""):this.removeAttribute("prime");}get primeTitle(){return this.getAttribute("prime-title")??Q.title}get primeBody(){return this.getAttribute("prime-body")??Q.body}get primeContinueLabel(){return this.getAttribute("prime-continue")??Q.continueLabel}get primeCancelLabel(){return this.getAttribute("prime-cancel")??Q.cancelLabel}get fallbackHref(){return this.getAttribute("fallback-href")}get fallbackLabel(){return this.getAttribute("fallback-label")??"Use a sign-in link instead"}get endpoints(){return {registerBegin:a(this,m).registerBegin??this.getAttribute("register-begin")??v.registerBegin,registerFinish:a(this,m).registerFinish??this.getAttribute("register-finish")??v.registerFinish,authenticateBegin:a(this,m).authenticateBegin??this.getAttribute("authenticate-begin")??v.authenticateBegin,authenticateFinish:a(this,m).authenticateFinish??this.getAttribute("authenticate-finish")??v.authenticateFinish}}set endpoints(t){c(this,m,{...t});}get phase(){return a(this,l)}connectedCallback(){a(this,h).addEventListener("click",a(this,O)),a(this,f).addEventListener("click",a(this,D)),a(this,A).addEventListener("click",a(this,z)),this.addEventListener("pointerenter",a(this,G),{once:true}),this.addEventListener("focusin",a(this,G),{once:true}),typeof window>"u"||typeof window.PublicKeyCredential!="function"?(d(this,u,p).call(this,"unsupported"),a(this,h).disabled=true):(d(this,F,le).call(this),d(this,P,$).call(this));}disconnectedCallback(){a(this,h).removeEventListener("click",a(this,O)),a(this,f).removeEventListener("click",a(this,D)),a(this,A).removeEventListener("click",a(this,z));}attributeChangedCallback(t){t==="label"?d(this,F,le).call(this):t==="disabled"?d(this,P,$).call(this):t.startsWith("prime")?d(this,T,Z).call(this):t.startsWith("fallback")&&d(this,L,X).call(this);}};m=new WeakMap,l=new WeakMap,k=new WeakMap,y=new WeakMap,h=new WeakMap,g=new WeakMap,w=new WeakMap,b=new WeakMap,S=new WeakMap,N=new WeakMap,U=new WeakMap,f=new WeakMap,A=new WeakMap,x=new WeakMap,te=new WeakSet,fe=function(){let t=me();if(t)a(this,y).adoptedStyleSheets=[t];else {let pe=document.createElement("style");pe.textContent=oe,a(this,y).appendChild(pe);}let r=document.createElement("button");r.type="button",r.setAttribute("part","button");let d$1=document.createElement("span");d$1.className="icon",d$1.setAttribute("part","icon"),d$1.setAttribute("aria-hidden","true"),d$1.appendChild(ae());let c$1=document.createElement("span");c$1.setAttribute("part","label"),r.append(d$1,c$1);let C=document.createElement("p");C.className="error",C.setAttribute("part","error"),C.setAttribute("role","alert"),C.hidden=true;let E=document.createElement("div");E.className="prime",E.setAttribute("part","prime"),E.setAttribute("role","group"),E.hidden=true;let re=document.createElement("div");re.className="prime-head";let V=document.createElement("span");V.className="icon",V.setAttribute("aria-hidden","true"),V.appendChild(ae());let W=document.createElement("p");W.className="prime-title",W.setAttribute("part","prime-title"),re.append(V,W);let Y=document.createElement("p");Y.className="prime-body",Y.setAttribute("part","prime-body");let se=document.createElement("div");se.className="prime-actions";let K=document.createElement("button");K.type="button",K.setAttribute("part","prime-continue");let B=document.createElement("button");B.type="button",B.className="prime-cancel",B.setAttribute("part","prime-cancel"),se.append(B,K),E.append(re,Y,se);let H=document.createElement("a");H.className="fallback",H.setAttribute("part","fallback"),H.hidden=true,a(this,y).append(r,E,C,H),c(this,h,r),c(this,w,d$1),c(this,g,c$1),c(this,b,C),c(this,S,E),c(this,N,W),c(this,U,Y),c(this,f,K),c(this,A,B),c(this,x,H),a(this,g).textContent=this.label,d(this,T,Z).call(this),d(this,L,X).call(this);},L=new WeakSet,X=function(){let t=this.fallbackHref,r=!!t&&(a(this,l)==="unsupported"||a(this,l)==="error");t&&(a(this,x).href=t,a(this,x).textContent=this.fallbackLabel),a(this,x).hidden=!r;},T=new WeakSet,Z=function(){a(this,N).textContent=this.primeTitle,a(this,U).textContent=this.primeBody,a(this,f).textContent=this.primeContinueLabel,a(this,A).textContent=this.primeCancelLabel;},F=new WeakSet,le=function(){(a(this,l)==="idle"||a(this,l)==="error")&&(a(this,g).textContent=this.label);},P=new WeakSet,$=function(){a(this,l)!=="unsupported"&&(a(this,h).disabled=this.hasAttribute("disabled")||a(this,k));},G=new WeakMap,O=new WeakMap,ie=new WeakSet,Ee=function(){d(this,q,ue).call(this),d(this,T,Z).call(this),a(this,h).hidden=true,a(this,S).hidden=false,d(this,u,p).call(this,"priming"),a(this,G).call(this),a(this,f).focus();},R=new WeakSet,de=function(){a(this,S).hidden=true,a(this,h).hidden=false;},D=new WeakMap,z=new WeakMap,_=new WeakSet,he=async function(){d(this,q,ue).call(this),d(this,j,ce).call(this,true),d(this,u,p).call(this,"loading");try{let t=await be();d(this,u,p).call(this,"authenticating");let r=await t.run({mode:this.mode,endpoints:this.endpoints,username:this.username,fetchOptions:this.fetchOptions});d(this,u,p).call(this,"success"),d(this,M,ee).call(this,"glide:success",r);}catch(t){let r=xe(t);d(this,u,p).call(this,"error"),r.code!=="aborted"&&d(this,ne,ve).call(this,r.message),d(this,M,ee).call(this,"glide:error",r);}finally{d(this,j,ce).call(this,false);}},j=new WeakSet,ce=function(t){if(c(this,k,t),d(this,P,$).call(this),t){let r=document.createElement("span");r.className="spinner",r.setAttribute("part","spinner"),r.setAttribute("aria-hidden","true"),a(this,w).replaceChildren(r),a(this,g).textContent="Authenticating\u2026";}else a(this,w).replaceChildren(ae()),a(this,g).textContent=this.label;},u=new WeakSet,p=function(t){t!==a(this,l)&&(c(this,l,t),this.setAttribute("phase",t),d(this,L,X).call(this),d(this,M,ee).call(this,"glide:phasechange",{phase:t}));},ne=new WeakSet,ve=function(t){a(this,b).textContent=t,a(this,b).hidden=false;},q=new WeakSet,ue=function(){a(this,b).textContent="",a(this,b).hidden=true;},M=new WeakSet,ee=function(t,r){this.dispatchEvent(new CustomEvent(t,{detail:r,bubbles:true,composed:true}));};function xe(s){return s&&typeof s=="object"&&"code"in s&&"message"in s?s:{code:"unknown",message:s instanceof Error?s.message:"Authentication failed.",cause:s}}function ae(){let s="http://www.w3.org/2000/svg",a=document.createElementNS(s,"svg");a.setAttribute("viewBox","0 0 24 24"),a.setAttribute("fill","none"),a.setAttribute("stroke","currentColor"),a.setAttribute("stroke-width","1.6"),a.setAttribute("stroke-linecap","round"),a.setAttribute("stroke-linejoin","round");let t=["M12 11c0 4 0 6-1 8","M8.5 6.8a6 6 0 0 1 9.5 4.9c0 1.3 0 2.4-.2 3.5","M5.5 13c0-4 3-7 6.5-7","M9 13c0-1.7 1.3-3 3-3s3 1.3 3 3c0 3 0 5-.6 7","M12 13v1c0 3.5-.4 5.6-1.3 7.5"];for(let r of t){let d=document.createElementNS(s,"path");d.setAttribute("d",r),a.appendChild(d);}return a}function Ce(s){let a=new AbortController,t=(async()=>{let r=await import('./engine-JXRDZTPB.js');if(!await r.isConditionalUIAvailable())return false;let d={...v,...s.endpoints};try{let c=await r.authenticate({mode:"signin",endpoints:d,username:s.username,fetchOptions:s.fetchOptions??{}},{mediation:"conditional",signal:a.signal});s.onSuccess(c);}catch(c){c?.code!=="aborted"&&(s.onError?.(c),typeof window<"u"&&window.dispatchEvent(new CustomEvent("glide:error",{detail:c,bubbles:true,composed:true})),s.onError||console.warn("[Glide] Conditional UI error (set onError to suppress):",c));}return true})();return {abort:()=>a.abort(),started:t}}var ke="biometric-auth-button";function Fe(s=ke){typeof customElements>"u"||customElements.get(s)||customElements.define(s,I);}
136
+
137
+ export { v as a, I as b, Ce as c, ke as d, Fe as e };
138
+ //# sourceMappingURL=out.js.map
139
+ //# sourceMappingURL=chunk-2NSEYZ3C.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/styles.ts","../src/types.ts","../src/biometric-auth-button.ts","../src/conditional.ts","../src/index.ts"],"names":["CSS","sheet","getSharedSheet","DEFAULT_ENDPOINTS","enginePromise","loadEngine","OBSERVED","PRIME_DEFAULTS","HTMLElementBase","_endpointOverrides","_phase","_busy","_root","_button","_labelEl","_iconSlot","_errorEl","_primeEl","_primeTitleEl","_primeBodyEl","_primeContinue","_primeCancel","_fallbackEl","_renderSkeleton","renderSkeleton_fn","_syncFallback","syncFallback_fn","_syncPrimeText","syncPrimeText_fn","_syncLabel","syncLabel_fn","_syncDisabled","syncDisabled_fn","_prefetch","_onClick","_showPrime","showPrime_fn","_hidePrime","hidePrime_fn","_onPrimeContinue","_onPrimeCancel","_runCeremony","runCeremony_fn","_setBusy","setBusy_fn","_setPhase","setPhase_fn","_showError","showError_fn","_clearError","clearError_fn","_emit","emit_fn","BiometricAuthButton","__privateAdd","__privateGet","__privateMethod","__privateSet","v","name","shared","style","button","icon","buildFingerprintIcon","label","error","prime","primeHead","primeIcon","primeTitle","primeBody","primeActions","primeContinue","primeCancel","fallback","href","shouldShow","engine","result","err","e","normalizeError","busy","spinner","phase","message","type","detail","NS","svg","paths","d","p","startConditionalUI","opts","controller","started","endpoints","TAG_NAME","defineBiometricAuthButton","tagName"],"mappings":"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","sourcesContent":["/**\n * Skeleton styles for the component, scoped inside the Shadow DOM.\n *\n * Design philosophy: HEADLESS BY DEFAULT.\n * - System font stack, neutral layout, no opinionated colors/brand.\n * - Everything visual is driven by CSS Custom Properties (--glide-*) which\n * pierce the shadow boundary, so clients theme from their global stylesheet.\n * - Every meaningful element exposes a `part=` so clients can target it with\n * ::part() — including pseudo-states like :hover.\n *\n * We return a CSSStyleSheet when constructable stylesheets are supported (so the\n * sheet is shared across all instances → zero per-instance parse cost), and fall\n * back to a <style> string otherwise.\n */\n\nexport const CSS = /* css */ `\n :host {\n /* ---- Public theming API: override these from your global CSS ---- */\n --glide-font: system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial,\n sans-serif;\n --glide-radius: 10px;\n --glide-bg: #111827;\n --glide-fg: #ffffff;\n --glide-bg-hover: #1f2937;\n --glide-border: transparent;\n --glide-focus-ring: #6366f1;\n --glide-gap: 0.5rem;\n --glide-padding: 0.7rem 1.1rem;\n --glide-font-size: 0.95rem;\n --glide-font-weight: 600;\n --glide-error-fg: #b91c1c;\n\n /* Priming pre-prompt panel theming. */\n --glide-prime-bg: #f9fafb;\n --glide-prime-fg: #111827;\n --glide-prime-muted: #4b5563;\n --glide-prime-border: #e5e7eb;\n --glide-prime-radius: 12px;\n\n --glide-fallback-fg: #4b5563;\n\n display: inline-block;\n font-family: var(--glide-font);\n }\n\n :host([hidden]) { display: none; }\n\n button {\n font: inherit;\n font-size: var(--glide-font-size);\n font-weight: var(--glide-font-weight);\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: var(--glide-gap);\n width: 100%;\n box-sizing: border-box;\n padding: var(--glide-padding);\n color: var(--glide-fg);\n background: var(--glide-bg);\n border: 1px solid var(--glide-border);\n border-radius: var(--glide-radius);\n cursor: pointer;\n transition: background-color 120ms ease, transform 80ms ease;\n -webkit-tap-highlight-color: transparent;\n }\n\n button:hover:not(:disabled) { background: var(--glide-bg-hover); }\n button:active:not(:disabled) { transform: translateY(1px); }\n button:focus-visible {\n outline: 2px solid var(--glide-focus-ring);\n outline-offset: 2px;\n }\n button:disabled { opacity: 0.65; cursor: progress; }\n /* The element-selector display above out-specifies the UA [hidden] rule, so\n restore hide-ability explicitly (used while the priming panel is shown). */\n button[hidden] { display: none; }\n\n .icon { display: inline-flex; width: 1.15em; height: 1.15em; }\n .icon svg { width: 100%; height: 100%; }\n\n /* Spinner shown while authenticating. */\n .spinner {\n width: 1.05em;\n height: 1.05em;\n border: 2px solid currentColor;\n border-top-color: transparent;\n border-radius: 50%;\n animation: glide-spin 0.7s linear infinite;\n }\n @keyframes glide-spin { to { transform: rotate(360deg); } }\n @media (prefers-reduced-motion: reduce) {\n .spinner { animation-duration: 1.6s; }\n button:active:not(:disabled) { transform: none; }\n }\n\n .error {\n margin: var(--glide-gap) 0 0;\n color: var(--glide-error-fg);\n font-size: 0.825rem;\n line-height: 1.3;\n }\n .error[hidden] { display: none; }\n\n /* ---- Priming pre-prompt: the explainer shown before the OS sheet ---- */\n .prime {\n display: flex;\n flex-direction: column;\n gap: 0.6rem;\n padding: 1rem;\n background: var(--glide-prime-bg);\n color: var(--glide-prime-fg);\n border: 1px solid var(--glide-prime-border);\n border-radius: var(--glide-prime-radius);\n text-align: left;\n }\n .prime[hidden] { display: none; }\n .prime-head { display: flex; align-items: center; gap: 0.5rem; }\n .prime-head .icon { width: 1.4em; height: 1.4em; flex: none; }\n .prime-title { margin: 0; font-size: 0.95rem; font-weight: 650; }\n .prime-body {\n margin: 0;\n font-size: 0.85rem;\n line-height: 1.45;\n color: var(--glide-prime-muted);\n }\n .prime-actions { display: flex; gap: var(--glide-gap); margin-top: 0.25rem; }\n .prime-actions button { width: auto; flex: 1; }\n .prime-cancel {\n color: var(--glide-prime-fg);\n background: transparent;\n border-color: var(--glide-prime-border);\n }\n .prime-cancel:hover:not(:disabled) { background: rgba(0, 0, 0, 0.04); }\n\n /* ---- Recovery fallback: the lockout escape hatch (e.g. a magic link) ---- */\n .fallback {\n display: block;\n margin: var(--glide-gap) 0 0;\n font-size: 0.82rem;\n text-align: center;\n color: var(--glide-fallback-fg);\n text-decoration: underline;\n text-underline-offset: 2px;\n cursor: pointer;\n }\n .fallback[hidden] { display: none; }\n`;\n\nlet sheet: CSSStyleSheet | null = null;\n\n/** Lazily build a single shared constructable stylesheet (when supported). */\nexport function getSharedSheet(): CSSStyleSheet | null {\n if (typeof CSSStyleSheet === \"undefined\" || !(\"replaceSync\" in CSSStyleSheet.prototype)) {\n return null;\n }\n if (!sheet) {\n sheet = new CSSStyleSheet();\n sheet.replaceSync(CSS);\n }\n return sheet;\n}\n","/**\n * Public types for the Glide passkey core.\n * Kept dependency-free so the bundle stays tiny and tree-shakeable.\n */\n\nexport type AuthMode = \"auto\" | \"signin\" | \"signup\";\n\nexport type AuthPhase =\n | \"idle\"\n | \"priming\" // showing the pre-prompt explainer before the native OS sheet\n | \"unsupported\"\n | \"loading\"\n | \"authenticating\"\n | \"success\"\n | \"error\";\n\nexport interface GlideUser {\n /** Stable opaque user id returned by your backend. */\n id: string;\n /** Optional display fields echoed back from the server. */\n name?: string;\n email?: string;\n}\n\nexport interface AuthResult {\n user: GlideUser;\n /**\n * Short-lived access token, if your backend returns one in the JSON body.\n * SECURITY: this is handed to you in memory only. Never persist it to\n * localStorage/sessionStorage. See SECURITY.md.\n */\n accessToken?: string;\n}\n\nexport interface GlideError {\n code:\n | \"unsupported\"\n | \"aborted\"\n | \"no_credentials\"\n | \"network\"\n | \"server_rejected\"\n | \"unknown\";\n message: string;\n cause?: unknown;\n}\n\n/**\n * Endpoints the component talks to. All are POST and return JSON.\n * Defaults use hyphenated single-segment paths (e.g. `/api/passkey/register-begin`)\n * matching the demo's `[action]` route handler; override per-deployment as needed.\n */\nexport interface GlideEndpoints {\n /** Begin registration → returns PublicKeyCredentialCreationOptions (JSON). */\n registerBegin: string;\n /** Finish registration → returns { user, accessToken? }. */\n registerFinish: string;\n /** Begin authentication → returns PublicKeyCredentialRequestOptions (JSON). */\n authenticateBegin: string;\n /** Finish authentication → returns { user, accessToken? }. */\n authenticateFinish: string;\n}\n\nexport const DEFAULT_ENDPOINTS: GlideEndpoints = {\n registerBegin: \"/api/passkey/register-begin\",\n registerFinish: \"/api/passkey/register-finish\",\n authenticateBegin: \"/api/passkey/authenticate-begin\",\n authenticateFinish: \"/api/passkey/authenticate-finish\",\n};\n\n/** Resolved configuration the engine receives. */\nexport interface GlideRunConfig {\n mode: AuthMode;\n endpoints: GlideEndpoints;\n /** Optional hint forwarded to the begin endpoints (e.g. an email). */\n username?: string | undefined;\n /** Forwarded to fetch; cookies must be included for the refresh-token flow. */\n fetchOptions: RequestInit;\n}\n\n/** Detail payload for the `glide:*` CustomEvents the component dispatches. */\nexport interface GlideEventMap {\n \"glide:success\": AuthResult;\n \"glide:error\": GlideError;\n \"glide:phasechange\": { phase: AuthPhase };\n}\n","/**\n * <biometric-auth-button> — the framework-agnostic core of Glide.\n *\n * Responsibilities (UI only — all WebAuthn logic lives in the lazy engine):\n * - Render a neutral, headless button inside an (open) Shadow DOM.\n * - Expose a clean theming API: CSS Custom Properties + CSS ::part()s.\n * - Lazy-load the WebAuthn engine on first interaction (keeps bundle tiny).\n * - Drive the auth flow and emit `glide:*` CustomEvents + reflect phase.\n *\n * Public attributes:\n * mode=\"auto|signin|signup\" username=\"...\" label=\"...\"\n * register-begin / register-finish / authenticate-begin / authenticate-finish\n * disabled\n * prime prime-title=\"...\" prime-body=\"...\" prime-continue=\"...\" prime-cancel=\"...\"\n * fallback-href=\"...\" fallback-label=\"...\"\n *\n * Public JS properties: mode, username, label, endpoints, fetchOptions, phase\n *\n * Events: glide:success, glide:error, glide:phasechange\n *\n * CSS parts: button, icon, label, spinner, error,\n * prime, prime-title, prime-body, prime-continue, prime-cancel,\n * fallback\n *\n * Priming: when the `prime` attribute is present, the first tap shows a small,\n * themeable explainer (\"You'll see a Face ID popup — that's how you sign in\")\n * BEFORE the native OS sheet. This is the #1 conversion lever for non-technical\n * / older users who otherwise bounce at the unexplained system prompt.\n */\n\nimport { CSS, getSharedSheet } from \"./styles.js\";\nimport {\n DEFAULT_ENDPOINTS,\n type AuthMode,\n type AuthPhase,\n type AuthResult,\n type GlideEndpoints,\n type GlideError,\n} from \"./types.js\";\n\n// Lazily-imported engine module. Typed via `import type` so it costs nothing\n// in the shipped shell; the real code is fetched on demand.\ntype Engine = typeof import(\"./webauthn/engine.js\");\nlet enginePromise: Promise<Engine> | null = null;\nfunction loadEngine(): Promise<Engine> {\n // Cached across all instances → fetched at most once per page.\n enginePromise ??= import(\"./webauthn/engine.js\");\n return enginePromise;\n}\n\nconst OBSERVED = [\n \"mode\",\n \"username\",\n \"label\",\n \"disabled\",\n \"register-begin\",\n \"register-finish\",\n \"authenticate-begin\",\n \"authenticate-finish\",\n \"prime\",\n \"prime-title\",\n \"prime-body\",\n \"prime-continue\",\n \"prime-cancel\",\n \"fallback-href\",\n \"fallback-label\",\n] as const;\n\nconst PRIME_DEFAULTS = {\n title: \"Use your device to sign in\",\n body: \"You'll see a Face ID, Touch ID, or Windows Hello prompt from your device — that's how you sign in. No password to remember.\",\n continueLabel: \"Continue\",\n cancelLabel: \"Not now\",\n} as const;\n\n// SSR-safe base: in Node (Next prerender, RSC) there is no HTMLElement global,\n// and `class extends undefined` throws at definition time. We fall back to a\n// dummy base so merely IMPORTING this module is safe; the element is only ever\n// instantiated/registered in the browser (guarded in defineBiometricAuthButton).\nconst HTMLElementBase: typeof HTMLElement =\n typeof HTMLElement !== \"undefined\"\n ? HTMLElement\n : (class {} as unknown as typeof HTMLElement);\n\nexport class BiometricAuthButton extends HTMLElementBase {\n static get observedAttributes(): readonly string[] {\n return OBSERVED;\n }\n\n /** Per-instance endpoint overrides set via JS property (win over attributes). */\n #endpointOverrides: Partial<GlideEndpoints> = {};\n /** Extra fetch init (e.g. custom headers) — set via JS property only. */\n fetchOptions: RequestInit = {};\n\n #phase: AuthPhase = \"idle\";\n #busy = false;\n\n // Cached shadow refs.\n #root: ShadowRoot;\n #button!: HTMLButtonElement;\n #labelEl!: HTMLSpanElement;\n #iconSlot!: HTMLSpanElement;\n #errorEl!: HTMLParagraphElement;\n // Priming panel refs.\n #primeEl!: HTMLDivElement;\n #primeTitleEl!: HTMLParagraphElement;\n #primeBodyEl!: HTMLParagraphElement;\n #primeContinue!: HTMLButtonElement;\n #primeCancel!: HTMLButtonElement;\n #fallbackEl!: HTMLAnchorElement;\n\n constructor() {\n super();\n this.#root = this.attachShadow({ mode: \"open\" });\n this.#renderSkeleton();\n }\n\n /* ---------------------------------------------------------------- props */\n\n get mode(): AuthMode {\n return (this.getAttribute(\"mode\") as AuthMode) ?? \"auto\";\n }\n set mode(v: AuthMode) {\n this.setAttribute(\"mode\", v);\n }\n\n get username(): string | undefined {\n return this.getAttribute(\"username\") ?? undefined;\n }\n set username(v: string | undefined) {\n if (v == null) this.removeAttribute(\"username\");\n else this.setAttribute(\"username\", v);\n }\n\n get label(): string {\n return this.getAttribute(\"label\") ?? \"Continue with passkey\";\n }\n set label(v: string) {\n this.setAttribute(\"label\", v);\n }\n\n /** Whether to show the priming explainer before the ceremony. */\n get prime(): boolean {\n return this.hasAttribute(\"prime\");\n }\n set prime(v: boolean) {\n if (v) this.setAttribute(\"prime\", \"\");\n else this.removeAttribute(\"prime\");\n }\n\n get primeTitle(): string {\n return this.getAttribute(\"prime-title\") ?? PRIME_DEFAULTS.title;\n }\n get primeBody(): string {\n return this.getAttribute(\"prime-body\") ?? PRIME_DEFAULTS.body;\n }\n get primeContinueLabel(): string {\n return this.getAttribute(\"prime-continue\") ?? PRIME_DEFAULTS.continueLabel;\n }\n get primeCancelLabel(): string {\n return this.getAttribute(\"prime-cancel\") ?? PRIME_DEFAULTS.cancelLabel;\n }\n\n /** Where the recovery fallback link points (e.g. your magic-link page). */\n get fallbackHref(): string | null {\n return this.getAttribute(\"fallback-href\");\n }\n get fallbackLabel(): string {\n return this.getAttribute(\"fallback-label\") ?? \"Use a sign-in link instead\";\n }\n\n /** Read merged endpoints (defaults ← attributes ← JS overrides). */\n get endpoints(): GlideEndpoints {\n return {\n registerBegin:\n this.#endpointOverrides.registerBegin ??\n this.getAttribute(\"register-begin\") ??\n DEFAULT_ENDPOINTS.registerBegin,\n registerFinish:\n this.#endpointOverrides.registerFinish ??\n this.getAttribute(\"register-finish\") ??\n DEFAULT_ENDPOINTS.registerFinish,\n authenticateBegin:\n this.#endpointOverrides.authenticateBegin ??\n this.getAttribute(\"authenticate-begin\") ??\n DEFAULT_ENDPOINTS.authenticateBegin,\n authenticateFinish:\n this.#endpointOverrides.authenticateFinish ??\n this.getAttribute(\"authenticate-finish\") ??\n DEFAULT_ENDPOINTS.authenticateFinish,\n };\n }\n set endpoints(v: Partial<GlideEndpoints>) {\n this.#endpointOverrides = { ...v };\n }\n\n get phase(): AuthPhase {\n return this.#phase;\n }\n\n /* ------------------------------------------------------------ lifecycle */\n\n connectedCallback(): void {\n this.#button.addEventListener(\"click\", this.#onClick);\n this.#primeContinue.addEventListener(\"click\", this.#onPrimeContinue);\n this.#primeCancel.addEventListener(\"click\", this.#onPrimeCancel);\n // Prefetch the engine on intent (hover/focus) so the click feels instant.\n this.addEventListener(\"pointerenter\", this.#prefetch, { once: true });\n this.addEventListener(\"focusin\", this.#prefetch, { once: true });\n\n // Cheap, synchronous feature detection. If unsupported, reflect it so\n // clients can style/hide via [phase=\"unsupported\"] or ::part.\n if (\n typeof window === \"undefined\" ||\n typeof window.PublicKeyCredential !== \"function\"\n ) {\n this.#setPhase(\"unsupported\");\n this.#button.disabled = true;\n } else {\n this.#syncLabel();\n this.#syncDisabled();\n }\n }\n\n disconnectedCallback(): void {\n this.#button.removeEventListener(\"click\", this.#onClick);\n this.#primeContinue.removeEventListener(\"click\", this.#onPrimeContinue);\n this.#primeCancel.removeEventListener(\"click\", this.#onPrimeCancel);\n }\n\n attributeChangedCallback(name: string): void {\n if (name === \"label\") this.#syncLabel();\n else if (name === \"disabled\") this.#syncDisabled();\n else if (name.startsWith(\"prime\")) this.#syncPrimeText();\n else if (name.startsWith(\"fallback\")) this.#syncFallback();\n }\n\n /* --------------------------------------------------------------- render */\n\n #renderSkeleton(): void {\n const shared = getSharedSheet();\n if (shared) {\n this.#root.adoptedStyleSheets = [shared];\n } else {\n const style = document.createElement(\"style\");\n style.textContent = CSS; // trusted constant, not user data\n this.#root.appendChild(style);\n }\n\n const button = document.createElement(\"button\");\n button.type = \"button\";\n button.setAttribute(\"part\", \"button\");\n\n const icon = document.createElement(\"span\");\n icon.className = \"icon\";\n icon.setAttribute(\"part\", \"icon\");\n icon.setAttribute(\"aria-hidden\", \"true\");\n icon.appendChild(buildFingerprintIcon());\n\n const label = document.createElement(\"span\");\n label.setAttribute(\"part\", \"label\");\n\n button.append(icon, label);\n\n const error = document.createElement(\"p\");\n error.className = \"error\";\n error.setAttribute(\"part\", \"error\");\n error.setAttribute(\"role\", \"alert\");\n error.hidden = true;\n\n // ---- Priming panel (hidden until the first tap when `prime` is set) ----\n const prime = document.createElement(\"div\");\n prime.className = \"prime\";\n prime.setAttribute(\"part\", \"prime\");\n prime.setAttribute(\"role\", \"group\");\n prime.hidden = true;\n\n const primeHead = document.createElement(\"div\");\n primeHead.className = \"prime-head\";\n const primeIcon = document.createElement(\"span\");\n primeIcon.className = \"icon\";\n primeIcon.setAttribute(\"aria-hidden\", \"true\");\n primeIcon.appendChild(buildFingerprintIcon());\n const primeTitle = document.createElement(\"p\");\n primeTitle.className = \"prime-title\";\n primeTitle.setAttribute(\"part\", \"prime-title\");\n primeHead.append(primeIcon, primeTitle);\n\n const primeBody = document.createElement(\"p\");\n primeBody.className = \"prime-body\";\n primeBody.setAttribute(\"part\", \"prime-body\");\n\n const primeActions = document.createElement(\"div\");\n primeActions.className = \"prime-actions\";\n const primeContinue = document.createElement(\"button\");\n primeContinue.type = \"button\";\n primeContinue.setAttribute(\"part\", \"prime-continue\");\n const primeCancel = document.createElement(\"button\");\n primeCancel.type = \"button\";\n primeCancel.className = \"prime-cancel\";\n primeCancel.setAttribute(\"part\", \"prime-cancel\");\n primeActions.append(primeCancel, primeContinue);\n\n prime.append(primeHead, primeBody, primeActions);\n\n // Recovery fallback link (hidden until passkeys are unavailable/fail).\n const fallback = document.createElement(\"a\");\n fallback.className = \"fallback\";\n fallback.setAttribute(\"part\", \"fallback\");\n fallback.hidden = true;\n\n this.#root.append(button, prime, error, fallback);\n\n this.#button = button;\n this.#iconSlot = icon;\n this.#labelEl = label;\n this.#errorEl = error;\n this.#primeEl = prime;\n this.#primeTitleEl = primeTitle;\n this.#primeBodyEl = primeBody;\n this.#primeContinue = primeContinue;\n this.#primeCancel = primeCancel;\n this.#fallbackEl = fallback;\n\n this.#labelEl.textContent = this.label;\n this.#syncPrimeText();\n this.#syncFallback();\n }\n\n /**\n * The fallback is the lockout escape hatch. We surface it whenever passkeys\n * can't carry the user through: the browser is unsupported, or an attempt\n * failed (including a cancelled prompt). Hidden on success and while idle.\n */\n #syncFallback(): void {\n const href = this.fallbackHref;\n const shouldShow =\n !!href &&\n (this.#phase === \"unsupported\" || this.#phase === \"error\");\n if (href) {\n this.#fallbackEl.href = href;\n this.#fallbackEl.textContent = this.fallbackLabel;\n }\n this.#fallbackEl.hidden = !shouldShow;\n }\n\n #syncPrimeText(): void {\n this.#primeTitleEl.textContent = this.primeTitle;\n this.#primeBodyEl.textContent = this.primeBody;\n this.#primeContinue.textContent = this.primeContinueLabel;\n this.#primeCancel.textContent = this.primeCancelLabel;\n }\n\n #syncLabel(): void {\n if (this.#phase === \"idle\" || this.#phase === \"error\") {\n this.#labelEl.textContent = this.label;\n }\n }\n\n #syncDisabled(): void {\n if (this.#phase === \"unsupported\") return;\n this.#button.disabled = this.hasAttribute(\"disabled\") || this.#busy;\n }\n\n /* ------------------------------------------------------------- behavior */\n\n #prefetch = (): void => {\n // Fire-and-forget; failures surface later on click.\n void loadEngine().catch(() => {});\n };\n\n #onClick = (): void => {\n if (this.#busy || this.#phase === \"unsupported\") return;\n // With priming on, the first tap shows the explainer instead of going\n // straight to the OS sheet. The ceremony runs from the \"Continue\" button.\n if (this.prime) {\n this.#showPrime();\n return;\n }\n void this.#runCeremony();\n };\n\n #showPrime(): void {\n this.#clearError();\n this.#syncPrimeText();\n this.#button.hidden = true;\n this.#primeEl.hidden = false;\n this.#setPhase(\"priming\");\n // Now is a great moment to fetch the engine so \"Continue\" is instant.\n this.#prefetch();\n this.#primeContinue.focus();\n }\n\n #hidePrime(): void {\n this.#primeEl.hidden = true;\n this.#button.hidden = false;\n }\n\n #onPrimeContinue = (): void => {\n this.#hidePrime();\n void this.#runCeremony();\n };\n\n #onPrimeCancel = (): void => {\n this.#hidePrime();\n this.#setPhase(\"idle\");\n this.#button.focus();\n };\n\n async #runCeremony(): Promise<void> {\n this.#clearError();\n this.#setBusy(true);\n this.#setPhase(\"loading\");\n try {\n const engine = await loadEngine();\n this.#setPhase(\"authenticating\");\n const result = await engine.run({\n mode: this.mode,\n endpoints: this.endpoints,\n username: this.username,\n fetchOptions: this.fetchOptions,\n });\n this.#setPhase(\"success\");\n this.#emit(\"glide:success\", result);\n } catch (err) {\n const e = normalizeError(err);\n this.#setPhase(\"error\");\n // \"aborted\" is a user action (cancelled prompt), not a hard failure:\n // keep the UI quiet but still emit so hosts can react.\n if (e.code !== \"aborted\") this.#showError(e.message);\n this.#emit(\"glide:error\", e);\n } finally {\n this.#setBusy(false);\n }\n }\n\n #setBusy(busy: boolean): void {\n this.#busy = busy;\n this.#syncDisabled();\n if (busy) {\n const spinner = document.createElement(\"span\");\n spinner.className = \"spinner\";\n spinner.setAttribute(\"part\", \"spinner\");\n spinner.setAttribute(\"aria-hidden\", \"true\");\n this.#iconSlot.replaceChildren(spinner);\n this.#labelEl.textContent = \"Authenticating…\";\n } else {\n this.#iconSlot.replaceChildren(buildFingerprintIcon());\n // Always restore the resting label once the ceremony settles, regardless\n // of phase, so a finished button never reads \"Authenticating…\".\n this.#labelEl.textContent = this.label;\n }\n }\n\n #setPhase(phase: AuthPhase): void {\n if (phase === this.#phase) return;\n this.#phase = phase;\n // Reflect to an attribute so hosts can style by state: [phase=\"error\"] {}\n this.setAttribute(\"phase\", phase);\n this.#syncFallback();\n this.#emit(\"glide:phasechange\", { phase });\n }\n\n #showError(message: string): void {\n this.#errorEl.textContent = message;\n this.#errorEl.hidden = false;\n }\n #clearError(): void {\n this.#errorEl.textContent = \"\";\n this.#errorEl.hidden = true;\n }\n\n #emit<T>(type: string, detail: T): void {\n this.dispatchEvent(\n new CustomEvent<T>(type, { detail, bubbles: true, composed: true }),\n );\n }\n}\n\n/* ------------------------------------------------------------------ utils */\n\nfunction normalizeError(err: unknown): GlideError {\n if (\n err &&\n typeof err === \"object\" &&\n \"code\" in err &&\n \"message\" in err\n ) {\n return err as GlideError;\n }\n return {\n code: \"unknown\",\n message: err instanceof Error ? err.message : \"Authentication failed.\",\n cause: err,\n };\n}\n\n/** Build the fingerprint icon via DOM (Trusted-Types friendly, no innerHTML). */\nfunction buildFingerprintIcon(): SVGSVGElement {\n const NS = \"http://www.w3.org/2000/svg\";\n const svg = document.createElementNS(NS, \"svg\");\n svg.setAttribute(\"viewBox\", \"0 0 24 24\");\n svg.setAttribute(\"fill\", \"none\");\n svg.setAttribute(\"stroke\", \"currentColor\");\n svg.setAttribute(\"stroke-width\", \"1.6\");\n svg.setAttribute(\"stroke-linecap\", \"round\");\n svg.setAttribute(\"stroke-linejoin\", \"round\");\n const paths = [\n \"M12 11c0 4 0 6-1 8\",\n \"M8.5 6.8a6 6 0 0 1 9.5 4.9c0 1.3 0 2.4-.2 3.5\",\n \"M5.5 13c0-4 3-7 6.5-7\",\n \"M9 13c0-1.7 1.3-3 3-3s3 1.3 3 3c0 3 0 5-.6 7\",\n \"M12 13v1c0 3.5-.4 5.6-1.3 7.5\",\n ];\n for (const d of paths) {\n const p = document.createElementNS(NS, \"path\");\n p.setAttribute(\"d\", d);\n svg.appendChild(p);\n }\n return svg;\n}\n","/**\n * Conditional UI (passkey autofill) — the \"it just works\" login experience.\n *\n * Unlike the button, conditional UI must start on PAGE LOAD (before any click),\n * so the browser can surface saved passkeys directly in the username field's\n * autofill dropdown. Requirements:\n * - an <input autocomplete=\"username webauthn\"> must exist on the page,\n * - the get() call pends (does not reject) until the user picks a passkey,\n * - drive it with an AbortController so you can cancel/restart.\n *\n * This helper lazy-imports the engine, so importing it costs nothing until you\n * actually call it. On a login page that uses autofill, that import happens on\n * load by design — which is the right tradeoff there.\n */\n\nimport {\n DEFAULT_ENDPOINTS,\n type AuthResult,\n type GlideEndpoints,\n type GlideError,\n} from \"./types.js\";\n\nexport interface ConditionalUIOptions {\n endpoints?: Partial<GlideEndpoints>;\n fetchOptions?: RequestInit;\n username?: string;\n onSuccess: (result: AuthResult) => void;\n onError?: (error: GlideError) => void;\n}\n\nexport interface ConditionalUIHandle {\n /** Cancel the pending conditional request (e.g. before launching a modal). */\n abort(): void;\n /**\n * Resolves true if conditional UI started, false if the browser doesn't\n * support it (caller should fall back to a normal button). Rejects only on\n * programmer error.\n */\n started: Promise<boolean>;\n}\n\nexport function startConditionalUI(\n opts: ConditionalUIOptions,\n): ConditionalUIHandle {\n const controller = new AbortController();\n\n const started = (async (): Promise<boolean> => {\n const engine = await import(\"./webauthn/engine.js\");\n if (!(await engine.isConditionalUIAvailable())) return false;\n\n const endpoints: GlideEndpoints = {\n ...DEFAULT_ENDPOINTS,\n ...opts.endpoints,\n };\n\n try {\n const result = await engine.authenticate(\n {\n mode: \"signin\",\n endpoints,\n username: opts.username,\n fetchOptions: opts.fetchOptions ?? {},\n },\n { mediation: \"conditional\", signal: controller.signal },\n );\n opts.onSuccess(result);\n } catch (err) {\n // Aborting (user clicked elsewhere, or we cancelled) is not an error.\n if ((err as GlideError)?.code !== \"aborted\") {\n opts.onError?.(err as GlideError);\n if (typeof window !== \"undefined\") {\n window.dispatchEvent(\n new CustomEvent<GlideError>(\"glide:error\", {\n detail: err as GlideError,\n bubbles: true,\n composed: true,\n }),\n );\n }\n if (!opts.onError) {\n console.warn(\n \"[Glide] Conditional UI error (set onError to suppress):\",\n err,\n );\n }\n }\n }\n return true;\n })();\n\n return { abort: () => controller.abort(), started };\n}\n","/**\n * @glydi/passkey-core — public, side-effect-free entry.\n *\n * Importing this module does NOT register the custom element (so it's safe in\n * SSR / tree-shaking contexts). To register, either:\n * import \"@glydi/passkey-core/define\"; // auto-registers <biometric-auth-button>\n * or call defineBiometricAuthButton() yourself.\n */\n\nexport { BiometricAuthButton } from \"./biometric-auth-button.js\";\nexport {\n startConditionalUI,\n type ConditionalUIOptions,\n type ConditionalUIHandle,\n} from \"./conditional.js\";\n// base64url codec — exported for consumers doing custom WebAuthn flows.\nexport {\n base64urlToBuffer,\n bufferToBase64url,\n} from \"./webauthn/base64url.js\";\nexport * from \"./types.js\";\n\nimport { BiometricAuthButton } from \"./biometric-auth-button.js\";\n\nexport const TAG_NAME = \"biometric-auth-button\" as const;\n\n/** Idempotently register the element. Safe to call multiple times. */\nexport function defineBiometricAuthButton(\n tagName: string = TAG_NAME,\n): void {\n if (typeof customElements === \"undefined\") return; // SSR guard\n if (!customElements.get(tagName)) {\n customElements.define(tagName, BiometricAuthButton);\n }\n}\n"]}
@@ -0,0 +1,5 @@
1
+ var o=(r,e,t)=>{if(!e.has(r))throw TypeError("Cannot "+t)};var l=(r,e,t)=>(o(r,e,"read from private field"),t?t.call(r):e.get(r)),c=(r,e,t)=>{if(e.has(r))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(r):e.set(r,t);},g=(r,e,t,n)=>(o(r,e,"write to private field"),n?n.call(r,t):e.set(r,t),t);var i=(r,e,t)=>(o(r,e,"access private method"),t);function u(r){let e=r.replace(/-/g,"+").replace(/_/g,"/"),t=e.length%4===0?"":"=".repeat(4-e.length%4),n=atob(e+t),f=new Uint8Array(n.length);for(let a=0;a<n.length;a++)f[a]=n.charCodeAt(a);return f.buffer}function p(r){let e=new Uint8Array(r),t="";for(let n=0;n<e.length;n++)t+=String.fromCharCode(e[n]);return btoa(t).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,"")}
2
+
3
+ export { l as a, c as b, g as c, i as d, u as e, p as f };
4
+ //# sourceMappingURL=out.js.map
5
+ //# sourceMappingURL=chunk-DEJPUCAZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/webauthn/base64url.ts"],"names":["base64urlToBuffer","value","padded","pad","binary","bytes","i","bufferToBase64url","buffer"],"mappings":"qYAYO,SAASA,EAAkBC,EAA4B,CAC5D,IAAMC,EAASD,EAAM,QAAQ,KAAM,GAAG,EAAE,QAAQ,KAAM,GAAG,EACnDE,EAAMD,EAAO,OAAS,IAAM,EAAI,GAAK,IAAI,OAAO,EAAKA,EAAO,OAAS,CAAE,EACvEE,EAAS,KAAKF,EAASC,CAAG,EAC1BE,EAAQ,IAAI,WAAWD,EAAO,MAAM,EAC1C,QAASE,EAAI,EAAGA,EAAIF,EAAO,OAAQE,IACjCD,EAAMC,CAAC,EAAIF,EAAO,WAAWE,CAAC,EAEhC,OAAOD,EAAM,MACf,CAEO,SAASE,EAAkBC,EAA6B,CAC7D,IAAMH,EAAQ,IAAI,WAAWG,CAAM,EAC/BJ,EAAS,GACb,QAASE,EAAI,EAAGA,EAAID,EAAM,OAAQC,IAChCF,GAAU,OAAO,aAAaC,EAAMC,CAAC,CAAE,EAEzC,OAAO,KAAKF,CAAM,EAAE,QAAQ,MAAO,GAAG,EAAE,QAAQ,MAAO,GAAG,EAAE,QAAQ,MAAO,EAAE,CAC/E","sourcesContent":["/**\n * base64url <-> ArrayBuffer helpers.\n *\n * WebAuthn JSON over the wire uses base64url (no padding) for all the binary\n * fields (challenge, user.id, credential ids, attestation/assertion blobs).\n * The browser API wants/returns ArrayBuffers, so we transcode at the boundary.\n *\n * Note: modern browsers ship `PublicKeyCredential.parseCreationOptionsFromJSON`\n * / `toJSON()`, but support is still uneven, so we keep these tiny helpers and\n * feature-detect the native path in the engine.\n */\n\nexport function base64urlToBuffer(value: string): ArrayBuffer {\n const padded = value.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const pad = padded.length % 4 === 0 ? \"\" : \"=\".repeat(4 - (padded.length % 4));\n const binary = atob(padded + pad);\n const bytes = new Uint8Array(binary.length);\n for (let i = 0; i < binary.length; i++) {\n bytes[i] = binary.charCodeAt(i);\n }\n return bytes.buffer;\n}\n\nexport function bufferToBase64url(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n let binary = \"\";\n for (let i = 0; i < bytes.length; i++) {\n binary += String.fromCharCode(bytes[i]!);\n }\n return btoa(binary).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n"]}
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/define.js ADDED
@@ -0,0 +1,6 @@
1
+ import { e } from './chunk-2NSEYZ3C.js';
2
+ import './chunk-DEJPUCAZ.js';
3
+
4
+ e();
5
+ //# sourceMappingURL=out.js.map
6
+ //# sourceMappingURL=define.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/define.ts"],"names":["defineBiometricAuthButton"],"mappings":"oEAMAA,EAA0B","sourcesContent":["/**\n * Side-effectful entry: importing this file registers <biometric-auth-button>.\n * Kept separate from index.ts so the class can be imported without side effects.\n */\nimport { defineBiometricAuthButton } from \"./index.js\";\n\ndefineBiometricAuthButton();\n"]}
@@ -0,0 +1,7 @@
1
+ import { e, f } from './chunk-DEJPUCAZ.js';
2
+
3
+ var r=class extends Error{constructor(t,i,n){super(i),this.name="GlideAuthError",this.code=t,this.cause=n;}};function h(){return typeof window<"u"&&typeof window.PublicKeyCredential=="function"&&typeof navigator.credentials?.create=="function"&&typeof navigator.credentials?.get=="function"}async function K(){try{return await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()}catch{return false}}async function T(){try{return h()&&typeof window.PublicKeyCredential.isConditionalMediationAvailable=="function"&&await window.PublicKeyCredential.isConditionalMediationAvailable()}catch{return false}}async function d(e,t,i){let n;try{n=await fetch(e,{method:"POST",credentials:"include",...i,headers:{"content-type":"application/json",...i.headers??{}},body:JSON.stringify(t)});}catch(s){throw new r("network","Network request failed.",s)}if(!n.ok)throw new r("server_rejected",`Server responded ${n.status}.`,await R(n));return await n.json()}async function R(e){try{return await e.text()}catch{return}}function P(e$1){return {...e$1,challenge:e(e$1.challenge),user:{...e$1.user,id:e(e$1.user.id)},excludeCredentials:(e$1.excludeCredentials??[]).map(t=>({...t,id:e(t.id)}))}}function E(e$1){return {...e$1,challenge:e(e$1.challenge),allowCredentials:(e$1.allowCredentials??[]).map(t=>({...t,id:e(t.id)}))}}function x(e){let t=e.response;return {id:e.id,rawId:f(e.rawId),type:e.type,clientExtensionResults:e.getClientExtensionResults(),response:{clientDataJSON:f(t.clientDataJSON),attestationObject:f(t.attestationObject),transports:typeof t.getTransports=="function"?t.getTransports():[]}}}function O(e){let t=e.response;return {id:e.id,rawId:f(e.rawId),type:e.type,clientExtensionResults:e.getClientExtensionResults(),response:{clientDataJSON:f(t.clientDataJSON),authenticatorData:f(t.authenticatorData),signature:f(t.signature),userHandle:t.userHandle?f(t.userHandle):null}}}function m(e){if(e instanceof r)return e;if(e instanceof DOMException){if(e.name==="NotAllowedError"||e.name==="AbortError")return new r("aborted","The request was cancelled or timed out.",e);if(e.name==="InvalidStateError")return new r("no_credentials","This passkey is already registered.",e);if(e.name==="OperationError")return new r("aborted","Another passkey request was already in progress. Please try again.",e)}return new r("unknown","Unexpected authentication error.",e)}var o=null;function C(){return o?(o.abort(),o=null,true):false}async function b(e){if(!h())throw new r("unsupported","WebAuthn is not available in this browser.");C();let{endpoints:t,fetchOptions:i,username:n}=e,s=await d(t.registerBegin,{username:n},i),l;try{l=await navigator.credentials.create({publicKey:P(s)});}catch(p){throw m(p)}if(!l)throw new r("aborted","No credential was created.");return d(t.registerFinish,x(l),i)}async function g(e,t={}){if(!h())throw new r("unsupported","WebAuthn is not available in this browser.");let i=t.mediation==="conditional",n=null;if(i){n=new AbortController;let c=n,w=t.signal;w&&(w.aborted?c.abort():w.addEventListener("abort",()=>c.abort(),{once:true})),o?.abort(),o=c;}else C();let{endpoints:s,fetchOptions:l,username:p}=e,A=await d(s.authenticateBegin,{username:p},l),y=n?n.signal:t.signal,f;try{f=await navigator.credentials.get({publicKey:E(A),...t.mediation?{mediation:t.mediation}:{},...y?{signal:y}:{}});}catch(c){throw m(c)}finally{i&&o===n&&(o=null);}if(!f)throw new r("aborted","No assertion was produced.");return d(s.authenticateFinish,O(f),l)}async function k(e){if(e.mode==="signup")return b(e);if(e.mode==="signin")return g(e);try{return await g(e)}catch(t){let i=t.code;if(i==="no_credentials"||i==="aborted")return b(e);throw t}}
4
+
5
+ export { g as authenticate, C as cancelConditionalRequest, K as hasPlatformAuthenticator, T as isConditionalUIAvailable, h as isWebAuthnSupported, b as register, k as run };
6
+ //# sourceMappingURL=out.js.map
7
+ //# sourceMappingURL=engine-JXRDZTPB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/webauthn/engine.ts"],"names":["AuthError","code","message","cause","isWebAuthnSupported","hasPlatformAuthenticator","isConditionalUIAvailable","postJson","url","body","fetchOptions","res","safeText","decodeCreationOptions","json","base64urlToBuffer","c","decodeRequestOptions","encodeRegistration","cred","att","bufferToBase64url","encodeAssertion","asr","mapDomException","err","pendingConditional","cancelConditionalRequest","register","config","endpoints","username","optionsJson","credential","authenticate","extras","isConditional","controller","own","caller","signal","assertion","run"],"mappings":"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","sourcesContent":["/**\n * The WebAuthn \"engine\" — the only heavy-ish part of the SDK.\n *\n * This module is loaded via dynamic import() the FIRST time the user actually\n * interacts with the button. Until then, the page ships only the thin custom\n * element shell, keeping the critical-path bundle tiny (great for Lighthouse).\n *\n * It contains zero DOM/UI code: pure transport + native WebAuthn orchestration,\n * which makes it trivial to unit-test and reuse from non-UI contexts.\n */\n\nimport { base64urlToBuffer, bufferToBase64url } from \"./base64url.js\";\nimport type { AuthResult, GlideError, GlideRunConfig } from \"../types.js\";\n\nclass AuthError extends Error implements GlideError {\n code: GlideError[\"code\"];\n cause?: unknown;\n constructor(code: GlideError[\"code\"], message: string, cause?: unknown) {\n super(message);\n this.name = \"GlideAuthError\";\n this.code = code;\n this.cause = cause;\n }\n}\n\n/** Feature detection — cheap, safe to call eagerly. */\nexport function isWebAuthnSupported(): boolean {\n return (\n typeof window !== \"undefined\" &&\n typeof window.PublicKeyCredential === \"function\" &&\n typeof navigator.credentials?.create === \"function\" &&\n typeof navigator.credentials?.get === \"function\"\n );\n}\n\n/** Is a platform authenticator (Touch ID / Face ID / Windows Hello) present? */\nexport async function hasPlatformAuthenticator(): Promise<boolean> {\n try {\n return await window.PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable();\n } catch {\n return false;\n }\n}\n\n/** Is Conditional UI (passkey autofill) usable in this browser? */\nexport async function isConditionalUIAvailable(): Promise<boolean> {\n try {\n return (\n isWebAuthnSupported() &&\n typeof window.PublicKeyCredential.isConditionalMediationAvailable ===\n \"function\" &&\n (await window.PublicKeyCredential.isConditionalMediationAvailable())\n );\n } catch {\n return false;\n }\n}\n\nasync function postJson<T>(\n url: string,\n body: unknown,\n fetchOptions: RequestInit,\n): Promise<T> {\n let res: Response;\n try {\n res = await fetch(url, {\n method: \"POST\",\n // credentials: \"include\" lets the HttpOnly refresh/session cookie ride\n // along. Caller can override via fetchOptions.\n credentials: \"include\",\n ...fetchOptions,\n headers: {\n \"content-type\": \"application/json\",\n ...(fetchOptions.headers ?? {}),\n },\n body: JSON.stringify(body),\n });\n } catch (cause) {\n throw new AuthError(\"network\", \"Network request failed.\", cause);\n }\n if (!res.ok) {\n throw new AuthError(\n \"server_rejected\",\n `Server responded ${res.status}.`,\n await safeText(res),\n );\n }\n return (await res.json()) as T;\n}\n\nasync function safeText(res: Response): Promise<string | undefined> {\n try {\n return await res.text();\n } catch {\n return undefined;\n }\n}\n\n/**\n * Transcode a server-issued creation-options JSON (base64url fields) into the\n * structured `PublicKeyCredentialCreationOptions` the browser expects.\n */\nfunction decodeCreationOptions(\n json: any,\n): PublicKeyCredentialCreationOptions {\n return {\n ...json,\n challenge: base64urlToBuffer(json.challenge),\n user: { ...json.user, id: base64urlToBuffer(json.user.id) },\n excludeCredentials: (json.excludeCredentials ?? []).map((c: any) => ({\n ...c,\n id: base64urlToBuffer(c.id),\n })),\n };\n}\n\nfunction decodeRequestOptions(json: any): PublicKeyCredentialRequestOptions {\n return {\n ...json,\n challenge: base64urlToBuffer(json.challenge),\n allowCredentials: (json.allowCredentials ?? []).map((c: any) => ({\n ...c,\n id: base64urlToBuffer(c.id),\n })),\n };\n}\n\n/** Serialize a registration credential back to base64url JSON for the server. */\nfunction encodeRegistration(cred: PublicKeyCredential) {\n const att = cred.response as AuthenticatorAttestationResponse;\n return {\n id: cred.id,\n rawId: bufferToBase64url(cred.rawId),\n type: cred.type,\n clientExtensionResults: cred.getClientExtensionResults(),\n response: {\n clientDataJSON: bufferToBase64url(att.clientDataJSON),\n attestationObject: bufferToBase64url(att.attestationObject),\n transports:\n typeof att.getTransports === \"function\" ? att.getTransports() : [],\n },\n };\n}\n\n/** Serialize an authentication assertion back to base64url JSON. */\nfunction encodeAssertion(cred: PublicKeyCredential) {\n const asr = cred.response as AuthenticatorAssertionResponse;\n return {\n id: cred.id,\n rawId: bufferToBase64url(cred.rawId),\n type: cred.type,\n clientExtensionResults: cred.getClientExtensionResults(),\n response: {\n clientDataJSON: bufferToBase64url(asr.clientDataJSON),\n authenticatorData: bufferToBase64url(asr.authenticatorData),\n signature: bufferToBase64url(asr.signature),\n userHandle: asr.userHandle ? bufferToBase64url(asr.userHandle) : null,\n },\n };\n}\n\nfunction mapDomException(err: unknown): AuthError {\n if (err instanceof AuthError) return err;\n if (err instanceof DOMException) {\n // NotAllowedError = user dismissed/timed out; AbortError = we (or the page)\n // aborted the request, e.g. cancelling a pending conditional-UI get().\n if (err.name === \"NotAllowedError\" || err.name === \"AbortError\") {\n return new AuthError(\"aborted\", \"The request was cancelled or timed out.\", err);\n }\n if (err.name === \"InvalidStateError\") {\n return new AuthError(\"no_credentials\", \"This passkey is already registered.\", err);\n }\n // OperationError = another WebAuthn call hasn't released the slot yet\n // (classically conditional-UI autofill: \"A request is already pending.\").\n // We cancel pending conditional requests before a modal ceremony, so this\n // should not happen; map it to a soft, retryable abort as defense-in-depth\n // rather than the scary generic \"unknown\" error.\n if (err.name === \"OperationError\") {\n return new AuthError(\n \"aborted\",\n \"Another passkey request was already in progress. Please try again.\",\n err,\n );\n }\n }\n return new AuthError(\"unknown\", \"Unexpected authentication error.\", err);\n}\n\n// ---------------------------------------------------------------------------\n// Single-flight coordination\n// ---------------------------------------------------------------------------\n// The platform allows only ONE navigator.credentials.get() in flight at a time.\n// Conditional UI (passkey autofill) deliberately holds one open for the page's\n// lifetime, so a modal ceremony MUST cancel it first — otherwise the modal\n// get()/create() rejects with OperationError \"A request is already pending.\"\nlet pendingConditional: AbortController | null = null;\n\n/**\n * Abort the in-flight conditional-UI request, if any, so a modal ceremony can\n * take the single WebAuthn slot. Modal `authenticate()`/`register()` call this\n * before touching `navigator.credentials`. Safe to call when nothing is pending.\n * Returns true if it actually aborted a request.\n */\nexport function cancelConditionalRequest(): boolean {\n if (!pendingConditional) return false;\n pendingConditional.abort();\n pendingConditional = null;\n return true;\n}\n\nexport async function register(config: GlideRunConfig): Promise<AuthResult> {\n if (!isWebAuthnSupported()) {\n throw new AuthError(\"unsupported\", \"WebAuthn is not available in this browser.\");\n }\n // A pending conditional-UI get() holds the only WebAuthn slot; release it\n // before create() so we don't collide with autofill on a login page.\n cancelConditionalRequest();\n const { endpoints, fetchOptions, username } = config;\n const optionsJson = await postJson<any>(\n endpoints.registerBegin,\n { username },\n fetchOptions,\n );\n let credential: PublicKeyCredential | null;\n try {\n credential = (await navigator.credentials.create({\n publicKey: decodeCreationOptions(optionsJson),\n })) as PublicKeyCredential | null;\n } catch (err) {\n throw mapDomException(err);\n }\n if (!credential) throw new AuthError(\"aborted\", \"No credential was created.\");\n return postJson<AuthResult>(\n endpoints.registerFinish,\n encodeRegistration(credential),\n fetchOptions,\n );\n}\n\nexport interface AuthenticateExtras {\n /** \"conditional\" powers passkey autofill; omit for a modal prompt. */\n mediation?: CredentialMediationRequirement;\n /** Abort the pending get() — required to cancel a conditional request. */\n signal?: AbortSignal;\n}\n\nexport async function authenticate(\n config: GlideRunConfig,\n extras: AuthenticateExtras = {},\n): Promise<AuthResult> {\n if (!isWebAuthnSupported()) {\n throw new AuthError(\"unsupported\", \"WebAuthn is not available in this browser.\");\n }\n\n const isConditional = extras.mediation === \"conditional\";\n\n // Single-flight coordination (see cancelConditionalRequest):\n // - A conditional request registers itself (under an internal controller we\n // can abort) so a later modal ceremony can cancel it and reclaim the slot.\n // We chain the caller's signal (startConditionalUI's abort on unmount) onto\n // ours so either source can cancel.\n // - A modal request first cancels any pending conditional request. Doing it\n // here, before the begin round-trip, gives the browser ample time to\n // release the WebAuthn slot before we call get().\n let controller: AbortController | null = null;\n if (isConditional) {\n controller = new AbortController();\n const own = controller;\n const caller = extras.signal;\n if (caller) {\n if (caller.aborted) own.abort();\n else caller.addEventListener(\"abort\", () => own.abort(), { once: true });\n }\n pendingConditional?.abort();\n pendingConditional = own;\n } else {\n cancelConditionalRequest();\n }\n\n const { endpoints, fetchOptions, username } = config;\n const optionsJson = await postJson<any>(\n endpoints.authenticateBegin,\n { username },\n fetchOptions,\n );\n const signal = controller ? controller.signal : extras.signal;\n let assertion: PublicKeyCredential | null;\n try {\n assertion = (await navigator.credentials.get({\n publicKey: decodeRequestOptions(optionsJson),\n ...(extras.mediation ? { mediation: extras.mediation } : {}),\n ...(signal ? { signal } : {}),\n })) as PublicKeyCredential | null;\n } catch (err) {\n throw mapDomException(err);\n } finally {\n // Deregister ourselves if we're still the active conditional request (a\n // modal ceremony may have already superseded us via cancelConditionalRequest).\n if (isConditional && pendingConditional === controller) pendingConditional = null;\n }\n if (!assertion) throw new AuthError(\"aborted\", \"No assertion was produced.\");\n return postJson<AuthResult>(\n endpoints.authenticateFinish,\n encodeAssertion(assertion),\n fetchOptions,\n );\n}\n\n/**\n * \"auto\" mode: try discoverable-credential authentication first; if the user\n * has no passkey yet (no_credentials / aborted), fall back to registration.\n * This powers the single-button \"just works\" experience.\n */\nexport async function run(config: GlideRunConfig): Promise<AuthResult> {\n if (config.mode === \"signup\") return register(config);\n if (config.mode === \"signin\") return authenticate(config);\n try {\n return await authenticate(config);\n } catch (err) {\n const code = (err as GlideError).code;\n // A first-time visitor has nothing to sign in with: the attempt either\n // reports no usable passkey (no_credentials) or is dismissed/empty and\n // surfaces as aborted (an empty discoverable get() rejects with\n // NotAllowedError). Either way, \"auto\" means onboarding — register instead.\n if (code === \"no_credentials\" || code === \"aborted\") return register(config);\n throw err;\n }\n}\n"]}
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Public types for the Glide passkey core.
3
+ * Kept dependency-free so the bundle stays tiny and tree-shakeable.
4
+ */
5
+ type AuthMode = "auto" | "signin" | "signup";
6
+ type AuthPhase = "idle" | "priming" | "unsupported" | "loading" | "authenticating" | "success" | "error";
7
+ interface GlideUser {
8
+ /** Stable opaque user id returned by your backend. */
9
+ id: string;
10
+ /** Optional display fields echoed back from the server. */
11
+ name?: string;
12
+ email?: string;
13
+ }
14
+ interface AuthResult {
15
+ user: GlideUser;
16
+ /**
17
+ * Short-lived access token, if your backend returns one in the JSON body.
18
+ * SECURITY: this is handed to you in memory only. Never persist it to
19
+ * localStorage/sessionStorage. See SECURITY.md.
20
+ */
21
+ accessToken?: string;
22
+ }
23
+ interface GlideError {
24
+ code: "unsupported" | "aborted" | "no_credentials" | "network" | "server_rejected" | "unknown";
25
+ message: string;
26
+ cause?: unknown;
27
+ }
28
+ /**
29
+ * Endpoints the component talks to. All are POST and return JSON.
30
+ * Defaults use hyphenated single-segment paths (e.g. `/api/passkey/register-begin`)
31
+ * matching the demo's `[action]` route handler; override per-deployment as needed.
32
+ */
33
+ interface GlideEndpoints {
34
+ /** Begin registration → returns PublicKeyCredentialCreationOptions (JSON). */
35
+ registerBegin: string;
36
+ /** Finish registration → returns { user, accessToken? }. */
37
+ registerFinish: string;
38
+ /** Begin authentication → returns PublicKeyCredentialRequestOptions (JSON). */
39
+ authenticateBegin: string;
40
+ /** Finish authentication → returns { user, accessToken? }. */
41
+ authenticateFinish: string;
42
+ }
43
+ declare const DEFAULT_ENDPOINTS: GlideEndpoints;
44
+ /** Resolved configuration the engine receives. */
45
+ interface GlideRunConfig {
46
+ mode: AuthMode;
47
+ endpoints: GlideEndpoints;
48
+ /** Optional hint forwarded to the begin endpoints (e.g. an email). */
49
+ username?: string | undefined;
50
+ /** Forwarded to fetch; cookies must be included for the refresh-token flow. */
51
+ fetchOptions: RequestInit;
52
+ }
53
+ /** Detail payload for the `glide:*` CustomEvents the component dispatches. */
54
+ interface GlideEventMap {
55
+ "glide:success": AuthResult;
56
+ "glide:error": GlideError;
57
+ "glide:phasechange": {
58
+ phase: AuthPhase;
59
+ };
60
+ }
61
+
62
+ /**
63
+ * <biometric-auth-button> — the framework-agnostic core of Glide.
64
+ *
65
+ * Responsibilities (UI only — all WebAuthn logic lives in the lazy engine):
66
+ * - Render a neutral, headless button inside an (open) Shadow DOM.
67
+ * - Expose a clean theming API: CSS Custom Properties + CSS ::part()s.
68
+ * - Lazy-load the WebAuthn engine on first interaction (keeps bundle tiny).
69
+ * - Drive the auth flow and emit `glide:*` CustomEvents + reflect phase.
70
+ *
71
+ * Public attributes:
72
+ * mode="auto|signin|signup" username="..." label="..."
73
+ * register-begin / register-finish / authenticate-begin / authenticate-finish
74
+ * disabled
75
+ * prime prime-title="..." prime-body="..." prime-continue="..." prime-cancel="..."
76
+ * fallback-href="..." fallback-label="..."
77
+ *
78
+ * Public JS properties: mode, username, label, endpoints, fetchOptions, phase
79
+ *
80
+ * Events: glide:success, glide:error, glide:phasechange
81
+ *
82
+ * CSS parts: button, icon, label, spinner, error,
83
+ * prime, prime-title, prime-body, prime-continue, prime-cancel,
84
+ * fallback
85
+ *
86
+ * Priming: when the `prime` attribute is present, the first tap shows a small,
87
+ * themeable explainer ("You'll see a Face ID popup — that's how you sign in")
88
+ * BEFORE the native OS sheet. This is the #1 conversion lever for non-technical
89
+ * / older users who otherwise bounce at the unexplained system prompt.
90
+ */
91
+
92
+ declare const HTMLElementBase: typeof HTMLElement;
93
+ declare class BiometricAuthButton extends HTMLElementBase {
94
+ #private;
95
+ static get observedAttributes(): readonly string[];
96
+ /** Extra fetch init (e.g. custom headers) — set via JS property only. */
97
+ fetchOptions: RequestInit;
98
+ constructor();
99
+ get mode(): AuthMode;
100
+ set mode(v: AuthMode);
101
+ get username(): string | undefined;
102
+ set username(v: string | undefined);
103
+ get label(): string;
104
+ set label(v: string);
105
+ /** Whether to show the priming explainer before the ceremony. */
106
+ get prime(): boolean;
107
+ set prime(v: boolean);
108
+ get primeTitle(): string;
109
+ get primeBody(): string;
110
+ get primeContinueLabel(): string;
111
+ get primeCancelLabel(): string;
112
+ /** Where the recovery fallback link points (e.g. your magic-link page). */
113
+ get fallbackHref(): string | null;
114
+ get fallbackLabel(): string;
115
+ /** Read merged endpoints (defaults ← attributes ← JS overrides). */
116
+ get endpoints(): GlideEndpoints;
117
+ set endpoints(v: Partial<GlideEndpoints>);
118
+ get phase(): AuthPhase;
119
+ connectedCallback(): void;
120
+ disconnectedCallback(): void;
121
+ attributeChangedCallback(name: string): void;
122
+ }
123
+
124
+ /**
125
+ * Conditional UI (passkey autofill) — the "it just works" login experience.
126
+ *
127
+ * Unlike the button, conditional UI must start on PAGE LOAD (before any click),
128
+ * so the browser can surface saved passkeys directly in the username field's
129
+ * autofill dropdown. Requirements:
130
+ * - an <input autocomplete="username webauthn"> must exist on the page,
131
+ * - the get() call pends (does not reject) until the user picks a passkey,
132
+ * - drive it with an AbortController so you can cancel/restart.
133
+ *
134
+ * This helper lazy-imports the engine, so importing it costs nothing until you
135
+ * actually call it. On a login page that uses autofill, that import happens on
136
+ * load by design — which is the right tradeoff there.
137
+ */
138
+
139
+ interface ConditionalUIOptions {
140
+ endpoints?: Partial<GlideEndpoints>;
141
+ fetchOptions?: RequestInit;
142
+ username?: string;
143
+ onSuccess: (result: AuthResult) => void;
144
+ onError?: (error: GlideError) => void;
145
+ }
146
+ interface ConditionalUIHandle {
147
+ /** Cancel the pending conditional request (e.g. before launching a modal). */
148
+ abort(): void;
149
+ /**
150
+ * Resolves true if conditional UI started, false if the browser doesn't
151
+ * support it (caller should fall back to a normal button). Rejects only on
152
+ * programmer error.
153
+ */
154
+ started: Promise<boolean>;
155
+ }
156
+ declare function startConditionalUI(opts: ConditionalUIOptions): ConditionalUIHandle;
157
+
158
+ /**
159
+ * base64url <-> ArrayBuffer helpers.
160
+ *
161
+ * WebAuthn JSON over the wire uses base64url (no padding) for all the binary
162
+ * fields (challenge, user.id, credential ids, attestation/assertion blobs).
163
+ * The browser API wants/returns ArrayBuffers, so we transcode at the boundary.
164
+ *
165
+ * Note: modern browsers ship `PublicKeyCredential.parseCreationOptionsFromJSON`
166
+ * / `toJSON()`, but support is still uneven, so we keep these tiny helpers and
167
+ * feature-detect the native path in the engine.
168
+ */
169
+ declare function base64urlToBuffer(value: string): ArrayBuffer;
170
+ declare function bufferToBase64url(buffer: ArrayBuffer): string;
171
+
172
+ /**
173
+ * @glydi/passkey-core — public, side-effect-free entry.
174
+ *
175
+ * Importing this module does NOT register the custom element (so it's safe in
176
+ * SSR / tree-shaking contexts). To register, either:
177
+ * import "@glydi/passkey-core/define"; // auto-registers <biometric-auth-button>
178
+ * or call defineBiometricAuthButton() yourself.
179
+ */
180
+
181
+ declare const TAG_NAME: "biometric-auth-button";
182
+ /** Idempotently register the element. Safe to call multiple times. */
183
+ declare function defineBiometricAuthButton(tagName?: string): void;
184
+
185
+ export { type AuthMode, type AuthPhase, type AuthResult, BiometricAuthButton, type ConditionalUIHandle, type ConditionalUIOptions, DEFAULT_ENDPOINTS, type GlideEndpoints, type GlideError, type GlideEventMap, type GlideRunConfig, type GlideUser, TAG_NAME, base64urlToBuffer, bufferToBase64url, defineBiometricAuthButton, startConditionalUI };
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ export { b as BiometricAuthButton, a as DEFAULT_ENDPOINTS, d as TAG_NAME, e as defineBiometricAuthButton, c as startConditionalUI } from './chunk-2NSEYZ3C.js';
2
+ export { e as base64urlToBuffer, f as bufferToBase64url } from './chunk-DEJPUCAZ.js';
3
+ //# sourceMappingURL=out.js.map
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@glydi/passkey-core",
3
+ "version": "0.1.0",
4
+ "publishConfig": {
5
+ "access": "public"
6
+ },
7
+ "description": "Framework-agnostic Passkeys/WebAuthn Web Component. Tiny, Shadow-DOM-isolated, themeable via CSS Parts.",
8
+ "type": "module",
9
+ "sideEffects": [
10
+ "./dist/define.js",
11
+ "./src/define.ts"
12
+ ],
13
+ "exports": {
14
+ ".": {
15
+ "types": "./dist/index.d.ts",
16
+ "import": "./dist/index.js"
17
+ },
18
+ "./define": {
19
+ "types": "./dist/define.d.ts",
20
+ "import": "./dist/define.js"
21
+ }
22
+ },
23
+ "main": "./dist/index.js",
24
+ "module": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "keywords": [
30
+ "passkey",
31
+ "webauthn",
32
+ "web-component",
33
+ "authentication",
34
+ "biometric",
35
+ "fido2"
36
+ ],
37
+ "license": "MIT",
38
+ "devDependencies": {
39
+ "tsup": "^8.0.0",
40
+ "typescript": "^5.4.0"
41
+ },
42
+ "scripts": {
43
+ "build": "tsup",
44
+ "dev": "tsup --watch",
45
+ "typecheck": "tsc --noEmit",
46
+ "clean": "rm -rf dist .turbo"
47
+ }
48
+ }