@dexterai/connect 0.2.0 → 0.3.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.
@@ -228,9 +228,129 @@ function createPasskeySigner(vault, apiBase, opts = {}) {
228
228
  });
229
229
  }
230
230
 
231
+ // src/walletStore.ts
232
+ var ACTIVE_HANDLE_KEY = "dexter:passkey:userHandle";
233
+ var ROSTER_KEY = "dexter:passkey:wallets";
234
+ var listeners = /* @__PURE__ */ new Set();
235
+ function hasStorage() {
236
+ try {
237
+ return typeof window !== "undefined" && !!window.localStorage;
238
+ } catch {
239
+ return false;
240
+ }
241
+ }
242
+ function readRoster() {
243
+ if (!hasStorage()) return [];
244
+ try {
245
+ const raw = window.localStorage.getItem(ROSTER_KEY);
246
+ if (!raw) return [];
247
+ const parsed = JSON.parse(raw);
248
+ if (!Array.isArray(parsed)) return [];
249
+ return parsed.filter(
250
+ (w) => !!w && typeof w === "object" && typeof w.handle === "string"
251
+ );
252
+ } catch {
253
+ return [];
254
+ }
255
+ }
256
+ function writeRoster(wallets) {
257
+ if (!hasStorage()) return;
258
+ try {
259
+ window.localStorage.setItem(ROSTER_KEY, JSON.stringify(wallets));
260
+ } catch {
261
+ }
262
+ }
263
+ function emit() {
264
+ for (const l of listeners) {
265
+ try {
266
+ l();
267
+ } catch {
268
+ }
269
+ }
270
+ }
271
+ function getActiveHandle() {
272
+ if (!hasStorage()) return null;
273
+ try {
274
+ return window.localStorage.getItem(ACTIVE_HANDLE_KEY);
275
+ } catch {
276
+ return null;
277
+ }
278
+ }
279
+ function setActiveHandle(handle, label) {
280
+ if (!hasStorage() || !handle) return;
281
+ try {
282
+ window.localStorage.setItem(ACTIVE_HANDLE_KEY, handle);
283
+ } catch {
284
+ return;
285
+ }
286
+ const roster = readRoster();
287
+ const existing = roster.find((w) => w.handle === handle);
288
+ const now = Date.now();
289
+ if (existing) {
290
+ existing.lastUsedAt = now;
291
+ if (label !== void 0) existing.label = label;
292
+ } else {
293
+ roster.push({ handle, label, lastUsedAt: now });
294
+ }
295
+ writeRoster(roster);
296
+ emit();
297
+ }
298
+ function ejectActiveWallet(opts) {
299
+ if (!hasStorage()) return;
300
+ const current = getActiveHandle();
301
+ try {
302
+ window.localStorage.removeItem(ACTIVE_HANDLE_KEY);
303
+ } catch {
304
+ }
305
+ if (opts?.forget && current) {
306
+ writeRoster(readRoster().filter((w) => w.handle !== current));
307
+ }
308
+ emit();
309
+ }
310
+ function listWallets() {
311
+ return readRoster().sort((a, b) => (b.lastUsedAt ?? 0) - (a.lastUsedAt ?? 0));
312
+ }
313
+ function switchWallet(handle) {
314
+ if (!readRoster().some((w) => w.handle === handle)) return false;
315
+ setActiveHandle(handle);
316
+ return true;
317
+ }
318
+ function forgetWallet(handle) {
319
+ if (getActiveHandle() === handle) {
320
+ ejectActiveWallet({ forget: true });
321
+ return;
322
+ }
323
+ writeRoster(readRoster().filter((w) => w.handle !== handle));
324
+ emit();
325
+ }
326
+ function subscribe(listener) {
327
+ listeners.add(listener);
328
+ if (hasStorage() && listeners.size === 1) {
329
+ window.addEventListener("storage", onStorageEvent);
330
+ }
331
+ return () => {
332
+ listeners.delete(listener);
333
+ if (hasStorage() && listeners.size === 0) {
334
+ window.removeEventListener("storage", onStorageEvent);
335
+ }
336
+ };
337
+ }
338
+ function onStorageEvent(e) {
339
+ if (e.key === ACTIVE_HANDLE_KEY || e.key === ROSTER_KEY || e.key === null) emit();
340
+ }
341
+ var ACTIVE_WALLET_STORAGE_KEY = ACTIVE_HANDLE_KEY;
342
+
231
343
  export {
232
344
  ConnectError,
233
345
  passkeyLogin,
234
346
  createAnonServerPolicy,
235
- createPasskeySigner
347
+ createPasskeySigner,
348
+ getActiveHandle,
349
+ setActiveHandle,
350
+ ejectActiveWallet,
351
+ listWallets,
352
+ switchWallet,
353
+ forgetWallet,
354
+ subscribe,
355
+ ACTIVE_WALLET_STORAGE_KEY
236
356
  };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { D as DexterConnectConfig, S as SignInResult, C as ConnectVault } from './types---RcNI2Y.js';
