@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-
|
|
2
|
-
export { A as ACTIVE_WALLET_STORAGE_KEY, b as ConnectError, P as PasskeyLoginTokens,
|
|
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-
|
|
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,
|
|
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
|
-
|
|
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-
|
|
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
|
|
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 };
|