@dexterai/connect 0.10.0 → 0.12.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.
@@ -60,10 +60,101 @@ function derInteger(component) {
60
60
  return out;
61
61
  }
62
62
 
63
+ // src/popup.ts
64
+ var DEFAULT_CONNECT_HOST = "https://dexter.cash/connect";
65
+ var POPUP_TIMEOUT_MS = 12e4;
66
+ var CANONICAL_ORIGIN = "https://dexter.cash";
67
+ function shouldUsePopup(transport) {
68
+ if (transport === "popup") return true;
69
+ if (transport === "inline") return false;
70
+ if (typeof window === "undefined") return false;
71
+ return window.location.origin !== CANONICAL_ORIGIN;
72
+ }
73
+ function openCeremonyPopup(op, config = {}) {
74
+ if (typeof window === "undefined") {
75
+ return Promise.reject(new ConnectError("not_browser", "popup ceremony requires a browser"));
76
+ }
77
+ const host = (config.connectHost ?? DEFAULT_CONNECT_HOST).replace(/\/$/, "");
78
+ const hostOrigin = new URL(host).origin;
79
+ const openerOrigin = window.location.origin;
80
+ const requestId = makeNonce();
81
+ const params = new URLSearchParams({ v: "1", op, requestId, origin: openerOrigin });
82
+ if (config.name) params.set("name", config.name);
83
+ if (config.apiBase) params.set("apiBase", config.apiBase);
84
+ const url = `${host}?${params.toString()}`;
85
+ return new Promise((resolve, reject) => {
86
+ const popup = window.open(url, "dexter-connect", popupFeatures());
87
+ if (!popup) {
88
+ reject(
89
+ new ConnectError("popup_blocked", "the Dexter sign-in popup was blocked \u2014 allow popups for this site")
90
+ );
91
+ return;
92
+ }
93
+ let settled = false;
94
+ const onMessage = (event) => {
95
+ if (event.origin !== hostOrigin) return;
96
+ const data = event.data;
97
+ if (!data || data.type !== "dexter-connect:result" || data.requestId !== requestId) return;
98
+ if (data.ok) finish(() => resolve(data.result));
99
+ else
100
+ finish(
101
+ () => reject(new ConnectError(data.error?.code ?? "popup_failed", data.error?.message))
102
+ );
103
+ };
104
+ const closedTimer = window.setInterval(() => {
105
+ if (popup.closed) finish(() => reject(new ConnectError("popup_closed", "the sign-in window was closed")));
106
+ }, 500);
107
+ const timeout = window.setTimeout(
108
+ () => finish(() => reject(new ConnectError("popup_timeout", "the sign-in window timed out"))),
109
+ POPUP_TIMEOUT_MS
110
+ );
111
+ function finish(act) {
112
+ if (settled) return;
113
+ settled = true;
114
+ window.removeEventListener("message", onMessage);
115
+ window.clearInterval(closedTimer);
116
+ window.clearTimeout(timeout);
117
+ try {
118
+ popup?.close();
119
+ } catch {
120
+ }
121
+ act();
122
+ }
123
+ window.addEventListener("message", onMessage);
124
+ });
125
+ }
126
+ function popupFeatures() {
127
+ const w = 420;
128
+ const h = 660;
129
+ const sy = typeof window !== "undefined" ? window.screenY || 0 : 0;
130
+ const sx = typeof window !== "undefined" ? window.screenX || 0 : 0;
131
+ const sh = typeof window !== "undefined" ? window.screen?.height ?? h : h;
132
+ const sw = typeof window !== "undefined" ? window.screen?.width ?? w : w;
133
+ const top = Math.max(0, Math.round((sh - h) / 2 + sy));
134
+ const left = Math.max(0, Math.round((sw - w) / 2 + sx));
135
+ return `popup,width=${w},height=${h},left=${left},top=${top}`;
136
+ }
137
+ function makeNonce() {
138
+ const c = typeof crypto !== "undefined" ? crypto : void 0;
139
+ if (c?.randomUUID) return c.randomUUID();
140
+ if (c?.getRandomValues) {
141
+ const a = new Uint8Array(16);
142
+ c.getRandomValues(a);
143
+ return Array.from(a, (b) => b.toString(16).padStart(2, "0")).join("");
144
+ }
145
+ return `rid-${new URL(location.href).searchParams.get("v") ?? ""}-${typeof performance !== "undefined" ? performance.now() : 0}`;
146
+ }
147
+
63
148
  // src/relay.ts
