@dexterai/connect 0.12.0 → 0.14.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.
- package/dist/{chunk-6NIGGPVI.js → chunk-IUZBHWTP.js} +283 -133
- package/dist/index.d.ts +35 -29
- package/dist/index.js +4 -142
- package/dist/react.d.ts +39 -2
- package/dist/react.js +133 -1
- package/dist/{signals-DM7IQdq6.d.ts → signals-BuP-7inZ.d.ts} +1 -1
- package/package.json +1 -1
|
@@ -8,6 +8,122 @@ var ConnectError = class extends Error {
|
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
+
// src/walletStore.ts
|
|
12
|
+
var ACTIVE_HANDLE_KEY = "dexter:passkey:userHandle";
|
|
13
|
+
var ROSTER_KEY = "dexter:passkey:wallets";
|
|
14
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
15
|
+
function hasStorage() {
|
|
16
|
+
try {
|
|
17
|
+
return typeof window !== "undefined" && !!window.localStorage;
|
|
18
|
+
} catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function readRoster() {
|
|
23
|
+
if (!hasStorage()) return [];
|
|
24
|
+
try {
|
|
25
|
+
const raw = window.localStorage.getItem(ROSTER_KEY);
|
|
26
|
+
if (!raw) return [];
|
|
27
|
+
const parsed = JSON.parse(raw);
|
|
28
|
+
if (!Array.isArray(parsed)) return [];
|
|
29
|
+
return parsed.filter(
|
|
30
|
+
(w) => !!w && typeof w === "object" && typeof w.handle === "string"
|
|
31
|
+
);
|
|
32
|
+
} catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function writeRoster(wallets) {
|
|
37
|
+
if (!hasStorage()) return;
|
|
38
|
+
try {
|
|
39
|
+
window.localStorage.setItem(ROSTER_KEY, JSON.stringify(wallets));
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function emit() {
|
|
44
|
+
for (const l of listeners) {
|
|
45
|
+
try {
|
|
46
|
+
l();
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function getActiveHandle() {
|
|
52
|
+
if (!hasStorage()) return null;
|
|
53
|
+
try {
|
|
54
|
+
return window.localStorage.getItem(ACTIVE_HANDLE_KEY);
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function setActiveHandle(handle, label, credentialId) {
|
|
60
|
+
if (!hasStorage() || !handle) return;
|
|
61
|
+
try {
|
|
62
|
+
window.localStorage.setItem(ACTIVE_HANDLE_KEY, handle);
|
|
63
|
+
} catch {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const roster = readRoster();
|
|
67
|
+
const existing = roster.find((w) => w.handle === handle);
|
|
68
|
+
const now = Date.now();
|
|
69
|
+
if (existing) {
|
|
70
|
+
existing.lastUsedAt = now;
|
|
71
|
+
if (label !== void 0) existing.label = label;
|
|
72
|
+
if (credentialId !== void 0) existing.credentialId = credentialId;
|
|
73
|
+
} else {
|
|
74
|
+
roster.push({ handle, label, credentialId, lastUsedAt: now });
|
|
75
|
+
}
|
|
76
|
+
writeRoster(roster);
|
|
77
|
+
emit();
|
|
78
|
+
}
|
|
79
|
+
function getCredentialId(handle) {
|
|
80
|
+
return readRoster().find((w) => w.handle === handle)?.credentialId;
|
|
81
|
+
}
|
|
82
|
+
function ejectActiveWallet(opts) {
|
|
83
|
+
if (!hasStorage()) return;
|
|
84
|
+
const current = getActiveHandle();
|
|
85
|
+
try {
|
|
86
|
+
window.localStorage.removeItem(ACTIVE_HANDLE_KEY);
|
|
87
|
+
} catch {
|
|
88
|
+
}
|
|
89
|
+
if (opts?.forget && current) {
|
|
90
|
+
writeRoster(readRoster().filter((w) => w.handle !== current));
|
|
91
|
+
}
|
|
92
|
+
emit();
|
|
93
|
+
}
|
|
94
|
+
function listWallets() {
|
|
95
|
+
return readRoster().sort((a, b) => (b.lastUsedAt ?? 0) - (a.lastUsedAt ?? 0));
|
|
96
|
+
}
|
|
97
|
+
function switchWallet(handle) {
|
|
98
|
+
if (!readRoster().some((w) => w.handle === handle)) return false;
|
|
99
|
+
setActiveHandle(handle);
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
function forgetWallet(handle) {
|
|
103
|
+
if (getActiveHandle() === handle) {
|
|
104
|
+
ejectActiveWallet({ forget: true });
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
writeRoster(readRoster().filter((w) => w.handle !== handle));
|
|
108
|
+
emit();
|
|
109
|
+
}
|
|
110
|
+
function subscribe(listener) {
|
|
111
|
+
listeners.add(listener);
|
|
112
|
+
if (hasStorage() && listeners.size === 1) {
|
|
113
|
+
window.addEventListener("storage", onStorageEvent);
|
|
114
|
+
}
|
|
115
|
+
return () => {
|
|
116
|
+
listeners.delete(listener);
|
|
117
|
+
if (hasStorage() && listeners.size === 0) {
|
|
118
|
+
window.removeEventListener("storage", onStorageEvent);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function onStorageEvent(e) {
|
|
123
|
+
if (e.key === ACTIVE_HANDLE_KEY || e.key === ROSTER_KEY || e.key === null) emit();
|
|
124
|
+
}
|
|
125
|
+
var ACTIVE_WALLET_STORAGE_KEY = ACTIVE_HANDLE_KEY;
|
|
126
|
+
|
|
11
127
|
// src/base64.ts
|
|
12
128
|
function base64ToBytes(s) {
|
|
13
129
|
const bin = atob(s);
|
|
@@ -145,8 +261,145 @@ function makeNonce() {
|
|
|
145
261
|
return `rid-${new URL(location.href).searchParams.get("v") ?? ""}-${typeof performance !== "undefined" ? performance.now() : 0}`;
|
|
146
262
|
}
|
|
147
263
|
|
|
148
|
-
// src/
|
|
264
|
+
// src/enroll.ts
|
|
149
265
|
var DEFAULT_API_BASE = "https://api.dexter.cash";
|
|
266
|
+
var DEFAULT_RP_ID = "dexter.cash";
|
|
267
|
+
var DEFAULT_WALLET_NAME = "Dexter Wallet";
|
|
268
|
+
async function createWallet(config = {}) {
|
|
269
|
+
if (shouldUsePopup(config.transport)) {
|
|
270
|
+
return openCeremonyPopup("create", {
|
|
271
|
+
connectHost: config.connectHost,
|
|
272
|
+
name: config.name,
|
|
273
|
+
apiBase: config.apiBase
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
if (typeof navigator === "undefined" || !navigator.credentials) {
|
|
277
|
+
throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
|
|
278
|
+
}
|
|
279
|
+
const apiBase = (config.apiBase ?? DEFAULT_API_BASE).replace(/\/$/, "");
|
|
280
|
+
const rpId = config.rpId ?? DEFAULT_RP_ID;
|
|
281
|
+
const name = config.name && config.name.trim() || DEFAULT_WALLET_NAME;
|
|
282
|
+
config.onPhase?.("challenge");
|
|
283
|
+
const options = await fetchEnrollChallenge(apiBase);
|
|
284
|
+
config.onPhase?.("passkey");
|
|
285
|
+
const credential = await createCredential(options, name, rpId);
|
|
286
|
+
config.onPhase?.("verifying");
|
|
287
|
+
const enrolled = await submitEnrollComplete(apiBase, credential);
|
|
288
|
+
config.onPhase?.("finalizing");
|
|
289
|
+
const init = await initializeVault(apiBase, enrolled.userHandle, enrolled.credentialId);
|
|
290
|
+
setActiveHandle(enrolled.userHandle, name, enrolled.credentialId);
|
|
291
|
+
return {
|
|
292
|
+
handle: enrolled.userHandle,
|
|
293
|
+
credentialId: enrolled.credentialId,
|
|
294
|
+
vault: {
|
|
295
|
+
vaultPda: init.vaultPda,
|
|
296
|
+
swigAddress: init.swigStateAddress,
|
|
297
|
+
// FAIL SAFE: never invent a receive address — null until the server returns
|
|
298
|
+
// one (depositing to the config PDA would strand funds).
|
|
299
|
+
receiveAddress: init.receiveAddress ?? null,
|
|
300
|
+
usdcAta: null,
|
|
301
|
+
// swig not deployed yet (counterfactual pattern)
|
|
302
|
+
publicKey: enrolled.publicKey,
|
|
303
|
+
userHandle: enrolled.userHandle,
|
|
304
|
+
credentialId: enrolled.credentialId
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
async function fetchEnrollChallenge(apiBase) {
|
|
309
|
+
const res = await fetch(`${apiBase}/api/passkey-anon/enroll/challenge`, {
|
|
310
|
+
method: "POST",
|
|
311
|
+
headers: { "content-type": "application/json" },
|
|
312
|
+
body: "{}"
|
|
313
|
+
});
|
|
314
|
+
if (!res.ok) throw new ConnectError("enroll_challenge_failed", `enroll/challenge ${res.status}`);
|
|
315
|
+
const data = await res.json();
|
|
316
|
+
if (!data?.options?.challenge) {
|
|
317
|
+
throw new ConnectError("enroll_challenge_malformed", "no creation options in response");
|
|
318
|
+
}
|
|
319
|
+
return data.options;
|
|
320
|
+
}
|
|
321
|
+
async function createCredential(options, name, rpId) {
|
|
322
|
+
let credential;
|
|
323
|
+
try {
|
|
324
|
+
credential = await navigator.credentials.create({
|
|
325
|
+
publicKey: buildCreationOptions(options, name, rpId)
|
|
326
|
+
});
|
|
327
|
+
} catch (err) {
|
|
328
|
+
throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
|
|
329
|
+
}
|
|
330
|
+
if (!credential || credential.type !== "public-key") {
|
|
331
|
+
throw new ConnectError("no_credential", "authenticator returned no credential");
|
|
332
|
+
}
|
|
333
|
+
return credential;
|
|
334
|
+
}
|
|
335
|
+
async function submitEnrollComplete(apiBase, credential) {
|
|
336
|
+
const attestation = credential.response;
|
|
337
|
+
const credentialJson = {
|
|
338
|
+
id: credential.id,
|
|
339
|
+
rawId: bytesToBase64url(new Uint8Array(credential.rawId)),
|
|
340
|
+
type: credential.type,
|
|
341
|
+
response: {
|
|
342
|
+
attestationObject: bytesToBase64url(new Uint8Array(attestation.attestationObject)),
|
|
343
|
+
clientDataJSON: bytesToBase64url(new Uint8Array(attestation.clientDataJSON)),
|
|
344
|
+
transports: typeof attestation.getTransports === "function" ? attestation.getTransports() : []
|
|
345
|
+
},
|
|
346
|
+
clientExtensionResults: credential.getClientExtensionResults?.() ?? {},
|
|
347
|
+
authenticatorAttachment: credential.authenticatorAttachment ?? null
|
|
348
|
+
};
|
|
349
|
+
const res = await fetch(`${apiBase}/api/passkey-anon/enroll/complete`, {
|
|
350
|
+
method: "POST",
|
|
351
|
+
headers: { "content-type": "application/json" },
|
|
352
|
+
body: JSON.stringify({ credential: credentialJson })
|
|
353
|
+
});
|
|
354
|
+
if (!res.ok) throw new ConnectError(await readErrorCode(res), `enroll/complete ${res.status}`);
|
|
355
|
+
return await res.json();
|
|
356
|
+
}
|
|
357
|
+
async function initializeVault(apiBase, userHandle, credentialId) {
|
|
358
|
+
const res = await fetch(`${apiBase}/api/passkey-vault-anon/initialize`, {
|
|
359
|
+
method: "POST",
|
|
360
|
+
headers: { "content-type": "application/json" },
|
|
361
|
+
body: JSON.stringify({ userHandle, credentialId, coolingOffSeconds: 0 })
|
|
362
|
+
});
|
|
363
|
+
if (!res.ok) throw new ConnectError(await readErrorCode(res), `initialize ${res.status}`);
|
|
364
|
+
return await res.json();
|
|
365
|
+
}
|
|
366
|
+
function toBuf(b64url) {
|
|
367
|
+
return base64urlToBytes(b64url).buffer.slice(0);
|
|
368
|
+
}
|
|
369
|
+
function buildCreationOptions(o, name, rpId) {
|
|
370
|
+
return {
|
|
371
|
+
// rp.name = the site shown in the keychain; user.name/displayName = the
|
|
372
|
+
// wallet label the user sees. We override the server's user.name (a raw,
|
|
373
|
+
// unreadable handle) with the chosen wallet name.
|
|
374
|
+
rp: { id: o.rp.id ?? rpId, name: "Dexter" },
|
|
375
|
+
user: {
|
|
376
|
+
id: toBuf(o.user.id),
|
|
377
|
+
name,
|
|
378
|
+
displayName: name
|
|
379
|
+
},
|
|
380
|
+
challenge: toBuf(o.challenge),
|
|
381
|
+
pubKeyCredParams: o.pubKeyCredParams,
|
|
382
|
+
timeout: o.timeout,
|
|
383
|
+
excludeCredentials: o.excludeCredentials?.map((c) => ({
|
|
384
|
+
id: toBuf(c.id),
|
|
385
|
+
type: c.type,
|
|
386
|
+
transports: c.transports
|
|
387
|
+
})),
|
|
388
|
+
authenticatorSelection: o.authenticatorSelection,
|
|
389
|
+
attestation: o.attestation
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
async function readErrorCode(res) {
|
|
393
|
+
try {
|
|
394
|
+
const body = await res.json();
|
|
395
|
+
if (body?.error) return body.error;
|
|
396
|
+
} catch {
|
|
397
|
+
}
|
|
398
|
+
return `http_${res.status}`;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// src/relay.ts
|
|
402
|
+
var DEFAULT_API_BASE2 = "https://api.dexter.cash";
|
|
150
403
|
var ANON_SIGN_BASE = "/api/passkey-anon/sign";
|
|
151
404
|
async function passkeyLogin(config = {}, onPhase) {
|
|
152
405
|
if (shouldUsePopup(config.transport)) {
|
|
@@ -155,7 +408,7 @@ async function passkeyLogin(config = {}, onPhase) {
|
|
|
155
408
|
apiBase: config.apiBase
|
|
156
409
|
});
|
|
157
410
|
}
|
|
158
|
-
const apiBase = (config.apiBase ??
|
|
411
|
+
const apiBase = (config.apiBase ?? DEFAULT_API_BASE2).replace(/\/$/, "");
|
|
159
412
|
onPhase?.("challenge");
|
|
160
413
|
const options = await fetchLoginChallenge(apiBase);
|
|
161
414
|
onPhase?.("passkey");
|
|
@@ -163,6 +416,21 @@ async function passkeyLogin(config = {}, onPhase) {
|
|
|
163
416
|
onPhase?.("verifying");
|
|
164
417
|
return submitLogin(apiBase, credential);
|
|
165
418
|
}
|
|
419
|
+
async function continueWithDexter(config = {}, onPhase) {
|
|
420
|
+
if (shouldUsePopup(config.transport)) {
|
|
421
|
+
return openCeremonyPopup("continue", {
|
|
422
|
+
connectHost: config.connectHost,
|
|
423
|
+
name: config.name,
|
|
424
|
+
apiBase: config.apiBase
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
if (getActiveHandle()) {
|
|
428
|
+
const result2 = await passkeyLogin(config, onPhase);
|
|
429
|
+
return { kind: "signin", ...result2 };
|
|
430
|
+
}
|
|
431
|
+
const result = await createWallet({ ...config, onPhase });
|
|
432
|
+
return { kind: "create", ...result };
|
|
433
|
+
}
|
|
166
434
|
async function fetchLoginChallenge(apiBase) {
|
|
167
435
|
const res = await fetch(`${apiBase}${ANON_SIGN_BASE}/login-challenge`, {
|
|
168
436
|
method: "POST",
|
|
@@ -221,7 +489,7 @@ async function submitLogin(apiBase, credential) {
|
|
|
221
489
|
body: JSON.stringify({ credential: credentialJson })
|
|
222
490
|
});
|
|
223
491
|
if (!res.ok) {
|
|
224
|
-
throw new ConnectError(await
|
|
492
|
+
throw new ConnectError(await readErrorCode2(res), `passkey-login ${res.status}`);
|
|
225
493
|
}
|
|
226
494
|
const data = await res.json();
|
|
227
495
|
const session = {
|
|
@@ -233,7 +501,7 @@ async function submitLogin(apiBase, credential) {
|
|
|
233
501
|
};
|
|
234
502
|
return data.vault ? { session, vault: data.vault } : { session };
|
|
235
503
|
}
|
|
236
|
-
async function
|
|
504
|
+
async function readErrorCode2(res) {
|
|
237
505
|
try {
|
|
238
506
|
const body = await res.json();
|
|
239
507
|
if (body?.error) return body.error;
|
|
@@ -243,9 +511,9 @@ async function readErrorCode(res) {
|
|
|
243
511
|
}
|
|
244
512
|
|
|
245
513
|
// src/anon-policy.ts
|
|
246
|
-
var
|
|
514
|
+
var DEFAULT_API_BASE3 = "https://api.dexter.cash";
|
|
247
515
|
var ANON_SIGN_BASE2 = "/api/passkey-anon/sign";
|
|
248
|
-
function createAnonServerPolicy(apiBase =
|
|
516
|
+
function createAnonServerPolicy(apiBase = DEFAULT_API_BASE3) {
|
|
249
517
|
const base = apiBase.replace(/\/$/, "");
|
|
250
518
|
return {
|
|
251
519
|
async issueChallenge({ userHandle, operationHash }) {
|
|
@@ -257,7 +525,7 @@ function createAnonServerPolicy(apiBase = DEFAULT_API_BASE2) {
|
|
|
257
525
|
operationHash: bytesToBase64url(operationHash)
|
|
258
526
|
})
|
|
259
527
|
});
|
|
260
|
-
if (!res.ok) throw new ConnectError(await
|
|
528
|
+
if (!res.ok) throw new ConnectError(await readErrorCode3(res), `challenge ${res.status}`);
|
|
261
529
|
const data = await res.json();
|
|
262
530
|
const options = data?.options;
|
|
263
531
|
if (!options?.challenge) {
|
|
@@ -294,7 +562,7 @@ function createAnonServerPolicy(apiBase = DEFAULT_API_BASE2) {
|
|
|
294
562
|
headers: { "content-type": "application/json" },
|
|
295
563
|
body: JSON.stringify({ credential, userHandle: bytesToBase64url(userHandle) })
|
|
296
564
|
});
|
|
297
|
-
if (!res.ok) throw new ConnectError(await
|
|
565
|
+
if (!res.ok) throw new ConnectError(await readErrorCode3(res), `verify ${res.status}`);
|
|
298
566
|
const data = await res.json();
|
|
299
567
|
if (data?.verified === false) {
|
|
300
568
|
throw new ConnectError("verification_failed", "server returned verified=false");
|
|
@@ -302,7 +570,7 @@ function createAnonServerPolicy(apiBase = DEFAULT_API_BASE2) {
|
|
|
302
570
|
}
|
|
303
571
|
};
|
|
304
572
|
}
|
|
305
|
-
async function
|
|
573
|
+
async function readErrorCode3(res) {
|
|
306
574
|
try {
|
|
307
575
|
const body = await res.json();
|
|
308
576
|
if (body?.error) return body.error;
|
|
@@ -336,122 +604,6 @@ function resolveIdentity(input) {
|
|
|
336
604
|
return { kind, userHandle, accountToken, hasPasskeyVault, hasAccount, hasWallet };
|
|
337
605
|
}
|
|
338
606
|
|
|
339
|
-
// src/walletStore.ts
|
|
340
|
-
var ACTIVE_HANDLE_KEY = "dexter:passkey:userHandle";
|
|
341
|
-
var ROSTER_KEY = "dexter:passkey:wallets";
|
|
342
|
-
var listeners = /* @__PURE__ */ new Set();
|
|
343
|
-
function hasStorage() {
|
|
344
|
-
try {
|
|
345
|
-
return typeof window !== "undefined" && !!window.localStorage;
|
|
346
|
-
} catch {
|
|
347
|
-
return false;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
function readRoster() {
|
|
351
|
-
if (!hasStorage()) return [];
|
|
352
|
-
try {
|
|
353
|
-
const raw = window.localStorage.getItem(ROSTER_KEY);
|
|
354
|
-
if (!raw) return [];
|
|
355
|
-
const parsed = JSON.parse(raw);
|
|
356
|
-
if (!Array.isArray(parsed)) return [];
|
|
357
|
-
return parsed.filter(
|
|
358
|
-
(w) => !!w && typeof w === "object" && typeof w.handle === "string"
|
|
359
|
-
);
|
|
360
|
-
} catch {
|
|
361
|
-
return [];
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
function writeRoster(wallets) {
|
|
365
|
-
if (!hasStorage()) return;
|
|
366
|
-
try {
|
|
367
|
-
window.localStorage.setItem(ROSTER_KEY, JSON.stringify(wallets));
|
|
368
|
-
} catch {
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
function emit() {
|
|
372
|
-
for (const l of listeners) {
|
|
373
|
-
try {
|
|
374
|
-
l();
|
|
375
|
-
} catch {
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
function getActiveHandle() {
|
|
380
|
-
if (!hasStorage()) return null;
|
|
381
|
-
try {
|
|
382
|
-
return window.localStorage.getItem(ACTIVE_HANDLE_KEY);
|
|
383
|
-
} catch {
|
|
384
|
-
return null;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
function setActiveHandle(handle, label, credentialId) {
|
|
388
|
-
if (!hasStorage() || !handle) return;
|
|
389
|
-
try {
|
|
390
|
-
window.localStorage.setItem(ACTIVE_HANDLE_KEY, handle);
|
|
391
|
-
} catch {
|
|
392
|
-
return;
|
|
393
|
-
}
|
|
394
|
-
const roster = readRoster();
|
|
395
|
-
const existing = roster.find((w) => w.handle === handle);
|
|
396
|
-
const now = Date.now();
|
|
397
|
-
if (existing) {
|
|
398
|
-
existing.lastUsedAt = now;
|
|
399
|
-
if (label !== void 0) existing.label = label;
|
|
400
|
-
if (credentialId !== void 0) existing.credentialId = credentialId;
|
|
401
|
-
} else {
|
|
402
|
-
roster.push({ handle, label, credentialId, lastUsedAt: now });
|
|
403
|
-
}
|
|
404
|
-
writeRoster(roster);
|
|
405
|
-
emit();
|
|
406
|
-
}
|
|
407
|
-
function getCredentialId(handle) {
|
|
408
|
-
return readRoster().find((w) => w.handle === handle)?.credentialId;
|
|
409
|
-
}
|
|
410
|
-
function ejectActiveWallet(opts) {
|
|
411
|
-
if (!hasStorage()) return;
|
|
412
|
-
const current = getActiveHandle();
|
|
413
|
-
try {
|
|
414
|
-
window.localStorage.removeItem(ACTIVE_HANDLE_KEY);
|
|
415
|
-
} catch {
|
|
416
|
-
}
|
|
417
|
-
if (opts?.forget && current) {
|
|
418
|
-
writeRoster(readRoster().filter((w) => w.handle !== current));
|
|
419
|
-
}
|
|
420
|
-
emit();
|
|
421
|
-
}
|
|
422
|
-
function listWallets() {
|
|
423
|
-
return readRoster().sort((a, b) => (b.lastUsedAt ?? 0) - (a.lastUsedAt ?? 0));
|
|
424
|
-
}
|
|
425
|
-
function switchWallet(handle) {
|
|
426
|
-
if (!readRoster().some((w) => w.handle === handle)) return false;
|
|
427
|
-
setActiveHandle(handle);
|
|
428
|
-
return true;
|
|
429
|
-
}
|
|
430
|
-
function forgetWallet(handle) {
|
|
431
|
-
if (getActiveHandle() === handle) {
|
|
432
|
-
ejectActiveWallet({ forget: true });
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
writeRoster(readRoster().filter((w) => w.handle !== handle));
|
|
436
|
-
emit();
|
|
437
|
-
}
|
|
438
|
-
function subscribe(listener) {
|
|
439
|
-
listeners.add(listener);
|
|
440
|
-
if (hasStorage() && listeners.size === 1) {
|
|
441
|
-
window.addEventListener("storage", onStorageEvent);
|
|
442
|
-
}
|
|
443
|
-
return () => {
|
|
444
|
-
listeners.delete(listener);
|
|
445
|
-
if (hasStorage() && listeners.size === 0) {
|
|
446
|
-
window.removeEventListener("storage", onStorageEvent);
|
|
447
|
-
}
|
|
448
|
-
};
|
|
449
|
-
}
|
|
450
|
-
function onStorageEvent(e) {
|
|
451
|
-
if (e.key === ACTIVE_HANDLE_KEY || e.key === ROSTER_KEY || e.key === null) emit();
|
|
452
|
-
}
|
|
453
|
-
var ACTIVE_WALLET_STORAGE_KEY = ACTIVE_HANDLE_KEY;
|
|
454
|
-
|
|
455
607
|
// src/phase.ts
|
|
456
608
|
var PHASE_LABEL = {
|
|
457
609
|
challenge: "Preparing\u2026",
|
|
@@ -525,14 +677,6 @@ async function syncAcceptedPasskeys(args) {
|
|
|
525
677
|
|
|
526
678
|
export {
|
|
527
679
|
ConnectError,
|
|
528
|
-
base64urlToBytes,
|
|
529
|
-
bytesToBase64url,
|
|
530
|
-
shouldUsePopup,
|
|
531
|
-
openCeremonyPopup,
|
|
532
|
-
passkeyLogin,
|
|
533
|
-
createAnonServerPolicy,
|
|
534
|
-
createPasskeySigner,
|
|
535
|
-
resolveIdentity,
|
|
536
680
|
getActiveHandle,
|
|
537
681
|
setActiveHandle,
|
|
538
682
|
getCredentialId,
|
|
@@ -542,6 +686,12 @@ export {
|
|
|
542
686
|
forgetWallet,
|
|
543
687
|
subscribe,
|
|
544
688
|
ACTIVE_WALLET_STORAGE_KEY,
|
|
689
|
+
createWallet,
|
|
690
|
+
passkeyLogin,
|
|
691
|
+
continueWithDexter,
|
|
692
|
+
createAnonServerPolicy,
|
|
693
|
+
createPasskeySigner,
|
|
694
|
+
resolveIdentity,
|
|
545
695
|
ceremonyPhaseLabel,
|
|
546
696
|
passkeySignalSupport,
|
|
547
697
|
renamePasskey,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,33 @@
|
|
|
1
|
-
import { D as DexterConnectConfig,
|
|
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-
|
|
1
|
+
import { C as ConnectVault, D as DexterConnectConfig, a as CeremonyPhase, S as SignInResult } from './signals-BuP-7inZ.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-BuP-7inZ.js';
|
|
3
3
|
import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
|
|
4
4
|
|
|
5
|
+
interface CreateWalletConfig extends DexterConnectConfig {
|
|
6
|
+
/** Label for the passkey in the OS keychain AND the wallet roster. Set at
|
|
7
|
+
* creation — the only moment naming is guaranteed to stick. Default "Dexter Wallet". */
|
|
8
|
+
name?: string;
|
|
9
|
+
/** RP id for the new credential. Default "dexter.cash". */
|
|
10
|
+
rpId?: string;
|
|
11
|
+
/** Called as the ceremony progresses, for live "connecting steps" UI:
|
|
12
|
+
* challenge → passkey → verifying → finalizing. */
|
|
13
|
+
onPhase?: (phase: CeremonyPhase) => void;
|
|
14
|
+
}
|
|
15
|
+
interface CreateWalletResult {
|
|
16
|
+
/** Server-minted 16-byte user handle, base64url — the vault identity. */
|
|
17
|
+
handle: string;
|
|
18
|
+
/** base64url credential id of the new passkey. */
|
|
19
|
+
credentialId: string;
|
|
20
|
+
/** The freshly initialized vault (swig not yet deployed; deploys lazily). */
|
|
21
|
+
vault: ConnectVault;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Mint a brand-new Dexter wallet (passkey + vault) and make it the active wallet.
|
|
25
|
+
*
|
|
26
|
+
* One passkey approval. Throws ConnectError on any failed leg (the `code` is the
|
|
27
|
+
* server's error string, or webauthn_failed / no_credential for the ceremony).
|
|
28
|
+
*/
|
|
29
|
+
declare function createWallet(config?: CreateWalletConfig): Promise<CreateWalletResult>;
|
|
30
|
+
|
|
5
31
|
/**
|
|
6
32
|
* "Sign in with Dexter" — the discoverable-credential login ceremony.
|
|
7
33
|
*
|
|
@@ -16,6 +42,12 @@ import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
|
|
|
16
42
|
* Supabase session, so the Supabase-gated router would 401.
|
|
17
43
|
*/
|
|
18
44
|
declare function passkeyLogin(config?: DexterConnectConfig, onPhase?: (phase: CeremonyPhase) => void): Promise<SignInResult>;
|
|
45
|
+
type ContinueResult = ({
|
|
46
|
+
kind: 'signin';
|
|
47
|
+
} & SignInResult) | ({
|
|
48
|
+
kind: 'create';
|
|
49
|
+
} & CreateWalletResult);
|
|
50
|
+
declare function continueWithDexter(config?: CreateWalletConfig, onPhase?: (phase: CeremonyPhase) => void): Promise<ContinueResult>;
|
|
19
51
|
|
|
20
52
|
/** What `issueChallenge` returns to the SDK signer. */
|
|
21
53
|
interface AnonChallengeResult {
|
|
@@ -76,32 +108,6 @@ declare function createPasskeySigner(vault: ConnectVault, apiBase?: string, opts
|
|
|
76
108
|
__assertion?: AssertionLike;
|
|
77
109
|
}): DexterApiBrowserPasskeySigner;
|
|
78
110
|
|
|
79
|
-
interface CreateWalletConfig extends DexterConnectConfig {
|
|
80
|
-
/** Label for the passkey in the OS keychain AND the wallet roster. Set at
|
|
81
|
-
* creation — the only moment naming is guaranteed to stick. Default "Dexter Wallet". */
|
|
82
|
-
name?: string;
|
|
83
|
-
/** RP id for the new credential. Default "dexter.cash". */
|
|
84
|
-
rpId?: string;
|
|
85
|
-
/** Called as the ceremony progresses, for live "connecting steps" UI:
|
|
86
|
-
* challenge → passkey → verifying → finalizing. */
|
|
87
|
-
onPhase?: (phase: CeremonyPhase) => void;
|
|
88
|
-
}
|
|
89
|
-
interface CreateWalletResult {
|
|
90
|
-
/** Server-minted 16-byte user handle, base64url — the vault identity. */
|
|
91
|
-
handle: string;
|
|
92
|
-
/** base64url credential id of the new passkey. */
|
|
93
|
-
credentialId: string;
|
|
94
|
-
/** The freshly initialized vault (swig not yet deployed; deploys lazily). */
|
|
95
|
-
vault: ConnectVault;
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Mint a brand-new Dexter wallet (passkey + vault) and make it the active wallet.
|
|
99
|
-
*
|
|
100
|
-
* One passkey approval. Throws ConnectError on any failed leg (the `code` is the
|
|
101
|
-
* server's error string, or webauthn_failed / no_credential for the ceremony).
|
|
102
|
-
*/
|
|
103
|
-
declare function createWallet(config?: CreateWalletConfig): Promise<CreateWalletResult>;
|
|
104
|
-
|
|
105
111
|
/**
|
|
106
112
|
* Human-readable label for a ceremony phase — the live "connecting step" copy.
|
|
107
113
|
* ONE source of truth so sign-in (SignInWithDexter) and create (consumer setup
|
|
@@ -110,4 +116,4 @@ declare function createWallet(config?: CreateWalletConfig): Promise<CreateWallet
|
|
|
110
116
|
*/
|
|
111
117
|
declare function ceremonyPhaseLabel(phase: CeremonyPhase): string;
|
|
112
118
|
|
|
113
|
-
export { type AnonChallengeResult, type AnonServerPolicy, CeremonyPhase, ConnectVault, type CreateWalletConfig, type CreateWalletResult, DexterConnectConfig, SignInResult, ceremonyPhaseLabel, createAnonServerPolicy, createPasskeySigner, createWallet, passkeyLogin };
|
|
119
|
+
export { type AnonChallengeResult, type AnonServerPolicy, CeremonyPhase, ConnectVault, type ContinueResult, type CreateWalletConfig, type CreateWalletResult, DexterConnectConfig, SignInResult, ceremonyPhaseLabel, continueWithDexter, createAnonServerPolicy, createPasskeySigner, createWallet, passkeyLogin };
|
package/dist/index.js
CHANGED
|
@@ -1,169 +1,31 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ACTIVE_WALLET_STORAGE_KEY,
|
|
3
3
|
ConnectError,
|
|
4
|
-
base64urlToBytes,
|
|
5
|
-
bytesToBase64url,
|
|
6
4
|
ceremonyPhaseLabel,
|
|
5
|
+
continueWithDexter,
|
|
7
6
|
createAnonServerPolicy,
|
|
8
7
|
createPasskeySigner,
|
|
8
|
+
createWallet,
|
|
9
9
|
ejectActiveWallet,
|
|
10
10
|
forgetWallet,
|
|
11
11
|
getActiveHandle,
|
|
12
12
|
getCredentialId,
|
|
13
13
|
listWallets,
|
|
14
|
-
openCeremonyPopup,
|
|
15
14
|
passkeyLogin,
|
|
16
15
|
passkeySignalSupport,
|
|
17
16
|
prunePasskey,
|
|
18
17
|
renamePasskey,
|
|
19
18
|
resolveIdentity,
|
|
20
19
|
setActiveHandle,
|
|
21
|
-
shouldUsePopup,
|
|
22
20
|
subscribe,
|
|
23
21
|
switchWallet,
|
|
24
22
|
syncAcceptedPasskeys
|
|
25
|
-
} from "./chunk-
|
|
26
|
-
|
|
27
|
-
// src/enroll.ts
|
|
28
|
-
var DEFAULT_API_BASE = "https://api.dexter.cash";
|
|
29
|
-
var DEFAULT_RP_ID = "dexter.cash";
|
|
30
|
-
var DEFAULT_WALLET_NAME = "Dexter Wallet";
|
|
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
|
-
}
|
|
39
|
-
if (typeof navigator === "undefined" || !navigator.credentials) {
|
|
40
|
-
throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
|
|
41
|
-
}
|
|
42
|
-
const apiBase = (config.apiBase ?? DEFAULT_API_BASE).replace(/\/$/, "");
|
|
43
|
-
const rpId = config.rpId ?? DEFAULT_RP_ID;
|
|
44
|
-
const name = config.name && config.name.trim() || DEFAULT_WALLET_NAME;
|
|
45
|
-
config.onPhase?.("challenge");
|
|
46
|
-
const options = await fetchEnrollChallenge(apiBase);
|
|
47
|
-
config.onPhase?.("passkey");
|
|
48
|
-
const credential = await createCredential(options, name, rpId);
|
|
49
|
-
config.onPhase?.("verifying");
|
|
50
|
-
const enrolled = await submitEnrollComplete(apiBase, credential);
|
|
51
|
-
config.onPhase?.("finalizing");
|
|
52
|
-
const init = await initializeVault(apiBase, enrolled.userHandle, enrolled.credentialId);
|
|
53
|
-
setActiveHandle(enrolled.userHandle, name, enrolled.credentialId);
|
|
54
|
-
return {
|
|
55
|
-
handle: enrolled.userHandle,
|
|
56
|
-
credentialId: enrolled.credentialId,
|
|
57
|
-
vault: {
|
|
58
|
-
vaultPda: init.vaultPda,
|
|
59
|
-
swigAddress: init.swigStateAddress,
|
|
60
|
-
// FAIL SAFE: never invent a receive address — null until the server returns
|
|
61
|
-
// one (depositing to the config PDA would strand funds).
|
|
62
|
-
receiveAddress: init.receiveAddress ?? null,
|
|
63
|
-
usdcAta: null,
|
|
64
|
-
// swig not deployed yet (counterfactual pattern)
|
|
65
|
-
publicKey: enrolled.publicKey,
|
|
66
|
-
userHandle: enrolled.userHandle,
|
|
67
|
-
credentialId: enrolled.credentialId
|
|
68
|
-
}
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
async function fetchEnrollChallenge(apiBase) {
|
|
72
|
-
const res = await fetch(`${apiBase}/api/passkey-anon/enroll/challenge`, {
|
|
73
|
-
method: "POST",
|
|
74
|
-
headers: { "content-type": "application/json" },
|
|
75
|
-
body: "{}"
|
|
76
|
-
});
|
|
77
|
-
if (!res.ok) throw new ConnectError("enroll_challenge_failed", `enroll/challenge ${res.status}`);
|
|
78
|
-
const data = await res.json();
|
|
79
|
-
if (!data?.options?.challenge) {
|
|
80
|
-
throw new ConnectError("enroll_challenge_malformed", "no creation options in response");
|
|
81
|
-
}
|
|
82
|
-
return data.options;
|
|
83
|
-
}
|
|
84
|
-
async function createCredential(options, name, rpId) {
|
|
85
|
-
let credential;
|
|
86
|
-
try {
|
|
87
|
-
credential = await navigator.credentials.create({
|
|
88
|
-
publicKey: buildCreationOptions(options, name, rpId)
|
|
89
|
-
});
|
|
90
|
-
} catch (err) {
|
|
91
|
-
throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
|
|
92
|
-
}
|
|
93
|
-
if (!credential || credential.type !== "public-key") {
|
|
94
|
-
throw new ConnectError("no_credential", "authenticator returned no credential");
|
|
95
|
-
}
|
|
96
|
-
return credential;
|
|
97
|
-
}
|
|
98
|
-
async function submitEnrollComplete(apiBase, credential) {
|
|
99
|
-
const attestation = credential.response;
|
|
100
|
-
const credentialJson = {
|
|
101
|
-
id: credential.id,
|
|
102
|
-
rawId: bytesToBase64url(new Uint8Array(credential.rawId)),
|
|
103
|
-
type: credential.type,
|
|
104
|
-
response: {
|
|
105
|
-
attestationObject: bytesToBase64url(new Uint8Array(attestation.attestationObject)),
|
|
106
|
-
clientDataJSON: bytesToBase64url(new Uint8Array(attestation.clientDataJSON)),
|
|
107
|
-
transports: typeof attestation.getTransports === "function" ? attestation.getTransports() : []
|
|
108
|
-
},
|
|
109
|
-
clientExtensionResults: credential.getClientExtensionResults?.() ?? {},
|
|
110
|
-
authenticatorAttachment: credential.authenticatorAttachment ?? null
|
|
111
|
-
};
|
|
112
|
-
const res = await fetch(`${apiBase}/api/passkey-anon/enroll/complete`, {
|
|
113
|
-
method: "POST",
|
|
114
|
-
headers: { "content-type": "application/json" },
|
|
115
|
-
body: JSON.stringify({ credential: credentialJson })
|
|
116
|
-
});
|
|
117
|
-
if (!res.ok) throw new ConnectError(await readErrorCode(res), `enroll/complete ${res.status}`);
|
|
118
|
-
return await res.json();
|
|
119
|
-
}
|
|
120
|
-
async function initializeVault(apiBase, userHandle, credentialId) {
|
|
121
|
-
const res = await fetch(`${apiBase}/api/passkey-vault-anon/initialize`, {
|
|
122
|
-
method: "POST",
|
|
123
|
-
headers: { "content-type": "application/json" },
|
|
124
|
-
body: JSON.stringify({ userHandle, credentialId, coolingOffSeconds: 0 })
|
|
125
|
-
});
|
|
126
|
-
if (!res.ok) throw new ConnectError(await readErrorCode(res), `initialize ${res.status}`);
|
|
127
|
-
return await res.json();
|
|
128
|
-
}
|
|
129
|
-
function toBuf(b64url) {
|
|
130
|
-
return base64urlToBytes(b64url).buffer.slice(0);
|
|
131
|
-
}
|
|
132
|
-
function buildCreationOptions(o, name, rpId) {
|
|
133
|
-
return {
|
|
134
|
-
// rp.name = the site shown in the keychain; user.name/displayName = the
|
|
135
|
-
// wallet label the user sees. We override the server's user.name (a raw,
|
|
136
|
-
// unreadable handle) with the chosen wallet name.
|
|
137
|
-
rp: { id: o.rp.id ?? rpId, name: "Dexter" },
|
|
138
|
-
user: {
|
|
139
|
-
id: toBuf(o.user.id),
|
|
140
|
-
name,
|
|
141
|
-
displayName: name
|
|
142
|
-
},
|
|
143
|
-
challenge: toBuf(o.challenge),
|
|
144
|
-
pubKeyCredParams: o.pubKeyCredParams,
|
|
145
|
-
timeout: o.timeout,
|
|
146
|
-
excludeCredentials: o.excludeCredentials?.map((c) => ({
|
|
147
|
-
id: toBuf(c.id),
|
|
148
|
-
type: c.type,
|
|
149
|
-
transports: c.transports
|
|
150
|
-
})),
|
|
151
|
-
authenticatorSelection: o.authenticatorSelection,
|
|
152
|
-
attestation: o.attestation
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
async function readErrorCode(res) {
|
|
156
|
-
try {
|
|
157
|
-
const body = await res.json();
|
|
158
|
-
if (body?.error) return body.error;
|
|
159
|
-
} catch {
|
|
160
|
-
}
|
|
161
|
-
return `http_${res.status}`;
|
|
162
|
-
}
|
|
23
|
+
} from "./chunk-IUZBHWTP.js";
|
|
163
24
|
export {
|
|
164
25
|
ACTIVE_WALLET_STORAGE_KEY,
|
|
165
26
|
ConnectError,
|
|
166
27
|
ceremonyPhaseLabel,
|
|
28
|
+
continueWithDexter,
|
|
167
29
|
createAnonServerPolicy,
|
|
168
30
|
createPasskeySigner,
|
|
169
31
|
createWallet,
|
package/dist/react.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
|
|
2
|
-
import {
|
|
2
|
+
import { a as CeremonyPhase, S as SignInResult, P as PasskeyLoginTokens, C as ConnectVault, b as ConnectError, e as StoredWallet, d as PasskeySignalSupport, R as ResolvedIdentity } from './signals-BuP-7inZ.js';
|
|
3
3
|
import { ReactElement, ReactNode } from 'react';
|
|
4
4
|
|
|
5
5
|
type ConnectStatus = 'idle' | 'pending' | 'done' | 'error';
|
|
@@ -124,6 +124,43 @@ interface UseDexterWallet {
|
|
|
124
124
|
}
|
|
125
125
|
declare function useDexterWallet(): UseDexterWallet;
|
|
126
126
|
|
|
127
|
+
interface DexterWalletChipProps {
|
|
128
|
+
/** Connected (a wallet/account is present) → avatar + label; else signed-out label only. */
|
|
129
|
+
connected: boolean;
|
|
130
|
+
/** What to show: account name, wallet label, or the signed-out prompt (e.g. "Log in"). */
|
|
131
|
+
label: string;
|
|
132
|
+
/** Avatar image (e.g. a linked X avatar); falls back to the initial. */
|
|
133
|
+
avatarUrl?: string | null;
|
|
134
|
+
/** Single-character avatar fallback (defaults to the first letter of `label`). */
|
|
135
|
+
avatarInitial?: string;
|
|
136
|
+
onClick?: () => void;
|
|
137
|
+
/** Extra className composed after the brand classes. */
|
|
138
|
+
className?: string;
|
|
139
|
+
/** Reflected to aria-expanded (when the chip toggles a menu). */
|
|
140
|
+
ariaExpanded?: boolean;
|
|
141
|
+
}
|
|
142
|
+
/** The branded wallet/account chip. Wire it to open your menu via `onClick`. */
|
|
143
|
+
declare function DexterWalletChip(props: DexterWalletChipProps): ReactElement;
|
|
144
|
+
|
|
145
|
+
interface DexterWalletMenuProps {
|
|
146
|
+
/** The wallet's display name (nickname, else "Dexter Wallet"). */
|
|
147
|
+
walletLabel: string;
|
|
148
|
+
/** Avatar initial (defaults to the first letter of walletLabel). */
|
|
149
|
+
avatarInitial?: string;
|
|
150
|
+
/** "Manage wallet" → the consumer's wallet page. Row hidden if omitted. */
|
|
151
|
+
onManageWallet?: () => void;
|
|
152
|
+
/** "Start fresh" → the consumer ejects/resets (guard + perform on its side). Row hidden if omitted. */
|
|
153
|
+
onStartFresh?: () => void;
|
|
154
|
+
/** Sign-in/save UI revealed when "Save your wallet" is tapped (e.g. <SignInWithDexter/>). Row hidden if omitted. */
|
|
155
|
+
saveSlot?: ReactNode;
|
|
156
|
+
/** Short hint shown above the save UI. */
|
|
157
|
+
saveHint?: string;
|
|
158
|
+
/** Extra className composed after the brand classes. */
|
|
159
|
+
className?: string;
|
|
160
|
+
}
|
|
161
|
+
/** The branded wallet dropdown. */
|
|
162
|
+
declare function DexterWalletMenu(props: DexterWalletMenuProps): ReactElement;
|
|
163
|
+
|
|
127
164
|
interface UseIdentityConfig {
|
|
128
165
|
/** The account session token (e.g. a Supabase access_token), or null when the
|
|
129
166
|
* user has no account session. The SDK is auth-agnostic — pass your own. */
|
|
@@ -131,4 +168,4 @@ interface UseIdentityConfig {
|
|
|
131
168
|
}
|
|
132
169
|
declare function useIdentity({ accountToken }: UseIdentityConfig): ResolvedIdentity;
|
|
133
170
|
|
|
134
|
-
export { type ConnectStatus, DexterButton, type DexterButtonProps, DexterMark, SignInWithDexter, type SignInWithDexterProps, type UseDexterWallet, type UseIdentityConfig, type UseSignInWithDexter, type UseSignInWithDexterConfig, useDexterWallet, useIdentity, useSignInWithDexter };
|
|
171
|
+
export { type ConnectStatus, DexterButton, type DexterButtonProps, DexterMark, DexterWalletChip, type DexterWalletChipProps, DexterWalletMenu, type DexterWalletMenuProps, SignInWithDexter, type SignInWithDexterProps, type UseDexterWallet, type UseIdentityConfig, type UseSignInWithDexter, type UseSignInWithDexterConfig, useDexterWallet, useIdentity, useSignInWithDexter };
|
package/dist/react.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
setActiveHandle,
|
|
15
15
|
subscribe,
|
|
16
16
|
switchWallet
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-IUZBHWTP.js";
|
|
18
18
|
|
|
19
19
|
// src/useSignInWithDexter.ts
|
|
20
20
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
@@ -323,6 +323,136 @@ function useDexterWallet() {
|
|
|
323
323
|
};
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
+
// src/DexterWalletChip.tsx
|
|
327
|
+
import { useEffect as useEffect5 } from "react";
|
|
328
|
+
|
|
329
|
+
// src/walletKitStyles.ts
|
|
330
|
+
var STYLE_ID2 = "dexter-connect-wallet-kit-styles";
|
|
331
|
+
var WALLET_KIT_CSS = `
|
|
332
|
+
.dx-wchip{
|
|
333
|
+
--dx-ember:#f26c18; --dx-ember-2:#ba3a00; --dx-fg:#fff4ea; --dx-radius:0px;
|
|
334
|
+
display:inline-flex; align-items:center; gap:9px; max-width:240px;
|
|
335
|
+
padding:6px 12px 6px 7px; font:inherit; font-weight:600; font-size:.74rem;
|
|
336
|
+
letter-spacing:.1em; text-transform:uppercase; cursor:pointer; background:transparent;
|
|
337
|
+
color:inherit; border:1px solid color-mix(in srgb,var(--dx-ember) 42%,transparent);
|
|
338
|
+
border-radius:var(--dx-radius); -webkit-tap-highlight-color:transparent;
|
|
339
|
+
transition:filter .16s ease, box-shadow .16s ease, border-color .16s ease;
|
|
340
|
+
}
|
|
341
|
+
.dx-wchip:hover{ filter:brightness(1.08); box-shadow:0 8px 18px color-mix(in srgb,var(--dx-ember) 18%,transparent); }
|
|
342
|
+
.dx-wchip:focus-visible{ outline:none; box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember) 34%,transparent); }
|
|
343
|
+
.dx-wchip--signedout{ border-color:color-mix(in srgb,currentColor 22%,transparent); padding-left:12px; }
|
|
344
|
+
.dx-wchip__avatar{
|
|
345
|
+
width:22px; height:22px; flex-shrink:0; display:inline-flex; align-items:center; justify-content:center;
|
|
346
|
+
border-radius:50%; overflow:hidden; font-size:.72rem; font-weight:700; letter-spacing:0; line-height:1;
|
|
347
|
+
background:linear-gradient(135deg,var(--dx-ember),var(--dx-ember-2)); color:var(--dx-fg);
|
|
348
|
+
}
|
|
349
|
+
.dx-wchip__avatar img{ width:100%; height:100%; object-fit:cover; }
|
|
350
|
+
.dx-wchip__label{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
351
|
+
|
|
352
|
+
.dx-wmenu{ --dx-ember:#f26c18; --dx-ember-2:#ba3a00; --dx-fg:#fff4ea; display:flex; flex-direction:column; min-width:248px; }
|
|
353
|
+
.dx-wmenu__id{ display:flex; align-items:center; gap:10px; padding:13px 15px; border-bottom:1px solid color-mix(in srgb,currentColor 12%,transparent); }
|
|
354
|
+
.dx-wmenu__avatar{ width:32px; height:32px; flex-shrink:0; display:inline-flex; align-items:center; justify-content:center; border-radius:50%; font-weight:700; font-size:.85rem; line-height:1; background:linear-gradient(135deg,var(--dx-ember),var(--dx-ember-2)); color:var(--dx-fg); }
|
|
355
|
+
.dx-wmenu__meta{ display:flex; flex-direction:column; gap:2px; min-width:0; }
|
|
356
|
+
.dx-wmenu__name{ font-weight:700; font-size:.9rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
357
|
+
.dx-wmenu__sub{ font-size:.66rem; letter-spacing:.12em; text-transform:uppercase; opacity:.55; }
|
|
358
|
+
.dx-wmenu__list{ display:flex; flex-direction:column; padding:6px; }
|
|
359
|
+
.dx-wmenu__item{ display:flex; align-items:center; justify-content:space-between; gap:10px; width:100%; padding:10px 12px; font:inherit; font-size:.82rem; font-weight:600; text-align:left; background:transparent; border:none; color:inherit; cursor:pointer; transition:background .12s ease; }
|
|
360
|
+
.dx-wmenu__item:hover{ background:color-mix(in srgb,var(--dx-ember) 10%,transparent); }
|
|
361
|
+
.dx-wmenu__item--danger{ color:#e5552e; }
|
|
362
|
+
.dx-wmenu__icon{ opacity:.45; font-size:.9rem; }
|
|
363
|
+
.dx-wmenu__back{ border-bottom:1px solid color-mix(in srgb,currentColor 10%,transparent); opacity:.85; }
|
|
364
|
+
.dx-wmenu__save{ display:flex; flex-direction:column; gap:9px; padding:13px 15px; }
|
|
365
|
+
.dx-wmenu__savehint{ font-size:.78rem; line-height:1.4; opacity:.7; }
|
|
366
|
+
`;
|
|
367
|
+
function ensureWalletKitStyles() {
|
|
368
|
+
if (typeof document === "undefined") return;
|
|
369
|
+
if (document.getElementById(STYLE_ID2)) return;
|
|
370
|
+
const el = document.createElement("style");
|
|
371
|
+
el.id = STYLE_ID2;
|
|
372
|
+
el.textContent = WALLET_KIT_CSS;
|
|
373
|
+
document.head.appendChild(el);
|
|
374
|
+
}
|
|
375
|
+
ensureWalletKitStyles();
|
|
376
|
+
|
|
377
|
+
// src/DexterWalletChip.tsx
|
|
378
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
379
|
+
function DexterWalletChip(props) {
|
|
380
|
+
const { connected, label, avatarUrl, avatarInitial, onClick, className, ariaExpanded } = props;
|
|
381
|
+
useEffect5(ensureWalletKitStyles, []);
|
|
382
|
+
const initial = (avatarInitial ?? label.trim().charAt(0) ?? "D").toUpperCase();
|
|
383
|
+
return /* @__PURE__ */ jsxs3(
|
|
384
|
+
"button",
|
|
385
|
+
{
|
|
386
|
+
type: "button",
|
|
387
|
+
className: cx("dx-wchip", !connected && "dx-wchip--signedout", className),
|
|
388
|
+
onClick,
|
|
389
|
+
"aria-expanded": ariaExpanded,
|
|
390
|
+
children: [
|
|
391
|
+
connected ? /* @__PURE__ */ jsx3("span", { className: "dx-wchip__avatar", "aria-hidden": "true", children: avatarUrl ? /* @__PURE__ */ jsx3("img", { src: avatarUrl, alt: "" }) : initial }) : null,
|
|
392
|
+
/* @__PURE__ */ jsx3("span", { className: "dx-wchip__label", children: label })
|
|
393
|
+
]
|
|
394
|
+
}
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// src/DexterWalletMenu.tsx
|
|
399
|
+
import { useEffect as useEffect6, useState as useState3 } from "react";
|
|
400
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
401
|
+
function DexterWalletMenu(props) {
|
|
402
|
+
const { walletLabel, avatarInitial, onManageWallet, onStartFresh, saveSlot, saveHint, className } = props;
|
|
403
|
+
useEffect6(ensureWalletKitStyles, []);
|
|
404
|
+
const [showSave, setShowSave] = useState3(false);
|
|
405
|
+
const initial = (avatarInitial ?? walletLabel.trim().charAt(0) ?? "D").toUpperCase();
|
|
406
|
+
if (showSave && saveSlot) {
|
|
407
|
+
return /* @__PURE__ */ jsxs4("div", { className: cx("dx-wmenu", className), children: [
|
|
408
|
+
/* @__PURE__ */ jsx4(
|
|
409
|
+
"button",
|
|
410
|
+
{
|
|
411
|
+
type: "button",
|
|
412
|
+
className: "dx-wmenu__item dx-wmenu__back",
|
|
413
|
+
onClick: () => setShowSave(false),
|
|
414
|
+
children: /* @__PURE__ */ jsx4("span", { children: "\u2190 Back to wallet" })
|
|
415
|
+
}
|
|
416
|
+
),
|
|
417
|
+
/* @__PURE__ */ jsxs4("div", { className: "dx-wmenu__save", children: [
|
|
418
|
+
saveHint ? /* @__PURE__ */ jsx4("span", { className: "dx-wmenu__savehint", children: saveHint }) : null,
|
|
419
|
+
saveSlot
|
|
420
|
+
] })
|
|
421
|
+
] });
|
|
422
|
+
}
|
|
423
|
+
return /* @__PURE__ */ jsxs4("div", { className: cx("dx-wmenu", className), children: [
|
|
424
|
+
/* @__PURE__ */ jsxs4("div", { className: "dx-wmenu__id", children: [
|
|
425
|
+
/* @__PURE__ */ jsx4("span", { className: "dx-wmenu__avatar", "aria-hidden": "true", children: initial }),
|
|
426
|
+
/* @__PURE__ */ jsxs4("span", { className: "dx-wmenu__meta", children: [
|
|
427
|
+
/* @__PURE__ */ jsx4("span", { className: "dx-wmenu__name", children: walletLabel }),
|
|
428
|
+
/* @__PURE__ */ jsx4("span", { className: "dx-wmenu__sub", children: "Your Dexter Wallet" })
|
|
429
|
+
] })
|
|
430
|
+
] }),
|
|
431
|
+
/* @__PURE__ */ jsxs4("div", { className: "dx-wmenu__list", children: [
|
|
432
|
+
onManageWallet ? /* @__PURE__ */ jsxs4("button", { type: "button", className: "dx-wmenu__item", onClick: onManageWallet, children: [
|
|
433
|
+
/* @__PURE__ */ jsx4("span", { children: "Manage wallet" }),
|
|
434
|
+
/* @__PURE__ */ jsx4("span", { className: "dx-wmenu__icon", "aria-hidden": "true", children: "\u2197" })
|
|
435
|
+
] }) : null,
|
|
436
|
+
saveSlot ? /* @__PURE__ */ jsxs4("button", { type: "button", className: "dx-wmenu__item", onClick: () => setShowSave(true), children: [
|
|
437
|
+
/* @__PURE__ */ jsx4("span", { children: "Save your wallet" }),
|
|
438
|
+
/* @__PURE__ */ jsx4("span", { className: "dx-wmenu__icon", "aria-hidden": "true", children: "\u2197" })
|
|
439
|
+
] }) : null,
|
|
440
|
+
onStartFresh ? /* @__PURE__ */ jsxs4(
|
|
441
|
+
"button",
|
|
442
|
+
{
|
|
443
|
+
type: "button",
|
|
444
|
+
className: "dx-wmenu__item dx-wmenu__item--danger",
|
|
445
|
+
onClick: onStartFresh,
|
|
446
|
+
children: [
|
|
447
|
+
/* @__PURE__ */ jsx4("span", { children: "Start fresh" }),
|
|
448
|
+
/* @__PURE__ */ jsx4("span", { className: "dx-wmenu__icon", "aria-hidden": "true", children: "\u27F5" })
|
|
449
|
+
]
|
|
450
|
+
}
|
|
451
|
+
) : null
|
|
452
|
+
] })
|
|
453
|
+
] });
|
|
454
|
+
}
|
|
455
|
+
|
|
326
456
|
// src/useIdentity.ts
|
|
327
457
|
import { useMemo as useMemo2 } from "react";
|
|
328
458
|
function useIdentity({ accountToken }) {
|
|
@@ -338,6 +468,8 @@ function useIdentity({ accountToken }) {
|
|
|
338
468
|
export {
|
|
339
469
|
DexterButton,
|
|
340
470
|
DexterMark,
|
|
471
|
+
DexterWalletChip,
|
|
472
|
+
DexterWalletMenu,
|
|
341
473
|
SignInWithDexter,
|
|
342
474
|
useDexterWallet,
|
|
343
475
|
useIdentity,
|
|
@@ -180,4 +180,4 @@ declare function syncAcceptedPasskeys(args: {
|
|
|
180
180
|
rpId?: string;
|
|
181
181
|
}): Promise<boolean>;
|
|
182
182
|
|
|
183
|
-
export { ACTIVE_WALLET_STORAGE_KEY as A, type
|
|
183
|
+
export { ACTIVE_WALLET_STORAGE_KEY as A, type ConnectVault as C, type DexterConnectConfig as D, type IdentityInput as I, type PasskeyLoginTokens as P, type ResolvedIdentity as R, type SignInResult as S, type CeremonyPhase 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 };
|