@dexterai/connect 0.3.0 → 0.4.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.
@@ -276,7 +276,7 @@ function getActiveHandle() {
276
276
  return null;
277
277
  }
278
278
  }
279
- function setActiveHandle(handle, label) {
279
+ function setActiveHandle(handle, label, credentialId) {
280
280
  if (!hasStorage() || !handle) return;
281
281
  try {
282
282
  window.localStorage.setItem(ACTIVE_HANDLE_KEY, handle);
@@ -289,12 +289,16 @@ function setActiveHandle(handle, label) {
289
289
  if (existing) {
290
290
  existing.lastUsedAt = now;
291
291
  if (label !== void 0) existing.label = label;
292
+ if (credentialId !== void 0) existing.credentialId = credentialId;
292
293
  } else {
293
- roster.push({ handle, label, lastUsedAt: now });
294
+ roster.push({ handle, label, credentialId, lastUsedAt: now });
294
295
  }
295
296
  writeRoster(roster);
296
297
  emit();
297
298
  }
299
+ function getCredentialId(handle) {
300
+ return readRoster().find((w) => w.handle === handle)?.credentialId;
301
+ }
298
302
  function ejectActiveWallet(opts) {
299
303
  if (!hasStorage()) return;
300
304
  const current = getActiveHandle();
@@ -340,6 +344,66 @@ function onStorageEvent(e) {
340
344
  }
341
345
  var ACTIVE_WALLET_STORAGE_KEY = ACTIVE_HANDLE_KEY;
342
346
 
347
+ // src/signals.ts
348
+ function pkc() {
349
+ if (typeof window === "undefined") return null;
350
+ const g = globalThis.PublicKeyCredential;
351
+ return g ?? null;
352
+ }
353
+ function defaultRpId() {
354
+ return typeof window !== "undefined" ? window.location.hostname : "";
355
+ }
356
+ function passkeySignalSupport() {
357
+ const p = pkc();
358
+ return {
359
+ rename: typeof p?.signalCurrentUserDetails === "function",
360
+ prune: typeof p?.signalUnknownCredential === "function",
361
+ syncAccepted: typeof p?.signalAllAcceptedCredentials === "function"
362
+ };
363
+ }
364
+ async function renamePasskey(args) {
365
+ const p = pkc();
366
+ if (typeof p?.signalCurrentUserDetails !== "function") return false;
367
+ try {
368
+ await p.signalCurrentUserDetails({
369
+ rpId: args.rpId ?? defaultRpId(),
370
+ userId: args.userId,
371
+ name: args.name,
372
+ displayName: args.displayName ?? args.name
373
+ });
374
+ return true;
375
+ } catch {
376
+ return false;
377
+ }
378
+ }
379
+ async function prunePasskey(args) {
380
+ const p = pkc();
381
+ if (typeof p?.signalUnknownCredential !== "function") return false;
382
+ try {
383
+ await p.signalUnknownCredential({
384
+ rpId: args.rpId ?? defaultRpId(),
385
+ credentialId: args.credentialId
386
+ });
387
+ return true;
388
+ } catch {
389
+ return false;
390
+ }
391
+ }
392
+ async function syncAcceptedPasskeys(args) {
393
+ const p = pkc();
394
+ if (typeof p?.signalAllAcceptedCredentials !== "function") return false;
395
+ try {
396
+ await p.signalAllAcceptedCredentials({
397
+ rpId: args.rpId ?? defaultRpId(),
398
+ userId: args.userId,
399
+ allAcceptedCredentialIds: args.acceptedCredentialIds
400
+ });
401
+ return true;
402
+ } catch {
403
+ return false;
404
+ }
405
+ }
406
+
343
407
  export {
344
408
  ConnectError,
345
409
  passkeyLogin,
@@ -347,10 +411,15 @@ export {
347
411
  createPasskeySigner,
348
412
  getActiveHandle,
349
413
  setActiveHandle,
414
+ getCredentialId,
350
415
  ejectActiveWallet,
351
416
  listWallets,
352
417
  switchWallet,
353
418
  forgetWallet,
354
419
  subscribe,
355
- ACTIVE_WALLET_STORAGE_KEY
420
+ ACTIVE_WALLET_STORAGE_KEY,
421
+ passkeySignalSupport,
422
+ renamePasskey,
423
+ prunePasskey,
424
+ syncAcceptedPasskeys
356
425
  };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { D as DexterConnectConfig, S as SignInResult, C as ConnectVault } from './walletStore-CWXhXuzK.js';
2
- export { A as ACTIVE_WALLET_STORAGE_KEY, a as ConnectError, P as PasskeyLoginTokens, b as StoredWallet, e as ejectActiveWallet, f as forgetWallet, g as getActiveHandle, l as listWallets, s as setActiveHandle, c as subscribeWallet, d as switchWallet } from './walletStore-CWXhXuzK.js';
1
+ import { D as DexterConnectConfig, S as SignInResult, C as ConnectVault } from './signals-CkBFwCNw.js';
2
+ export { A as ACTIVE_WALLET_STORAGE_KEY, a as ConnectError, P as PasskeyLoginTokens, b as PasskeySignalSupport, c as StoredWallet, e as ejectActiveWallet, f as forgetWallet, g as getActiveHandle, d as getCredentialId, l as listWallets, p as passkeySignalSupport, h as prunePasskey, r as renamePasskey, s as setActiveHandle, i as subscribeWallet, j as switchWallet, k as syncAcceptedPasskeys } from './signals-CkBFwCNw.js';
3
3
  import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
4
4
 
5
5
  /**
package/dist/index.js CHANGED
@@ -6,12 +6,17 @@ import {
6
6
  ejectActiveWallet,
7
7
  forgetWallet,
8
8
  getActiveHandle,
9
+ getCredentialId,
9
10
  listWallets,
10
11
  passkeyLogin,
12
+ passkeySignalSupport,
13
+ prunePasskey,
14
+ renamePasskey,
11
15
  setActiveHandle,
12
16
  subscribe,
13
- switchWallet
14
- } from "./chunk-HFG2GUFX.js";
17
+ switchWallet,
18
+ syncAcceptedPasskeys
19
+ } from "./chunk-2V6EGIHV.js";
15
20
  export {
16
21
  ACTIVE_WALLET_STORAGE_KEY,
17
22
  ConnectError,
@@ -20,9 +25,14 @@ export {
20
25
  ejectActiveWallet,
21
26
  forgetWallet,
22
27
  getActiveHandle,
28
+ getCredentialId,
23
29
  listWallets,
24
30
  passkeyLogin,
31
+ passkeySignalSupport,
32
+ prunePasskey,
33
+ renamePasskey,
25
34
  setActiveHandle,
26
35
  subscribe as subscribeWallet,
27
- switchWallet
36
+ switchWallet,
37
+ syncAcceptedPasskeys
28
38
  };
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
2
- import { S as SignInResult, P as PasskeyLoginTokens, C as ConnectVault, a as ConnectError, b as StoredWallet } from './walletStore-CWXhXuzK.js';
2
+ import { S as SignInResult, P as PasskeyLoginTokens, C as ConnectVault, a as ConnectError, c as StoredWallet, b as PasskeySignalSupport } from './signals-CkBFwCNw.js';
3
3
  import { ReactElement } from 'react';
4
4
 
5
5
  type ConnectStatus = 'idle' | 'pending' | 'done' | 'error';
@@ -69,19 +69,28 @@ interface UseDexterWallet {
69
69
  activeHandle: string | null;
70
70
  /** Known wallets on this browser, most-recently-used first. */
71
71
  wallets: StoredWallet[];
72
+ /** What the WebAuthn Signal API supports in THIS browser (rename / prune). */
73
+ support: PasskeySignalSupport;
72
74
  /**
73
- * Eject the active wallet — "switch / start fresh / sign out of this wallet".
74
- * The browser is no longer bound to it; the next enroll/recover starts clean.
75
- * Pass `{ forget: true }` to also drop it from the roster.
75
+ * Eject the active wallet — "switch / start fresh". Clears the local binding
76
+ * and, where supported, prunes the old passkey from the OS manager so it
77
+ * disappears from the user's list. `{ forget: true }` also drops it from the
78
+ * roster.
76
79
  */
77
80
  eject: (opts?: {
78
81
  forget?: boolean;
79
82
  }) => void;
80
83
  /** Switch the active wallet to a known handle. No-op if unknown. */
81
84
  switchTo: (handle: string) => boolean;
82
- /** Record/activate a handle (after enroll or recover). Prefer this over
83
- * hand-writing localStorage so the roster + subscribers stay correct. */
84
- setActive: (handle: string, label?: string) => void;
85
+ /** Record/activate a handle (after enroll or recover). Prefer over writing
86
+ * localStorage by hand so the roster + subscribers stay correct. */
87
+ setActive: (handle: string, label?: string, credentialId?: string) => void;
88
+ /**
89
+ * Rename the ACTIVE passkey in the OS keychain (post-creation). Returns true
90
+ * if the browser supported it and the signal fired; false otherwise (the
91
+ * keychain entry is then just left as-is).
92
+ */
93
+ rename: (name: string, displayName?: string) => Promise<boolean>;
85
94
  }
86
95
  declare function useDexterWallet(): UseDexterWallet;
87
96
 
package/dist/react.js CHANGED
@@ -3,12 +3,16 @@ import {
3
3
  createPasskeySigner,
4
4
  ejectActiveWallet,
5
5
  getActiveHandle,
6
+ getCredentialId,
6
7
  listWallets,
7
8
  passkeyLogin,
9
+ passkeySignalSupport,
10
+ prunePasskey,
11
+ renamePasskey,
8
12
  setActiveHandle,
9
13
  subscribe,
10
14
  switchWallet
11
- } from "./chunk-HFG2GUFX.js";
15
+ } from "./chunk-2V6EGIHV.js";
12
16
 
13
17
  // src/useSignInWithDexter.ts
14
18
  import { useCallback, useEffect, useMemo, useState } from "react";
@@ -207,23 +211,44 @@ var DISCONNECT = {
207
211
 
208
212
  // src/useDexterWallet.ts
209
213
  import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
214
+ var NO_SUPPORT = { rename: false, prune: false, syncAccepted: false };
210
215
  function useDexterWallet() {
211
216
  const [activeHandle, setHandle] = useState2(() => getActiveHandle());
212
217
  const [wallets, setWallets] = useState2(() => listWallets());
218
+ const [support, setSupport] = useState2(NO_SUPPORT);
213
219
  useEffect2(() => {
214
220
  const sync = () => {
215
221
  setHandle(getActiveHandle());
216
222
  setWallets(listWallets());
217
223
  };
224
+ setSupport(passkeySignalSupport());
218
225
  sync();
219
226
  return subscribe(sync);
220
227
  }, []);
228
+ const eject = useCallback2((opts) => {
229
+ const handle = getActiveHandle();
230
+ const credentialId = handle ? getCredentialId(handle) : void 0;
231
+ ejectActiveWallet(opts);
232
+ if (credentialId) void prunePasskey({ credentialId });
233
+ }, []);
234
+ const rename = useCallback2(async (name, displayName) => {
235
+ const handle = getActiveHandle();
236
+ if (!handle) return false;
237
+ const ok = await renamePasskey({ userId: handle, name, displayName });
238
+ if (ok) setActiveHandle(handle, name);
239
+ return ok;
240
+ }, []);
221
241
  return {
222
242
  activeHandle,
223
243
  wallets,
224
- eject: useCallback2((opts) => ejectActiveWallet(opts), []),
244
+ support,
245
+ eject,
225
246
  switchTo: useCallback2((handle) => switchWallet(handle), []),
226
- setActive: useCallback2((handle, label) => setActiveHandle(handle, label), [])
247
+ setActive: useCallback2(
248
+ (handle, label, credentialId) => setActiveHandle(handle, label, credentialId),
249
+ []
250
+ ),
251
+ rename
227
252
  };
228
253
  }
229
254
  export {
@@ -48,6 +48,9 @@ interface StoredWallet {
48
48
  handle: string;
49
49
  /** Human label for switch UIs (e.g. an email, or "Dexter Wallet"). */
50
50
  label?: string;
51
+ /** base64url credential id — enables the WebAuthn Signal API to prune this
52
+ * passkey from the OS manager on eject (see ./signals). */
53
+ credentialId?: string;
51
54
  /** Epoch ms of last activation — for ordering the switcher. */
52
55
  lastUsedAt?: number;
53
56
  }
@@ -58,7 +61,9 @@ declare function getActiveHandle(): string | null;
58
61
  * Set the active wallet handle (e.g. after enroll or recover), upserting it into
59
62
  * the roster with a fresh `lastUsedAt`. Idempotent. Fires subscribers.
60
63
  */
61
- declare function setActiveHandle(handle: string, label?: string): void;
64
+ declare function setActiveHandle(handle: string, label?: string, credentialId?: string): void;
65
+ /** Look up a known wallet's stored credentialId (for Signal-API prune on eject). */
66
+ declare function getCredentialId(handle: string): string | undefined;
62
67
  /**
63
68
  * EJECT — clear the active wallet so the browser is no longer bound to it. This
64
69
  * is "switch / start fresh / sign out of this wallet". The wallet stays in the
@@ -89,4 +94,49 @@ declare function subscribe(listener: Listener): () => void;
89
94
  * tests). Prefer the accessors above — do NOT read localStorage by hand. */
90
95
  declare const ACTIVE_WALLET_STORAGE_KEY = "dexter:passkey:userHandle";
91
96
 
92
- export { ACTIVE_WALLET_STORAGE_KEY as A, type ConnectVault as C, type DexterConnectConfig as D, type PasskeyLoginTokens as P, type SignInResult as S, ConnectError as a, type StoredWallet as b, subscribe as c, switchWallet as d, ejectActiveWallet as e, forgetWallet as f, getActiveHandle as g, listWallets as l, setActiveHandle as s };
97
+ interface PasskeySignalSupport {
98
+ /** signalCurrentUserDetails — rename a passkey post-creation. */
99
+ rename: boolean;
100
+ /** signalUnknownCredential — remove one stale passkey from the manager. */
101
+ prune: boolean;
102
+ /** signalAllAcceptedCredentials — reconcile the full valid set. */
103
+ syncAccepted: boolean;
104
+ }
105
+ /**
106
+ * What the CURRENT browser supports, by direct feature-detection. Instant, no
107
+ * network, no UA sniffing — tells you exactly what will light up on THIS device
108
+ * (e.g. call once on Branch's iPhone to learn its Safari's status).
109
+ */
110
+ declare function passkeySignalSupport(): PasskeySignalSupport;
111
+ /**
112
+ * Rename a passkey in the OS keychain AFTER creation. `userId` is the base64url
113
+ * user handle; `rpId` defaults to the current host. Returns true if the signal
114
+ * fired, false if unsupported/failed (caller treats false as "left as-is").
115
+ */
116
+ declare function renamePasskey(args: {
117
+ userId: string;
118
+ name: string;
119
+ displayName?: string;
120
+ rpId?: string;
121
+ }): Promise<boolean>;
122
+ /**
123
+ * Tell the OS manager a credential is gone so it removes that passkey from the
124
+ * user's list — the welded-old-wallet auto-cleanup. `credentialId` is base64url.
125
+ * Returns true if fired, false if unsupported/failed.
126
+ */
127
+ declare function prunePasskey(args: {
128
+ credentialId: string;
129
+ rpId?: string;
130
+ }): Promise<boolean>;
131
+ /**
132
+ * Declare the FULL set of still-valid credential IDs for a user; the manager
133
+ * prunes anything not listed. Use after sign-in or eject to reconcile in one
134
+ * shot (pass `[]` to clear all of a user's passkeys). Returns true if fired.
135
+ */
136
+ declare function syncAcceptedPasskeys(args: {
137
+ userId: string;
138
+ acceptedCredentialIds: string[];
139
+ rpId?: string;
140
+ }): Promise<boolean>;
141
+
142
+ export { ACTIVE_WALLET_STORAGE_KEY as A, type ConnectVault as C, type DexterConnectConfig as D, type PasskeyLoginTokens as P, type SignInResult as S, ConnectError as a, type PasskeySignalSupport as b, type StoredWallet as c, getCredentialId as d, ejectActiveWallet as e, forgetWallet as f, getActiveHandle as g, prunePasskey as h, subscribe as i, switchWallet as j, syncAcceptedPasskeys as k, listWallets as l, passkeySignalSupport as p, renamePasskey as r, setActiveHandle as s };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dexterai/connect",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Sign in with Dexter — passkey connector. Composes @dexterai/vault.",
5
5
  "type": "module",
6
6
  "exports": {