2
- export { a as ConnectError, P as PasskeyLoginTokens } from './types---RcNI2Y.js';
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';
3
3
  import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
4
4
 
5
5
  /**
package/dist/index.js CHANGED
@@ -1,12 +1,28 @@
1
1
  import {
2
+ ACTIVE_WALLET_STORAGE_KEY,
2
3
  ConnectError,
3
4
  createAnonServerPolicy,
4
5
  createPasskeySigner,
5
- passkeyLogin
6
- } from "./chunk-46P7XAAI.js";
6
+ ejectActiveWallet,
7
+ forgetWallet,
8
+ getActiveHandle,
9
+ listWallets,
10
+ passkeyLogin,
11
+ setActiveHandle,
12
+ subscribe,
13
+ switchWallet
14
+ } from "./chunk-HFG2GUFX.js";
7
15
  export {
16
+ ACTIVE_WALLET_STORAGE_KEY,
8
17
  ConnectError,
9
18
  createAnonServerPolicy,
10
19
  createPasskeySigner,
11
- passkeyLogin
20
+ ejectActiveWallet,
21
+ forgetWallet,
22
+ getActiveHandle,
23
+ listWallets,
24
+ passkeyLogin,
25
+ setActiveHandle,
26
+ subscribe as subscribeWallet,
27
+ switchWallet
12
28
  };
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 } from './types---RcNI2Y.js';
2
+ import { S as SignInResult, P as PasskeyLoginTokens, C as ConnectVault, a as ConnectError, b as StoredWallet } from './walletStore-CWXhXuzK.js';
3
3
  import { ReactElement } from 'react';
4
4
 
5
5
  type ConnectStatus = 'idle' | 'pending' | 'done' | 'error';
@@ -64,4 +64,25 @@ interface SignInWithDexterProps extends UseSignInWithDexterConfig {
64
64
  */
65
65
  declare function SignInWithDexter(props: SignInWithDexterProps): ReactElement | null;
66
66
 
67
- export { type ConnectStatus, SignInWithDexter, type SignInWithDexterProps, type UseSignInWithDexter, type UseSignInWithDexterConfig, useSignInWithDexter };
67
+ interface UseDexterWallet {
68
+ /** Active wallet handle, or null if this browser has no active wallet. */
69
+ activeHandle: string | null;
70
+ /** Known wallets on this browser, most-recently-used first. */
71
+ wallets: StoredWallet[];
72
+ /**
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.
76
+ */
77
+ eject: (opts?: {
78
+ forget?: boolean;
79
+ }) => void;
80
+ /** Switch the active wallet to a known handle. No-op if unknown. */
81
+ 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
+ }
86
+ declare function useDexterWallet(): UseDexterWallet;
87
+
88
+ export { type ConnectStatus, SignInWithDexter, type SignInWithDexterProps, type UseDexterWallet, type UseSignInWithDexter, type UseSignInWithDexterConfig, useDexterWallet, useSignInWithDexter };
package/dist/react.js CHANGED
@@ -1,8 +1,14 @@
1
1
  import {
2
2
  ConnectError,
3
3
  createPasskeySigner,
4
- passkeyLogin
5
- } from "./chunk-46P7XAAI.js";
4
+ ejectActiveWallet,
5
+ getActiveHandle,
6
+ listWallets,
7
+ passkeyLogin,
8
+ setActiveHandle,
9
+ subscribe,
10
+ switchWallet
11
+ } from "./chunk-HFG2GUFX.js";
6
12
 
