@dexterai/connect 0.9.0 → 0.11.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);
|
|
@@ -347,6 +438,17 @@ function onStorageEvent(e) {
|
|
|
347
438
|
}
|
|
348
439
|
var ACTIVE_WALLET_STORAGE_KEY = ACTIVE_HANDLE_KEY;
|
|
349
440
|
|
|
441
|
+
// src/phase.ts
|
|
442
|
+
var PHASE_LABEL = {
|
|
443
|
+
challenge: "Preparing\u2026",
|
|
444
|
+
passkey: "Waiting for your passkey\u2026",
|
|
445
|
+
verifying: "Verifying\u2026",
|
|
446
|
+
finalizing: "Finishing\u2026"
|
|
447
|
+
};
|
|
448
|
+
function ceremonyPhaseLabel(phase) {
|
|
449
|
+
return PHASE_LABEL[phase];
|
|
450
|
+
}
|
|
451
|
+
|
|
350
452
|
// src/signals.ts
|
|
351
453
|
function pkc() {
|
|
352
454
|
if (typeof window === "undefined") return null;
|
|
@@ -411,6 +513,8 @@ export {
|
|
|
411
513
|
ConnectError,
|
|
412
514
|
base64urlToBytes,
|
|
413
515
|
bytesToBase64url,
|
|
516
|
+
shouldUsePopup,
|
|
517
|
+
openCeremonyPopup,
|
|
414
518
|
passkeyLogin,
|
|
415
519
|
createAnonServerPolicy,
|
|
416
520
|
createPasskeySigner,
|
|
@@ -423,6 +527,7 @@ export {
|
|
|
423
527
|
forgetWallet,
|
|
424
528
|
subscribe,
|
|
425
529
|
ACTIVE_WALLET_STORAGE_KEY,
|
|
530
|
+
ceremonyPhaseLabel,
|
|
426
531
|
passkeySignalSupport,
|
|
427
532
|
renamePasskey,
|
|
428
533
|
prunePasskey,
|
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, 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-
|
|
1
|
+
import { D as DexterConnectConfig, C as CeremonyPhase, S as SignInResult, a as ConnectVault } from './signals-B7GXneoj.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-B7GXneoj.js';
|
|
3
3
|
import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -102,4 +102,12 @@ interface CreateWalletResult {
|
|
|
102
102
|
*/
|
|
103
103
|
declare function createWallet(config?: CreateWalletConfig): Promise<CreateWalletResult>;
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Human-readable label for a ceremony phase — the live "connecting step" copy.
|
|
107
|
+
* ONE source of truth so sign-in (SignInWithDexter) and create (consumer setup
|
|
108
|
+
* flows) show identical wording. Consumers surfacing createWallet's `onPhase`
|
|
109
|
+
* should use this instead of hand-rolling their own strings (Rule #7).
|
|
110
|
+
*/
|
|
111
|
+
declare function ceremonyPhaseLabel(phase: CeremonyPhase): string;
|
|
112
|
+
|
|
113
|
+
export { type AnonChallengeResult, type AnonServerPolicy, CeremonyPhase, ConnectVault, type CreateWalletConfig, type CreateWalletResult, DexterConnectConfig, SignInResult, ceremonyPhaseLabel, createAnonServerPolicy, createPasskeySigner, createWallet, passkeyLogin };
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
ConnectError,
|
|
4
4
|
base64urlToBytes,
|
|
5
5
|
bytesToBase64url,
|
|
6
|
+
ceremonyPhaseLabel,
|
|
6
7
|
createAnonServerPolicy,
|
|
7
8
|
createPasskeySigner,
|
|
8
9
|
ejectActiveWallet,
|
|
@@ -10,21 +11,30 @@ import {
|
|
|
10
11
|
getActiveHandle,
|
|
11
12
|
getCredentialId,
|
|
12
13
|
listWallets,
|
|
14
|
+
openCeremonyPopup,
|
|
13
15
|
passkeyLogin,
|
|
14
16
|
passkeySignalSupport,
|
|
15
17
|
prunePasskey,
|
|
16
18
|
renamePasskey,
|
|
17
19
|
setActiveHandle,
|
|
20
|
+
shouldUsePopup,
|
|
18
21
|
subscribe,
|
|
19
22
|
switchWallet,
|
|
20
23
|
syncAcceptedPasskeys
|
|
21
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-YCA2UT4N.js";
|
|
22
25
|
|
|
23
26
|
// src/enroll.ts
|
|
24
27
|
var DEFAULT_API_BASE = "https://api.dexter.cash";
|
|
25
28
|
var DEFAULT_RP_ID = "dexter.cash";
|
|
26
29
|
var DEFAULT_WALLET_NAME = "Dexter Wallet";
|
|
27
30
|
async function createWallet(config = {}) {
|
|
31
|
+
if (shouldUsePopup(config.transport)) {
|
|
32
|
+
return openCeremonyPopup("create", {
|
|
33
|
+
connectHost: config.connectHost,
|
|
34
|
+
name: config.name,
|
|
35
|
+
apiBase: config.apiBase
|
|
36
|
+
});
|
|
37
|
+
}
|
|
28
38
|
if (typeof navigator === "undefined" || !navigator.credentials) {
|
|
29
39
|
throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
|
|
30
40
|
}
|
|
@@ -152,6 +162,7 @@ async function readErrorCode(res) {
|
|
|
152
162
|
export {
|
|
153
163
|
ACTIVE_WALLET_STORAGE_KEY,
|
|
154
164
|
ConnectError,
|
|
165
|
+
ceremonyPhaseLabel,
|
|
155
166
|
createAnonServerPolicy,
|
|
156
167
|
createPasskeySigner,
|
|
157
168
|
createWallet,
|
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-
|
|
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-B7GXneoj.js';
|
|
3
3
|
import { ReactElement, ReactNode } from 'react';
|
|
4
4
|
|
|
5
5
|
type ConnectStatus = 'idle' | 'pending' | 'done' | 'error';
|
package/dist/react.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ConnectError,
|
|
3
|
+
ceremonyPhaseLabel,
|
|
3
4
|
createPasskeySigner,
|
|
4
5
|
ejectActiveWallet,
|
|
5
6
|
getActiveHandle,
|
|
@@ -12,7 +13,7 @@ import {
|
|
|
12
13
|
setActiveHandle,
|
|
13
14
|
subscribe,
|
|
14
15
|
switchWallet
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-YCA2UT4N.js";
|
|
16
17
|
|
|
17
18
|
// src/useSignInWithDexter.ts
|
|
18
19
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
@@ -226,12 +227,6 @@ function DexterButton(props) {
|
|
|
226
227
|
|
|
227
228
|
// src/SignInWithDexter.tsx
|
|
228
229
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
229
|
-
var PHASE_LABEL = {
|
|
230
|
-
challenge: "Preparing\u2026",
|
|
231
|
-
passkey: "Waiting for your passkey\u2026",
|
|
232
|
-
verifying: "Verifying\u2026",
|
|
233
|
-
finalizing: "Finishing\u2026"
|
|
234
|
-
};
|
|
235
230
|
function shortAddress(addr) {
|
|
236
231
|
return addr.length > 10 ? `${addr.slice(0, 4)}\u2026${addr.slice(-4)}` : addr;
|
|
237
232
|
}
|
|
@@ -274,7 +269,7 @@ function SignInWithDexter(props) {
|
|
|
274
269
|
DexterButton,
|
|
275
270
|
{
|
|
276
271
|
loading: c.status === "pending",
|
|
277
|
-
loadingLabel: c.phase ?
|
|
272
|
+
loadingLabel: c.phase ? ceremonyPhaseLabel(c.phase) : "Connecting\u2026",
|
|
278
273
|
variant,
|
|
279
274
|
block,
|
|
280
275
|
className,
|
|
@@ -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 {
|