64
149
  var DEFAULT_API_BASE = "https://api.dexter.cash";
65
150
  var ANON_SIGN_BASE = "/api/passkey-anon/sign";
66
151
  async function passkeyLogin(config = {}, onPhase) {
152
+ if (shouldUsePopup(config.transport)) {
153
+ return openCeremonyPopup("signin", {
154
+ connectHost: config.connectHost,
155
+ apiBase: config.apiBase
156
+ });
157
+ }
67
158
  const apiBase = (config.apiBase ?? DEFAULT_API_BASE).replace(/\/$/, "");
68
159
  onPhase?.("challenge");
69
160
  const options = await fetchLoginChallenge(apiBase);
@@ -231,6 +322,20 @@ function createPasskeySigner(vault, apiBase, opts = {}) {
231
322
  });
232
323
  }
233
324
 
325
+ // src/identity.ts
326
+ function presentOrNull(value) {
327
+ return value && value.length > 0 ? value : null;
328
+ }
329
+ function resolveIdentity(input) {
330
+ const userHandle = presentOrNull(input.userHandle);
331
+ const accountToken = presentOrNull(input.accountToken);
332
+ const hasPasskeyVault = userHandle !== null;
333
+ const hasAccount = accountToken !== null;
334
+ const hasWallet = hasPasskeyVault || hasAccount;
335
+ const kind = hasPasskeyVault ? "passkey-vault" : hasAccount ? "account" : "none";
336
+ return { kind, userHandle, accountToken, hasPasskeyVault, hasAccount, hasWallet };
337
+ }
338
+
234
339
  // src/walletStore.ts
235
340
  var ACTIVE_HANDLE_KEY = "dexter:passkey:userHandle";
236
341
  var ROSTER_KEY = "dexter:passkey:wallets";