7
13
  // src/useSignInWithDexter.ts
8
14
  import { useCallback, useEffect, useMemo, useState } from "react";
@@ -198,7 +204,30 @@ var DISCONNECT = {
198
204
  lineHeight: 1,
199
205
  opacity: 0.6
200
206
  };
207
+
208
+ // src/useDexterWallet.ts
209
+ import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
210
+ function useDexterWallet() {
211
+ const [activeHandle, setHandle] = useState2(() => getActiveHandle());
212
+ const [wallets, setWallets] = useState2(() => listWallets());
213
+ useEffect2(() => {
214
+ const sync = () => {
215
+ setHandle(getActiveHandle());
216
+ setWallets(listWallets());
217
+ };
218
+ sync();
219
+ return subscribe(sync);
220
+ }, []);
221
+ return {
222
+ activeHandle,
223
+ wallets,
224
+ eject: useCallback2((opts) => ejectActiveWallet(opts), []),
225
+ switchTo: useCallback2((handle) => switchWallet(handle), []),
226
+ setActive: useCallback2((handle, label) => setActiveHandle(handle, label), [])
227
+ };
228
+ }
201
229
  export {
202
230
  SignInWithDexter,
231
+ useDexterWallet,
203
232
  useSignInWithDexter
204
233
  };
