@glydi/passkey-svelte 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 +144 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +67 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
- package/src/PasskeyButton.svelte +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# @glydi/passkey-svelte
|
|
2
|
+
|
|
3
|
+
Thin Svelte adapter for the Glide passkey Web Component.
|
|
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-svelte": "file:../glide/dist-packs/glydi-passkey-svelte-0.1.0.tgz",
|
|
15
|
+
"@glydi/passkey-core": "file:../glide/dist-packs/glydi-passkey-core-0.1.0.tgz"
|
|
16
|
+
},
|
|
17
|
+
"pnpm": {
|
|
18
|
+
"overrides": {
|
|
19
|
+
"@glydi/passkey-core": "file:../glide/dist-packs/glydi-passkey-core-0.1.0.tgz"
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The `pnpm.overrides` entry for `@glydi/passkey-core` prevents pnpm from trying to fetch
|
|
26
|
+
the transitive peer from the npm registry. See [docs/DISTRIBUTION.md](../../docs/DISTRIBUTION.md)
|
|
27
|
+
for full tarball and `pnpm link` instructions, including how to produce the tarballs.
|
|
28
|
+
|
|
29
|
+
> **Forthcoming:** `npm install @glydi/passkey-svelte` will be the public form once the
|
|
30
|
+
> package is published. It is not yet available on npm.
|
|
31
|
+
|
|
32
|
+
## Minimal Usage
|
|
33
|
+
|
|
34
|
+
This package has **two entry points**:
|
|
35
|
+
|
|
36
|
+
| Entry point | Import path | What you get |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| `"."` (main) | `@glydi/passkey-svelte` | `createPasskeyAuth` headless action + types |
|
|
39
|
+
| `"./PasskeyButton.svelte"` | `@glydi/passkey-svelte/PasskeyButton.svelte` | Ready-made Svelte component |
|
|
40
|
+
|
|
41
|
+
> **Note:** `PasskeyButton.svelte` is **not** exported from the `"."` entry — it lives at the
|
|
42
|
+
> separate `./PasskeyButton.svelte` subpath and ships as source so your Svelte compiler handles it.
|
|
43
|
+
|
|
44
|
+
### 1. Component: `PasskeyButton.svelte`
|
|
45
|
+
|
|
46
|
+
Import from the `./PasskeyButton.svelte` subpath:
|
|
47
|
+
|
|
48
|
+
```svelte
|
|
49
|
+
<!-- Source: packages/svelte/src/PasskeyButton.svelte -->
|
|
50
|
+
<script>
|
|
51
|
+
import PasskeyButton from "@glydi/passkey-svelte/PasskeyButton.svelte";
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<PasskeyButton
|
|
55
|
+
mode="signup"
|
|
56
|
+
label="Register passkey"
|
|
57
|
+
onSuccess={(r) => console.log(r.user.id)}
|
|
58
|
+
onError={(e) => console.warn(e.code)}
|
|
59
|
+
/>
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 2. Headless: `createPasskeyAuth` action
|
|
63
|
+
|
|
64
|
+
For custom UI, use the headless `createPasskeyAuth` action from the main entry point.
|
|
65
|
+
It returns a `PasskeyAuth` store with a Svelte action (`use:auth.attach`) and reactive
|
|
66
|
+
state (`$auth`):
|
|
67
|
+
|
|
68
|
+
```svelte
|
|
69
|
+
<!-- Source: packages/svelte/src/usePasskeyAuth.ts -->
|
|
70
|
+
<script>
|
|
71
|
+
import { createPasskeyAuth } from "@glydi/passkey-svelte";
|
|
72
|
+
const auth = createPasskeyAuth({ mode: "auto" });
|
|
73
|
+
$: ({ phase, isPending, error, user } = $auth);
|
|
74
|
+
</script>
|
|
75
|
+
|
|
76
|
+
<biometric-auth-button use:auth.attach mode="auto" />
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## API
|
|
80
|
+
|
|
81
|
+
### `PasskeyButton.svelte`
|
|
82
|
+
|
|
83
|
+
A ready-made Svelte component wrapping `<biometric-auth-button>`. Import from
|
|
84
|
+
`@glydi/passkey-svelte/PasskeyButton.svelte` (the `./PasskeyButton.svelte` subpath).
|
|
85
|
+
The component ships as **Svelte source** — your project's Svelte compiler handles it.
|
|
86
|
+
Any extra attributes are forwarded via `{...$$restProps}` to `<biometric-auth-button>`.
|
|
87
|
+
|
|
88
|
+
**Props:**
|
|
89
|
+
|
|
90
|
+
| Prop | Type | Default | Description |
|
|
91
|
+
|------|------|---------|-------------|
|
|
92
|
+
| `mode` | `"auto" \| "signin" \| "signup"` | `"auto"` | Auth mode. See AuthMode callout below. |
|
|
93
|
+
| `username` | `string \| undefined` | `undefined` | Username hint for registration/conditional UI. |
|
|
94
|
+
| `label` | `string \| undefined` | `undefined` | Button label text. |
|
|
95
|
+
| `endpoints` | `GlideEndpoints \| undefined` | `undefined` | Override API endpoint paths. |
|
|
96
|
+
| `fetchOptions` | `RequestInit \| undefined` | `undefined` | Merged into every `fetch` call. |
|
|
97
|
+
| `onSuccess` | `(result: AuthResult) => void \| undefined` | `undefined` | Called after successful authentication. |
|
|
98
|
+
| `onError` | `(error: GlideError) => void \| undefined` | `undefined` | Called when authentication fails. |
|
|
99
|
+
| `onPhaseChange` | `(detail: { phase: AuthPhase }) => void \| undefined` | `undefined` | Called on every phase transition. |
|
|
100
|
+
|
|
101
|
+
> **Valid `mode` values are `"auto"`, `"signin"`, and `"signup"` only.** Using `"register"`
|
|
102
|
+
> or `"authenticate"` is invalid and will silently no-op.
|
|
103
|
+
|
|
104
|
+
### `createPasskeyAuth(options?)`
|
|
105
|
+
|
|
106
|
+
Headless action factory. Returns a `PasskeyAuth` object:
|
|
107
|
+
|
|
108
|
+
| Member | Type | Description |
|
|
109
|
+
|--------|------|-------------|
|
|
110
|
+
| `subscribe` | `Readable<PasskeyAuthState>["subscribe"]` | Svelte store protocol — use `$auth` in markup. |
|
|
111
|
+
| `attach(node)` | `(node: HTMLElement) => { destroy(): void }` | Svelte action — apply as `use:auth.attach`. |
|
|
112
|
+
| `trigger()` | `() => void` | Imperatively start auth (same as clicking the button). |
|
|
113
|
+
| `reset()` | `() => void` | Reset phase and error back to `"idle"`. |
|
|
114
|
+
|
|
115
|
+
**`PasskeyAuthState`** (readable via `$auth`):
|
|
116
|
+
|
|
117
|
+
| Field | Type | Description |
|
|
118
|
+
|-------|------|-------------|
|
|
119
|
+
| `phase` | `AuthPhase` | Current auth phase (`"idle"`, `"loading"`, `"success"`, etc.). |
|
|
120
|
+
| `isPending` | `boolean` | `true` during `"loading"` or `"authenticating"`. |
|
|
121
|
+
| `isUnsupported` | `boolean` | `true` when `phase === "unsupported"`. |
|
|
122
|
+
| `error` | `GlideError \| null` | Last error, or `null`. |
|
|
123
|
+
| `user` | `GlideUser \| null` | Authenticated user after success, or `null`. |
|
|
124
|
+
|
|
125
|
+
**`UsePasskeyAuthOptions`** (all optional):
|
|
126
|
+
|
|
127
|
+
| Option | Type | Description |
|
|
128
|
+
|--------|------|-------------|
|
|
129
|
+
| `mode` | `"auto" \| "signin" \| "signup"` | Auth mode. Default `"auto"`. |
|
|
130
|
+
| `username` | `string` | Username hint. |
|
|
131
|
+
| `endpoints` | `Partial<GlideEndpoints>` | Override API endpoint paths. |
|
|
132
|
+
| `fetchOptions` | `RequestInit` | Merged into every `fetch` call. |
|
|
133
|
+
| `onSuccess` | `(result: AuthResult) => void` | Success callback. |
|
|
134
|
+
| `onError` | `(error: GlideError) => void` | Error callback. |
|
|
135
|
+
|
|
136
|
+
### Re-exported types
|
|
137
|
+
|
|
138
|
+
`AuthMode`, `AuthPhase`, `AuthResult`, `GlideEndpoints`, `GlideError`, `GlideUser`
|
|
139
|
+
|
|
140
|
+
## Links
|
|
141
|
+
|
|
142
|
+
- [Root README](../../README.md) — architecture overview
|
|
143
|
+
- [Quickstart](../../docs/QUICKSTART.md) — full Next.js App Router integration walkthrough
|
|
144
|
+
- [Distribution guide](../../docs/DISTRIBUTION.md) — tarball and pnpm link install details
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Readable } from 'svelte/store';
|
|
2
|
+
import { AuthPhase, GlideError, GlideUser, GlideEndpoints, AuthResult } from '@glydi/passkey-core';
|
|
3
|
+
export { AuthMode, AuthPhase, AuthResult, GlideEndpoints, GlideError, GlideUser } from '@glydi/passkey-core';
|
|
4
|
+
|
|
5
|
+
interface UsePasskeyAuthOptions {
|
|
6
|
+
mode?: "auto" | "signin" | "signup";
|
|
7
|
+
username?: string;
|
|
8
|
+
endpoints?: Partial<GlideEndpoints>;
|
|
9
|
+
/** Forwarded to fetch (e.g. custom headers). credentials:"include" is default. */
|
|
10
|
+
fetchOptions?: RequestInit;
|
|
11
|
+
onSuccess?: (result: AuthResult) => void;
|
|
12
|
+
onError?: (error: GlideError) => void;
|
|
13
|
+
}
|
|
14
|
+
/** Reactive snapshot of the auth flow, exposed as a Svelte readable store. */
|
|
15
|
+
interface PasskeyAuthState {
|
|
16
|
+
phase: AuthPhase;
|
|
17
|
+
/** True while the ceremony is in flight. */
|
|
18
|
+
isPending: boolean;
|
|
19
|
+
/** True once a feature-detect failure is reflected by the element. */
|
|
20
|
+
isUnsupported: boolean;
|
|
21
|
+
error: GlideError | null;
|
|
22
|
+
user: GlideUser | null;
|
|
23
|
+
}
|
|
24
|
+
/** Svelte action: cleanup contract returned from `attach`. */
|
|
25
|
+
interface ActionReturn {
|
|
26
|
+
destroy(): void;
|
|
27
|
+
}
|
|
28
|
+
interface PasskeyAuth {
|
|
29
|
+
/**
|
|
30
|
+
* Subscribe to the reactive auth state. Use directly in markup with `$`:
|
|
31
|
+
* const auth = createPasskeyAuth();
|
|
32
|
+
* $: ({ phase, isPending, error, user } = $auth);
|
|
33
|
+
*/
|
|
34
|
+
subscribe: Readable<PasskeyAuthState>["subscribe"];
|
|
35
|
+
/**
|
|
36
|
+
* Svelte action — attach to the element:
|
|
37
|
+
* <biometric-auth-button use:auth.attach />
|
|
38
|
+
* Forwards object props and bridges the `glide:*` CustomEvents into the store.
|
|
39
|
+
*/
|
|
40
|
+
attach(node: HTMLElement): ActionReturn;
|
|
41
|
+
/** Imperatively start auth (same as a user click). */
|
|
42
|
+
trigger(): void;
|
|
43
|
+
/** Clear the error/user without touching the element. */
|
|
44
|
+
reset(): void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Headless Svelte helper wrapping the <biometric-auth-button> Web Component.
|
|
48
|
+
*
|
|
49
|
+
* It owns nothing about rendering — you bring the element (via <PasskeyButton>
|
|
50
|
+
* or a raw tag) and wire `use:auth.attach`. The helper subscribes to the
|
|
51
|
+
* element's `glide:*` CustomEvents and projects them into a Svelte store +
|
|
52
|
+
* callbacks. Mirrors the React `usePasskeyAuth` hook semantics.
|
|
53
|
+
*/
|
|
54
|
+
declare function createPasskeyAuth(options?: UsePasskeyAuthOptions): PasskeyAuth;
|
|
55
|
+
|
|
56
|
+
export { type PasskeyAuth, type PasskeyAuthState, type UsePasskeyAuthOptions, createPasskeyAuth };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { writable } from 'svelte/store';
|
|
2
|
+
|
|
3
|
+
// src/usePasskeyAuth.ts
|
|
4
|
+
var INITIAL = {
|
|
5
|
+
phase: "idle",
|
|
6
|
+
isPending: false,
|
|
7
|
+
isUnsupported: false,
|
|
8
|
+
error: null,
|
|
9
|
+
user: null
|
|
10
|
+
};
|
|
11
|
+
function derive(phase) {
|
|
12
|
+
return {
|
|
13
|
+
isPending: phase === "loading" || phase === "authenticating",
|
|
14
|
+
isUnsupported: phase === "unsupported"
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function createPasskeyAuth(options = {}) {
|
|
18
|
+
const store = writable(INITIAL);
|
|
19
|
+
let current = null;
|
|
20
|
+
function attach(node) {
|
|
21
|
+
const el = node;
|
|
22
|
+
current = el;
|
|
23
|
+
if (options.endpoints)
|
|
24
|
+
el.endpoints = options.endpoints;
|
|
25
|
+
if (options.fetchOptions)
|
|
26
|
+
el.fetchOptions = options.fetchOptions;
|
|
27
|
+
const onSuccess = (e) => {
|
|
28
|
+
const detail = e.detail;
|
|
29
|
+
store.update((s) => ({ ...s, user: detail.user, error: null }));
|
|
30
|
+
options.onSuccess?.(detail);
|
|
31
|
+
};
|
|
32
|
+
const onError = (e) => {
|
|
33
|
+
const detail = e.detail;
|
|
34
|
+
store.update((s) => ({ ...s, error: detail }));
|
|
35
|
+
options.onError?.(detail);
|
|
36
|
+
};
|
|
37
|
+
const onPhase = (e) => {
|
|
38
|
+
const phase2 = e.detail.phase;
|
|
39
|
+
store.update((s) => ({ ...s, phase: phase2, ...derive(phase2) }));
|
|
40
|
+
};
|
|
41
|
+
el.addEventListener("glide:success", onSuccess);
|
|
42
|
+
el.addEventListener("glide:error", onError);
|
|
43
|
+
el.addEventListener("glide:phasechange", onPhase);
|
|
44
|
+
const phase = el.phase;
|
|
45
|
+
store.update((s) => ({ ...s, phase, ...derive(phase) }));
|
|
46
|
+
return {
|
|
47
|
+
destroy() {
|
|
48
|
+
el.removeEventListener("glide:success", onSuccess);
|
|
49
|
+
el.removeEventListener("glide:error", onError);
|
|
50
|
+
el.removeEventListener("glide:phasechange", onPhase);
|
|
51
|
+
if (current === el)
|
|
52
|
+
current = null;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function trigger() {
|
|
57
|
+
current?.click();
|
|
58
|
+
}
|
|
59
|
+
function reset() {
|
|
60
|
+
store.update((s) => ({ ...s, error: null, user: null }));
|
|
61
|
+
}
|
|
62
|
+
return { subscribe: store.subscribe, attach, trigger, reset };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { createPasskeyAuth };
|
|
66
|
+
//# sourceMappingURL=out.js.map
|
|
67
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/usePasskeyAuth.ts"],"names":["phase"],"mappings":";AAAA,SAAS,gBAA+B;AA6DxC,IAAM,UAA4B;AAAA,EAChC,OAAO;AAAA,EACP,WAAW;AAAA,EACX,eAAe;AAAA,EACf,OAAO;AAAA,EACP,MAAM;AACR;AAEA,SAAS,OAAO,OAGd;AACA,SAAO;AAAA,IACL,WAAW,UAAU,aAAa,UAAU;AAAA,IAC5C,eAAe,UAAU;AAAA,EAC3B;AACF;AAUO,SAAS,kBACd,UAAiC,CAAC,GACrB;AACb,QAAM,QAAQ,SAA2B,OAAO;AAChD,MAAI,UAA+B;AAEnC,WAAS,OAAO,MAAiC;AAC/C,UAAM,KAAK;AACX,cAAU;AAGV,QAAI,QAAQ;AAAW,SAAG,YAAY,QAAQ;AAC9C,QAAI,QAAQ;AAAc,SAAG,eAAe,QAAQ;AAEpD,UAAM,YAAY,CAAC,MAAa;AAC9B,YAAM,SAAU,EAA8B;AAC9C,YAAM,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,MAAM,OAAO,MAAM,OAAO,KAAK,EAAE;AAC9D,cAAQ,YAAY,MAAM;AAAA,IAC5B;AACA,UAAM,UAAU,CAAC,MAAa;AAC5B,YAAM,SAAU,EAA8B;AAC9C,YAAM,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,OAAO,EAAE;AAC7C,cAAQ,UAAU,MAAM;AAAA,IAC1B;AACA,UAAM,UAAU,CAAC,MAAa;AAC5B,YAAMA,SAAS,EAAwC,OAAO;AAC9D,YAAM,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,OAAAA,QAAO,GAAG,OAAOA,MAAK,EAAE,EAAE;AAAA,IACzD;AAEA,OAAG,iBAAiB,iBAAiB,SAAS;AAC9C,OAAG,iBAAiB,eAAe,OAAO;AAC1C,OAAG,iBAAiB,qBAAqB,OAAO;AAEhD,UAAM,QAAQ,GAAG;AACjB,UAAM,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,GAAG,OAAO,KAAK,EAAE,EAAE;AAEvD,WAAO;AAAA,MACL,UAAU;AACR,WAAG,oBAAoB,iBAAiB,SAAS;AACjD,WAAG,oBAAoB,eAAe,OAAO;AAC7C,WAAG,oBAAoB,qBAAqB,OAAO;AACnD,YAAI,YAAY;AAAI,oBAAU;AAAA,MAChC;AAAA,IACF;AAAA,EACF;AAEA,WAAS,UAAgB;AACvB,aAAS,MAAM;AAAA,EACjB;AAEA,WAAS,QAAc;AACrB,UAAM,OAAO,CAAC,OAAO,EAAE,GAAG,GAAG,OAAO,MAAM,MAAM,KAAK,EAAE;AAAA,EACzD;AAEA,SAAO,EAAE,WAAW,MAAM,WAAW,QAAQ,SAAS,MAAM;AAC9D","sourcesContent":["import { writable, type Readable } from \"svelte/store\";\nimport type {\n AuthPhase,\n AuthResult,\n GlideEndpoints,\n GlideError,\n GlideUser,\n} from \"@glydi/passkey-core\";\n\n/** Element instance shape we rely on (subset of BiometricAuthButton). */\ninterface GlideElement extends HTMLElement {\n phase: AuthPhase;\n endpoints: Partial<GlideEndpoints>;\n fetchOptions: RequestInit;\n}\n\nexport interface UsePasskeyAuthOptions {\n mode?: \"auto\" | \"signin\" | \"signup\";\n username?: string;\n endpoints?: Partial<GlideEndpoints>;\n /** Forwarded to fetch (e.g. custom headers). credentials:\"include\" is default. */\n fetchOptions?: RequestInit;\n onSuccess?: (result: AuthResult) => void;\n onError?: (error: GlideError) => void;\n}\n\n/** Reactive snapshot of the auth flow, exposed as a Svelte readable store. */\nexport interface PasskeyAuthState {\n phase: AuthPhase;\n /** True while the ceremony is in flight. */\n isPending: boolean;\n /** True once a feature-detect failure is reflected by the element. */\n isUnsupported: boolean;\n error: GlideError | null;\n user: GlideUser | null;\n}\n\n/** Svelte action: cleanup contract returned from `attach`. */\ninterface ActionReturn {\n destroy(): void;\n}\n\nexport interface PasskeyAuth {\n /**\n * Subscribe to the reactive auth state. Use directly in markup with `$`:\n * const auth = createPasskeyAuth();\n * $: ({ phase, isPending, error, user } = $auth);\n */\n subscribe: Readable<PasskeyAuthState>[\"subscribe\"];\n /**\n * Svelte action — attach to the element:\n * <biometric-auth-button use:auth.attach />\n * Forwards object props and bridges the `glide:*` CustomEvents into the store.\n */\n attach(node: HTMLElement): ActionReturn;\n /** Imperatively start auth (same as a user click). */\n trigger(): void;\n /** Clear the error/user without touching the element. */\n reset(): void;\n}\n\nconst INITIAL: PasskeyAuthState = {\n phase: \"idle\",\n isPending: false,\n isUnsupported: false,\n error: null,\n user: null,\n};\n\nfunction derive(phase: AuthPhase): Pick<\n PasskeyAuthState,\n \"isPending\" | \"isUnsupported\"\n> {\n return {\n isPending: phase === \"loading\" || phase === \"authenticating\",\n isUnsupported: phase === \"unsupported\",\n };\n}\n\n/**\n * Headless Svelte helper wrapping the <biometric-auth-button> Web Component.\n *\n * It owns nothing about rendering — you bring the element (via <PasskeyButton>\n * or a raw tag) and wire `use:auth.attach`. The helper subscribes to the\n * element's `glide:*` CustomEvents and projects them into a Svelte store +\n * callbacks. Mirrors the React `usePasskeyAuth` hook semantics.\n */\nexport function createPasskeyAuth(\n options: UsePasskeyAuthOptions = {},\n): PasskeyAuth {\n const store = writable<PasskeyAuthState>(INITIAL);\n let current: GlideElement | null = null;\n\n function attach(node: HTMLElement): ActionReturn {\n const el = node as GlideElement;\n current = el;\n\n // Push imperative config (objects can't be set as attributes).\n if (options.endpoints) el.endpoints = options.endpoints;\n if (options.fetchOptions) el.fetchOptions = options.fetchOptions;\n\n const onSuccess = (e: Event) => {\n const detail = (e as CustomEvent<AuthResult>).detail;\n store.update((s) => ({ ...s, user: detail.user, error: null }));\n options.onSuccess?.(detail);\n };\n const onError = (e: Event) => {\n const detail = (e as CustomEvent<GlideError>).detail;\n store.update((s) => ({ ...s, error: detail }));\n options.onError?.(detail);\n };\n const onPhase = (e: Event) => {\n const phase = (e as CustomEvent<{ phase: AuthPhase }>).detail.phase;\n store.update((s) => ({ ...s, phase, ...derive(phase) }));\n };\n\n el.addEventListener(\"glide:success\", onSuccess);\n el.addEventListener(\"glide:error\", onError);\n el.addEventListener(\"glide:phasechange\", onPhase);\n // Sync any phase already reflected before listeners attached.\n const phase = el.phase;\n store.update((s) => ({ ...s, phase, ...derive(phase) }));\n\n return {\n destroy() {\n el.removeEventListener(\"glide:success\", onSuccess);\n el.removeEventListener(\"glide:error\", onError);\n el.removeEventListener(\"glide:phasechange\", onPhase);\n if (current === el) current = null;\n },\n };\n }\n\n function trigger(): void {\n current?.click();\n }\n\n function reset(): void {\n store.update((s) => ({ ...s, error: null, user: null }));\n }\n\n return { subscribe: store.subscribe, attach, trigger, reset };\n}\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@glydi/passkey-svelte",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "Thin Svelte adapter for the Glide passkey Web Component.",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"svelte": "./dist/index.js",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./PasskeyButton.svelte": {
|
|
16
|
+
"svelte": "./src/PasskeyButton.svelte"
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"module": "./dist/index.js",
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"svelte": "./dist/index.js",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"src/PasskeyButton.svelte"
|
|
26
|
+
],
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"svelte": ">=4"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@glydi/passkey-core": "0.1.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"svelte": "^4.2.0",
|
|
35
|
+
"tsup": "^8.0.0",
|
|
36
|
+
"typescript": "^5.4.0"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"passkey",
|
|
40
|
+
"webauthn",
|
|
41
|
+
"svelte",
|
|
42
|
+
"authentication",
|
|
43
|
+
"biometric",
|
|
44
|
+
"fido2"
|
|
45
|
+
],
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup",
|
|
49
|
+
"dev": "tsup --watch",
|
|
50
|
+
"typecheck": "tsc --noEmit",
|
|
51
|
+
"clean": "rm -rf dist .turbo"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
PasskeyButton.svelte — thin Svelte wrapper around <biometric-auth-button>.
|
|
3
|
+
|
|
4
|
+
This is intentionally minimal: all real WebAuthn work lives in the core Web
|
|
5
|
+
Component. The wrapper only:
|
|
6
|
+
1. ensures the element is registered (client-only import in onMount),
|
|
7
|
+
2. forwards object props (endpoints, fetchOptions) imperatively, and
|
|
8
|
+
3. bridges the element's CustomEvents → Svelte callback props.
|
|
9
|
+
|
|
10
|
+
Svelte natively renders custom elements in markup, so attribute props
|
|
11
|
+
(mode/username/label) pass straight through. For full reactive state
|
|
12
|
+
(phase/error/user) use `createPasskeyAuth` instead.
|
|
13
|
+
-->
|
|
14
|
+
<script>
|
|
15
|
+
import { onMount, tick } from "svelte";
|
|
16
|
+
|
|
17
|
+
/** @type {"auto" | "signin" | "signup"} */
|
|
18
|
+
export let mode = "auto";
|
|
19
|
+
/** @type {string | undefined} */
|
|
20
|
+
export let username = undefined;
|
|
21
|
+
/** @type {string | undefined} */
|
|
22
|
+
export let label = undefined;
|
|
23
|
+
/** @type {import("@glydi/passkey-core").GlideEndpoints | undefined} */
|
|
24
|
+
export let endpoints = undefined;
|
|
25
|
+
/** @type {RequestInit | undefined} */
|
|
26
|
+
export let fetchOptions = undefined;
|
|
27
|
+
/** @type {((result: import("@glydi/passkey-core").AuthResult) => void) | undefined} */
|
|
28
|
+
export let onSuccess = undefined;
|
|
29
|
+
/** @type {((error: import("@glydi/passkey-core").GlideError) => void) | undefined} */
|
|
30
|
+
export let onError = undefined;
|
|
31
|
+
/** @type {((detail: { phase: import("@glydi/passkey-core").AuthPhase }) => void) | undefined} */
|
|
32
|
+
export let onPhaseChange = undefined;
|
|
33
|
+
|
|
34
|
+
/** @type {HTMLElement | undefined} */
|
|
35
|
+
let el;
|
|
36
|
+
|
|
37
|
+
// Forward non-serializable object props imperatively whenever they change
|
|
38
|
+
// (and once the element is bound).
|
|
39
|
+
$: if (el && endpoints) /** @type {any} */ (el).endpoints = endpoints;
|
|
40
|
+
$: if (el && fetchOptions) /** @type {any} */ (el).fetchOptions = fetchOptions;
|
|
41
|
+
|
|
42
|
+
onMount(() => {
|
|
43
|
+
let disposed = false;
|
|
44
|
+
|
|
45
|
+
/** @param {Event} e */
|
|
46
|
+
const handleSuccess = (e) =>
|
|
47
|
+
onSuccess?.(/** @type {CustomEvent} */ (e).detail);
|
|
48
|
+
/** @param {Event} e */
|
|
49
|
+
const handleError = (e) => onError?.(/** @type {CustomEvent} */ (e).detail);
|
|
50
|
+
/** @param {Event} e */
|
|
51
|
+
const handlePhase = (e) =>
|
|
52
|
+
onPhaseChange?.(/** @type {CustomEvent} */ (e).detail);
|
|
53
|
+
|
|
54
|
+
// Register the element client-side, then bind listeners + object props.
|
|
55
|
+
import("@glydi/passkey-core/define").then(async () => {
|
|
56
|
+
await tick();
|
|
57
|
+
if (disposed || !el) return;
|
|
58
|
+
if (endpoints) /** @type {any} */ (el).endpoints = endpoints;
|
|
59
|
+
if (fetchOptions) /** @type {any} */ (el).fetchOptions = fetchOptions;
|
|
60
|
+
el.addEventListener("glide:success", handleSuccess);
|
|
61
|
+
el.addEventListener("glide:error", handleError);
|
|
62
|
+
el.addEventListener("glide:phasechange", handlePhase);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return () => {
|
|
66
|
+
disposed = true;
|
|
67
|
+
if (!el) return;
|
|
68
|
+
el.removeEventListener("glide:success", handleSuccess);
|
|
69
|
+
el.removeEventListener("glide:error", handleError);
|
|
70
|
+
el.removeEventListener("glide:phasechange", handlePhase);
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
</script>
|
|
74
|
+
|
|
75
|
+
<biometric-auth-button
|
|
76
|
+
bind:this={el}
|
|
77
|
+
{mode}
|
|
78
|
+
{username}
|
|
79
|
+
{label}
|
|
80
|
+
{...$$restProps}
|
|
81
|
+
/>
|