@@ -422,9 +527,12 @@ export {
422
527
  ConnectError,
423
528
  base64urlToBytes,
424
529
  bytesToBase64url,
530
+ shouldUsePopup,
531
+ openCeremonyPopup,
425
532
  passkeyLogin,
426
533
  createAnonServerPolicy,
427
534
  createPasskeySigner,
535
+ resolveIdentity,
428
536
  getActiveHandle,
429
537
  setActiveHandle,
430
538
  getCredentialId,
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { D as DexterConnectConfig, C as CeremonyPhase, S as SignInResult, a as ConnectVault } from './signals-CXkNJfIo.js';
2
- export { A as ACTIVE_WALLET_STORAGE_KEY, b as ConnectError, P as PasskeyLoginTokens, c as PasskeySignalSupport, d as StoredWallet, e as ejectActiveWallet, f as forgetWallet, g as getActiveHandle, h as getCredentialId, l as listWallets, p as passkeySignalSupport, i as prunePasskey, r as renamePasskey, s as setActiveHandle, j as subscribeWallet, k as switchWallet, m as syncAcceptedPasskeys } from './signals-CXkNJfIo.js';
1
+ import { D as DexterConnectConfig, C as CeremonyPhase, S as SignInResult, a as ConnectVault } from './signals-DM7IQdq6.js';
2
+ export { A as ACTIVE_WALLET_STORAGE_KEY, b as ConnectError, I as IdentityInput, c as IdentityKind, P as PasskeyLoginTokens, d as PasskeySignalSupport, R as ResolvedIdentity, e as StoredWallet, f as ejectActiveWallet, g as forgetWallet, h as getActiveHandle, i as getCredentialId, l as listWallets, p as passkeySignalSupport, j as prunePasskey, r as renamePasskey, k as resolveIdentity, s as setActiveHandle, m as subscribeWallet, n as switchWallet, o as syncAcceptedPasskeys } from './signals-DM7IQdq6.js';
3
3
  import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
4
4
 
5
5
  /**
package/dist/index.js CHANGED
@@ -11,21 +11,31 @@ import {
11
11
  getActiveHandle,
12
12
  getCredentialId,
13
13
  listWallets,
14
+ openCeremonyPopup,
14
15
  passkeyLogin,
15
16
  passkeySignalSupport,
16
17
  prunePasskey,
17
18
  renamePasskey,
19
+ resolveIdentity,
18
20
  setActiveHandle,
21
+ shouldUsePopup,
19
22
  subscribe,
20
23
  switchWallet,
21
24
  syncAcceptedPasskeys
22
- } from "./chunk-ZHJ557FI.js";
25
+ } from "./chunk-6NIGGPVI.js";
23
26
 
24
27
  // src/enroll.ts
25
28
  var DEFAULT_API_BASE = "https://api.dexter.cash";
26
29
  var DEFAULT_RP_ID = "dexter.cash";
27
30
  var DEFAULT_WALLET_NAME = "Dexter Wallet";
28
31
  async function createWallet(config = {}) {
32
+ if (shouldUsePopup(config.transport)) {
33
+ return openCeremonyPopup("create", {
34
+ connectHost: config.connectHost,
35
+ name: config.name,
36
+ apiBase: config.apiBase
37
+ });
38
+ }
29
39
  if (typeof navigator === "undefined" || !navigator.credentials) {
30
40
  throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
31
41
  }
@@ -166,6 +176,7 @@ export {
166
176
  passkeySignalSupport,
167
177
  prunePasskey,
168
178
  renamePasskey,
179
+ resolveIdentity,
169
180
  setActiveHandle,
170
181
  subscribe as subscribeWallet,
171
182
  switchWallet,
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
2
- import { C as CeremonyPhase, S as SignInResult, P as PasskeyLoginTokens, a as ConnectVault, b as ConnectError, d as StoredWallet, c as PasskeySignalSupport } from './signals-CXkNJfIo.js';
2
+ import { C as CeremonyPhase, S as SignInResult, P as PasskeyLoginTokens, a as ConnectVault, b as ConnectError, e as StoredWallet, d as PasskeySignalSupport, R as ResolvedIdentity } from './signals-DM7IQdq6.js';
3
3
  import { ReactElement, ReactNode } from 'react';
4
4
 
5
5
  type ConnectStatus = 'idle' | 'pending' | 'done' | 'error';
@@ -124,4 +124,11 @@ interface UseDexterWallet {
124
124
  }
125
125
  declare function useDexterWallet(): UseDexterWallet;
126
126
 
127
- export { type ConnectStatus, DexterButton, type DexterButtonProps, DexterMark, SignInWithDexter, type SignInWithDexterProps, type UseDexterWallet, type UseSignInWithDexter, type UseSignInWithDexterConfig, useDexterWallet, useSignInWithDexter };
127
+ interface UseIdentityConfig {
128
+ /** The account session token (e.g. a Supabase access_token), or null when the
129
+ * user has no account session. The SDK is auth-agnostic — pass your own. */
130
+ accountToken: string | null;
131
+ }
132
+ declare function useIdentity({ accountToken }: UseIdentityConfig): ResolvedIdentity;
133
+
134
+ export { type ConnectStatus, DexterButton, type DexterButtonProps, DexterMark, SignInWithDexter, type SignInWithDexterProps, type UseDexterWallet, type UseIdentityConfig, type UseSignInWithDexter, type UseSignInWithDexterConfig, useDexterWallet, useIdentity, useSignInWithDexter };
package/dist/react.js CHANGED
@@ -10,10 +10,11 @@ import {
10
10
  passkeySignalSupport,
11
11
  prunePasskey,
12
12
  renamePasskey,
13
+ resolveIdentity,
13
14
  setActiveHandle,
14
15
  subscribe,
15
16
  switchWallet
16
- } from "./chunk-ZHJ557FI.js";
17
+ } from "./chunk-6NIGGPVI.js";
17
18
 
18
19
  // src/useSignInWithDexter.ts
19
20
  import { useCallback, useEffect, useMemo, useState } from "react";
@@ -321,10 +322,24 @@ function useDexterWallet() {
321
322
  rename
322
323
  };
323
324
  }
325
+
326
+ // src/useIdentity.ts
327
+ import { useMemo as useMemo2 } from "react";
328
+ function useIdentity({ accountToken }) {
329
+ const { activeHandle } = useDexterWallet();
330
+ return useMemo2(
331
+ () => resolveIdentity({
332
+ accountToken: accountToken ?? null,
333
+ userHandle: activeHandle ?? null
334
+ }),
335
+ [accountToken, activeHandle]
336
+ );
337
+ }
324
338
  export {
325
339
  DexterButton,
326
340
  DexterMark,
327
341
  SignInWithDexter,
328
342
  useDexterWallet,
343
+ useIdentity,
329
344
  useSignInWithDexter
330
345
  };