@@ -0,0 +1,92 @@
1
+ /** Supabase session tokens returned by dexter-api's passkey-login (camelCase). */
2
+ interface PasskeyLoginTokens {
3
+ accessToken: string;
4
+ refreshToken: string;
5
+ expiresAt: number;
6
+ expiresIn: number;
7
+ tokenType: string;
8
+ }
9
+ /**
10
+ * Vault identity, returned ALONGSIDE the session by passkey-login once
11
+ * vault-review ships the dexter-api change (ASK 1). Optional until then —
12
+ * the connector degrades to session-only. Consumers that open x402 tabs
13
+ * (dexter-agents) need `vaultPda` + `publicKey` to build a passkey signer.
14
+ */
15
+ interface ConnectVault {
16
+ vaultPda: string;
17
+ /** Swig state address, base58 — the user-facing Dexter Wallet address. */
18
+ swigAddress: string;
19
+ /** v2 swig wallet PDA (deposit address); null until the swig is deployed. */
20
+ receiveAddress: string | null;
21
+ /** Swig wallet's USDC ATA, base58 (for the connected-chip balance read);
22
+ * null until the swig is deployed. Server-resolved (off-curve-safe). */
23
+ usdcAta: string | null;
24
+ /** base64 33-byte SEC1 compressed P-256 authority pubkey (for the signer). */
25
+ publicKey: string;
26
+ userHandle: string;
27
+ credentialId: string;
28
+ }
29
+ /** Result of a completed "Sign in with Dexter" ceremony. */
30
+ interface SignInResult {
31
+ session: PasskeyLoginTokens;
32
+ /** Present once vault-review ships the vault-in-login change. */
33
+ vault?: ConnectVault;
34
+ }
35
+ interface DexterConnectConfig {
36
+ /** dexter-api base. Default https://api.dexter.cash. */
37
+ apiBase?: string;
38
+ }
39
+ /** Typed error whose `code` is the server's snake_case error string. */
40
+ declare class ConnectError extends Error {
41
+ readonly code: string;
42
+ constructor(code: string, message?: string);
43
+ }
44
+
45
+ /** A wallet this browser knows about. `handle` is the identity; the rest is UX. */
46
+ interface StoredWallet {
47
+ /** base64url 16-byte user handle — the vault identity. */
48
+ handle: string;
49
+ /** Human label for switch UIs (e.g. an email, or "Dexter Wallet"). */
50
+ label?: string;
51
+ /** Epoch ms of last activation — for ordering the switcher. */
52
+ lastUsedAt?: number;
53
+ }
54
+ type Listener = () => void;
55
+ /** The active wallet handle, or null if this browser has no active wallet. */
56
+ declare function getActiveHandle(): string | null;
57
+ /**
58
+ * Set the active wallet handle (e.g. after enroll or recover), upserting it into
59
+ * the roster with a fresh `lastUsedAt`. Idempotent. Fires subscribers.
60
+ */
61
+ declare function setActiveHandle(handle: string, label?: string): void;
62
+ /**
63
+ * EJECT — clear the active wallet so the browser is no longer bound to it. This
64
+ * is "switch / start fresh / sign out of this wallet". The wallet stays in the
65
+ * roster (so the user can switch back) unless `forget` is true. After eject,
66
+ * `getActiveHandle()` is null and the next enroll/recover starts clean. Fires
67
+ * subscribers. This is the function whose absence WAS the welded-wallet bug.
68
+ */
69
+ declare function ejectActiveWallet(opts?: {
70
+ forget?: boolean;
71
+ }): void;
72
+ /** Every wallet this browser knows about, most-recently-used first. */
73
+ declare function listWallets(): StoredWallet[];
74
+ /**
75
+ * Switch the active wallet to a handle ALREADY in the roster. Returns false (and
76
+ * does nothing) if the handle is unknown — switching is only ever to a wallet
77
+ * this browser has seen, never to an arbitrary string.
78
+ */
79
+ declare function switchWallet(handle: string): boolean;
80
+ /** Remove a wallet from the roster entirely; clears active if it was active. */
81
+ declare function forgetWallet(handle: string): void;
82
+ /**
83
+ * Subscribe to active-wallet/roster changes. Returns an unsubscribe fn. Also
84
+ * wires the cross-tab `storage` event once, so ejecting in one tab updates the
85
+ * others. The React hook (`useDexterWallet`) is a thin wrapper over this.
86
+ */
87
+ declare function subscribe(listener: Listener): () => void;
88
+ /** Exposed for consumers that must reference the canonical key (migrations,
89
+ * tests). Prefer the accessors above — do NOT read localStorage by hand. */
90
+ declare const ACTIVE_WALLET_STORAGE_KEY = "dexter:passkey:userHandle";
91
+
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dexterai/connect",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Sign in with Dexter — passkey connector. Composes @dexterai/vault.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1,45 +0,0 @@
1
- /** Supabase session tokens returned by dexter-api's passkey-login (camelCase). */
2
- interface PasskeyLoginTokens {
3
- accessToken: string;
4
- refreshToken: string;
5
- expiresAt: number;
6
- expiresIn: number;
7
- tokenType: string;
8
- }
9
- /**
10
- * Vault identity, returned ALONGSIDE the session by passkey-login once
11
- * vault-review ships the dexter-api change (ASK 1). Optional until then —
12
- * the connector degrades to session-only. Consumers that open x402 tabs
13
- * (dexter-agents) need `vaultPda` + `publicKey` to build a passkey signer.
14
- */
15
- interface ConnectVault {
16
- vaultPda: string;
17
- /** Swig state address, base58 — the user-facing Dexter Wallet address. */
18
- swigAddress: string;
19
- /** v2 swig wallet PDA (deposit address); null until the swig is deployed. */
20
- receiveAddress: string | null;
21
- /** Swig wallet's USDC ATA, base58 (for the connected-chip balance read);
22
- * null until the swig is deployed. Server-resolved (off-curve-safe). */
23
- usdcAta: string | null;
24
- /** base64 33-byte SEC1 compressed P-256 authority pubkey (for the signer). */
25
- publicKey: string;
26
- userHandle: string;
27
- credentialId: string;
28
- }
29
- /** Result of a completed "Sign in with Dexter" ceremony. */
30
- interface SignInResult {
31
- session: PasskeyLoginTokens;
32
- /** Present once vault-review ships the vault-in-login change. */
33
- vault?: ConnectVault;
34
- }
35
- interface DexterConnectConfig {
36
- /** dexter-api base. Default https://api.dexter.cash. */
37
- apiBase?: string;
38
- }
39
- /** Typed error whose `code` is the server's snake_case error string. */
40
- declare class ConnectError extends Error {
41
- readonly code: string;
42
- constructor(code: string, message?: string);
43
- }
44
-
45
- export { type ConnectVault as C, type DexterConnectConfig as D, type PasskeyLoginTokens as P, type SignInResult as S, ConnectError as a };