@@ -41,6 +41,18 @@ type CeremonyPhase = 'challenge' | 'passkey' | 'verifying' | 'finalizing';
41
41
  interface DexterConnectConfig {
42
42
  /** dexter-api base. Default https://api.dexter.cash. */
43
43
  apiBase?: string;
44
+ /**
45
+ * Where the WebAuthn ceremony runs:
46
+ * - 'auto' (default): inline on the canonical Dexter origin (dexter.cash),
47
+ * popup on ANY other origin — so a third-party site works without the
48
+ * WebAuthn rpId-origin problem (in-page only works on dexter.cash).
49
+ * - 'popup': always via the hosted popup (works on any website).
50
+ * - 'inline': always in-page — only valid on a Dexter origin; this is what
51
+ * the hosted ceremony page itself uses.
52
+ */
53
+ transport?: 'auto' | 'popup' | 'inline';
54
+ /** Hosted ceremony page (popup transport). Default https://dexter.cash/connect. */
55
+ connectHost?: string;
44
56
  }
45
57
  /** Typed error whose `code` is the server's snake_case error string. */
46
58
  declare class ConnectError extends Error {
@@ -48,6 +60,29 @@ declare class ConnectError extends Error {
48
60
  constructor(code: string, message?: string);
49
61
  }
50
62
 
63
+ type IdentityKind = 'passkey-vault' | 'account' | 'none';
64
+ interface IdentityInput {
65
+ /** An account session token (e.g. Supabase access_token) when present, else null. */
66
+ accountToken: string | null;
67
+ /** The passkey-vault user handle (the connect wallet store), else null. */
68
+ userHandle: string | null;
69
+ }
70
+ interface ResolvedIdentity {
71
+ /** The primary identity axis, passkey-vault-first. */
72
+ kind: IdentityKind;
73
+ /** Passkey-vault identity (FIRST-CLASS): the wallet handle, or null. */
74
+ userHandle: string | null;
75
+ /** Account identity (secondary/legacy axis): bearer for account-scoped fetches, or null. */
76
+ accountToken: string | null;
77
+ /** A passkey vault is present on this device. */
78
+ hasPasskeyVault: boolean;
79
+ /** An account session is present. */
80
+ hasAccount: boolean;
81
+ /** Any identity at all — drives "show the wallet" vs "Sign in with Dexter". */
82
+ hasWallet: boolean;
83
+ }
84
+ declare function resolveIdentity(input: IdentityInput): ResolvedIdentity;
85
+
51
86
  /** A wallet this browser knows about. `handle` is the identity; the rest is UX. */
52
87
  interface StoredWallet {
53
88
  /** base64url 16-byte user handle — the vault identity. */
@@ -145,4 +180,4 @@ declare function syncAcceptedPasskeys(args: {
145
180
  rpId?: string;
146
181
  }): Promise<boolean>;
147
182
 
148
- export { ACTIVE_WALLET_STORAGE_KEY as A, type CeremonyPhase as C, type DexterConnectConfig as D, type PasskeyLoginTokens as P, type SignInResult as S, type ConnectVault as a, ConnectError as b, type PasskeySignalSupport as c, type StoredWallet as d, ejectActiveWallet as e, forgetWallet as f, getActiveHandle as g, getCredentialId as h, prunePasskey as i, subscribe as j, switchWallet as k, listWallets as l, syncAcceptedPasskeys as m, passkeySignalSupport as p, renamePasskey as r, setActiveHandle as s };
183
+ export { ACTIVE_WALLET_STORAGE_KEY as A, type CeremonyPhase as C, type DexterConnectConfig as D, type IdentityInput as I, type PasskeyLoginTokens as P, type ResolvedIdentity as R, type SignInResult as S, type ConnectVault as a, ConnectError as b, type IdentityKind as c, type PasskeySignalSupport as d, type StoredWallet as e, ejectActiveWallet as f, forgetWallet as g, getActiveHandle as h, getCredentialId as i, prunePasskey as j, resolveIdentity as k, listWallets as l, subscribe as m, switchWallet as n, syncAcceptedPasskeys as o, 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.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Sign in with Dexter — passkey connector. Composes @dexterai/vault.",
5
5
  "type": "module",
6
6
  "exports": {