@dexterai/connect 0.13.0 → 0.15.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-RLVMYLHC.js} +299 -216
- package/dist/index.d.ts +35 -29
- package/dist/index.js +4 -142
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/{signals-DM7IQdq6.d.ts → signals-BuP-7inZ.d.ts} +1 -1
- package/package.json +4 -1
|
@@ -8,57 +8,124 @@ var ConnectError = class extends Error {
|
|
|
8
8
|
}
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
// src/
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
+
}
|
|
17
21
|
}
|
|
18
|
-
function
|
|
19
|
-
|
|
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
|
+
}
|
|
22
35
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
36
|
+
function writeRoster(wallets) {
|
|
37
|
+
if (!hasStorage()) return;
|
|
38
|
+
try {
|
|
39
|
+
window.localStorage.setItem(ROSTER_KEY, JSON.stringify(wallets));
|
|
40
|
+
} catch {
|
|
41
|
+
}
|
|
27
42
|
}
|
|
28
|
-
function
|
|
29
|
-
|
|
43
|
+
function emit() {
|
|
44
|
+
for (const l of listeners) {
|
|
45
|
+
try {
|
|
46
|
+
l();
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
30
50
|
}
|
|
31
|
-
function
|
|
32
|
-
if (
|
|
33
|
-
|
|
51
|
+
function getActiveHandle() {
|
|
52
|
+
if (!hasStorage()) return null;
|
|
53
|
+
try {
|
|
54
|
+
return window.localStorage.getItem(ACTIVE_HANDLE_KEY);
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
34
57
|
}
|
|
35
|
-
const r = derInteger(compact.subarray(0, 32));
|
|
36
|
-
const s = derInteger(compact.subarray(32, 64));
|
|
37
|
-
const body = new Uint8Array(r.length + s.length);
|
|
38
|
-
body.set(r, 0);
|
|
39
|
-
body.set(s, r.length);
|
|
40
|
-
const out = new Uint8Array(2 + body.length);
|
|
41
|
-
out[0] = 48;
|
|
42
|
-
out[1] = body.length;
|
|
43
|
-
out.set(body, 2);
|
|
44
|
-
return out;
|
|
45
58
|
}
|
|
46
|
-
function
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
padded[0] = 0;
|
|
53
|
-
padded.set(content, 1);
|
|
54
|
-
content = padded;
|
|
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;
|
|
55
65
|
}
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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));
|
|
61
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
|
+
|
|
127
|
+
// src/enroll.ts
|
|
128
|
+
import { startRegistration } from "@simplewebauthn/browser";
|
|
62
129
|
|
|
63
130
|
// src/popup.ts
|
|
64
131
|
var DEFAULT_CONNECT_HOST = "https://dexter.cash/connect";
|
|
@@ -145,8 +212,103 @@ function makeNonce() {
|
|
|
145
212
|
return `rid-${new URL(location.href).searchParams.get("v") ?? ""}-${typeof performance !== "undefined" ? performance.now() : 0}`;
|
|
146
213
|
}
|
|
147
214
|
|
|
148
|
-
// src/
|
|
215
|
+
// src/enroll.ts
|
|
149
216
|
var DEFAULT_API_BASE = "https://api.dexter.cash";
|
|
217
|
+
var DEFAULT_RP_ID = "dexter.cash";
|
|
218
|
+
var DEFAULT_WALLET_NAME = "Dexter Wallet";
|
|
219
|
+
async function createWallet(config = {}) {
|
|
220
|
+
if (shouldUsePopup(config.transport)) {
|
|
221
|
+
return openCeremonyPopup("create", {
|
|
222
|
+
connectHost: config.connectHost,
|
|
223
|
+
name: config.name,
|
|
224
|
+
apiBase: config.apiBase
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
if (typeof navigator === "undefined" || !navigator.credentials) {
|
|
228
|
+
throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
|
|
229
|
+
}
|
|
230
|
+
const apiBase = (config.apiBase ?? DEFAULT_API_BASE).replace(/\/$/, "");
|
|
231
|
+
const rpId = config.rpId ?? DEFAULT_RP_ID;
|
|
232
|
+
const name = config.name && config.name.trim() || DEFAULT_WALLET_NAME;
|
|
233
|
+
config.onPhase?.("challenge");
|
|
234
|
+
const options = await fetchEnrollChallenge(apiBase);
|
|
235
|
+
config.onPhase?.("passkey");
|
|
236
|
+
const optionsJSON = {
|
|
237
|
+
...options,
|
|
238
|
+
rp: { ...options.rp, id: options.rp.id ?? rpId, name: "Dexter" },
|
|
239
|
+
user: { ...options.user, name, displayName: name }
|
|
240
|
+
};
|
|
241
|
+
let regResponse;
|
|
242
|
+
try {
|
|
243
|
+
regResponse = await startRegistration({ optionsJSON });
|
|
244
|
+
} catch (err) {
|
|
245
|
+
throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
|
|
246
|
+
}
|
|
247
|
+
config.onPhase?.("verifying");
|
|
248
|
+
const enrolled = await submitEnrollComplete(apiBase, regResponse);
|
|
249
|
+
config.onPhase?.("finalizing");
|
|
250
|
+
const init = await initializeVault(apiBase, enrolled.userHandle, enrolled.credentialId);
|
|
251
|
+
setActiveHandle(enrolled.userHandle, name, enrolled.credentialId);
|
|
252
|
+
return {
|
|
253
|
+
handle: enrolled.userHandle,
|
|
254
|
+
credentialId: enrolled.credentialId,
|
|
255
|
+
vault: {
|
|
256
|
+
vaultPda: init.vaultPda,
|
|
257
|
+
swigAddress: init.swigStateAddress,
|
|
258
|
+
// FAIL SAFE: never invent a receive address — null until the server returns
|
|
259
|
+
// one (depositing to the config PDA would strand funds).
|
|
260
|
+
receiveAddress: init.receiveAddress ?? null,
|
|
261
|
+
usdcAta: null,
|
|
262
|
+
// swig not deployed yet (counterfactual pattern)
|
|
263
|
+
publicKey: enrolled.publicKey,
|
|
264
|
+
userHandle: enrolled.userHandle,
|
|
265
|
+
credentialId: enrolled.credentialId
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
async function fetchEnrollChallenge(apiBase) {
|
|
270
|
+
const res = await fetch(`${apiBase}/api/passkey-anon/enroll/challenge`, {
|
|
271
|
+
method: "POST",
|
|
272
|
+
headers: { "content-type": "application/json" },
|
|
273
|
+
body: "{}"
|
|
274
|
+
});
|
|
275
|
+
if (!res.ok) throw new ConnectError("enroll_challenge_failed", `enroll/challenge ${res.status}`);
|
|
276
|
+
const data = await res.json();
|
|
277
|
+
if (!data?.options?.challenge) {
|
|
278
|
+
throw new ConnectError("enroll_challenge_malformed", "no creation options in response");
|
|
279
|
+
}
|
|
280
|
+
return data.options;
|
|
281
|
+
}
|
|
282
|
+
async function submitEnrollComplete(apiBase, response) {
|
|
283
|
+
const res = await fetch(`${apiBase}/api/passkey-anon/enroll/complete`, {
|
|
284
|
+
method: "POST",
|
|
285
|
+
headers: { "content-type": "application/json" },
|
|
286
|
+
body: JSON.stringify({ credential: response })
|
|
287
|
+
});
|
|
288
|
+
if (!res.ok) throw new ConnectError(await readErrorCode(res), `enroll/complete ${res.status}`);
|
|
289
|
+
return await res.json();
|
|
290
|
+
}
|
|
291
|
+
async function initializeVault(apiBase, userHandle, credentialId) {
|
|
292
|
+
const res = await fetch(`${apiBase}/api/passkey-vault-anon/initialize`, {
|
|
293
|
+
method: "POST",
|
|
294
|
+
headers: { "content-type": "application/json" },
|
|
295
|
+
body: JSON.stringify({ userHandle, credentialId, coolingOffSeconds: 0 })
|
|
296
|
+
});
|
|
297
|
+
if (!res.ok) throw new ConnectError(await readErrorCode(res), `initialize ${res.status}`);
|
|
298
|
+
return await res.json();
|
|
299
|
+
}
|
|
300
|
+
async function readErrorCode(res) {
|
|
301
|
+
try {
|
|
302
|
+
const body = await res.json();
|
|
303
|
+
if (body?.error) return body.error;
|
|
304
|
+
} catch {
|
|
305
|
+
}
|
|
306
|
+
return `http_${res.status}`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// src/relay.ts
|
|
310
|
+
import { startAuthentication, browserSupportsWebAuthn } from "@simplewebauthn/browser";
|
|
311
|
+
var DEFAULT_API_BASE2 = "https://api.dexter.cash";
|
|
150
312
|
var ANON_SIGN_BASE = "/api/passkey-anon/sign";
|
|
151
313
|
async function passkeyLogin(config = {}, onPhase) {
|
|
152
314
|
if (shouldUsePopup(config.transport)) {
|
|
@@ -155,13 +317,36 @@ async function passkeyLogin(config = {}, onPhase) {
|
|
|
155
317
|
apiBase: config.apiBase
|
|
156
318
|
});
|
|
157
319
|
}
|
|
158
|
-
|
|
320
|
+
if (!browserSupportsWebAuthn()) {
|
|
321
|
+
throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
|
|
322
|
+
}
|
|
323
|
+
const apiBase = (config.apiBase ?? DEFAULT_API_BASE2).replace(/\/$/, "");
|
|
159
324
|
onPhase?.("challenge");
|
|
160
325
|
const options = await fetchLoginChallenge(apiBase);
|
|
161
326
|
onPhase?.("passkey");
|
|
162
|
-
|
|
327
|
+
let response;
|
|
328
|
+
try {
|
|
329
|
+
response = await startAuthentication({ optionsJSON: options });
|
|
330
|
+
} catch (err) {
|
|
331
|
+
throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
|
|
332
|
+
}
|
|
163
333
|
onPhase?.("verifying");
|
|
164
|
-
return submitLogin(apiBase,
|
|
334
|
+
return submitLogin(apiBase, response);
|
|
335
|
+
}
|
|
336
|
+
async function continueWithDexter(config = {}, onPhase) {
|
|
337
|
+
if (shouldUsePopup(config.transport)) {
|
|
338
|
+
return openCeremonyPopup("continue", {
|
|
339
|
+
connectHost: config.connectHost,
|
|
340
|
+
name: config.name,
|
|
341
|
+
apiBase: config.apiBase
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
if (getActiveHandle()) {
|
|
345
|
+
const result2 = await passkeyLogin(config, onPhase);
|
|
346
|
+
return { kind: "signin", ...result2 };
|
|
347
|
+
}
|
|
348
|
+
const result = await createWallet({ ...config, onPhase });
|
|
349
|
+
return { kind: "create", ...result };
|
|
165
350
|
}
|
|
166
351
|
async function fetchLoginChallenge(apiBase) {
|
|
167
352
|
const res = await fetch(`${apiBase}${ANON_SIGN_BASE}/login-challenge`, {
|
|
@@ -178,50 +363,14 @@ async function fetchLoginChallenge(apiBase) {
|
|
|
178
363
|
}
|
|
179
364
|
return data.options;
|
|
180
365
|
}
|
|
181
|
-
async function
|
|
182
|
-
if (typeof navigator === "undefined" || !navigator.credentials) {
|
|
183
|
-
throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
|
|
184
|
-
}
|
|
185
|
-
let credential;
|
|
186
|
-
try {
|
|
187
|
-
credential = await navigator.credentials.get({
|
|
188
|
-
publicKey: {
|
|
189
|
-
challenge: base64urlToBytes(options.challenge).buffer.slice(0),
|
|
190
|
-
rpId: options.rpId,
|
|
191
|
-
timeout: options.timeout ?? 6e4,
|
|
192
|
-
userVerification: options.userVerification ?? "required"
|
|
193
|
-
// No allowCredentials — discoverable resident-key login.
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
} catch (err) {
|
|
197
|
-
throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
|
|
198
|
-
}
|
|
199
|
-
if (!credential || credential.type !== "public-key") {
|
|
200
|
-
throw new ConnectError("no_credential", "WebAuthn returned no credential");
|
|
201
|
-
}
|
|
202
|
-
return credential;
|
|
203
|
-
}
|
|
204
|
-
async function submitLogin(apiBase, credential) {
|
|
205
|
-
const assertion = credential.response;
|
|
206
|
-
const credentialJson = {
|
|
207
|
-
id: credential.id,
|
|
208
|
-
rawId: bytesToBase64url(new Uint8Array(credential.rawId)),
|
|
209
|
-
type: credential.type,
|
|
210
|
-
response: {
|
|
211
|
-
clientDataJSON: bytesToBase64url(new Uint8Array(assertion.clientDataJSON)),
|
|
212
|
-
authenticatorData: bytesToBase64url(new Uint8Array(assertion.authenticatorData)),
|
|
213
|
-
signature: bytesToBase64url(new Uint8Array(assertion.signature)),
|
|
214
|
-
userHandle: assertion.userHandle ? bytesToBase64url(new Uint8Array(assertion.userHandle)) : null
|
|
215
|
-
},
|
|
216
|
-
clientExtensionResults: credential.getClientExtensionResults?.() ?? {}
|
|
217
|
-
};
|
|
366
|
+
async function submitLogin(apiBase, response) {
|
|
218
367
|
const res = await fetch(`${apiBase}${ANON_SIGN_BASE}/passkey-login`, {
|
|
219
368
|
method: "POST",
|
|
220
369
|
headers: { "content-type": "application/json" },
|
|
221
|
-
body: JSON.stringify({ credential:
|
|
370
|
+
body: JSON.stringify({ credential: response })
|
|
222
371
|
});
|
|
223
372
|
if (!res.ok) {
|
|
224
|
-
throw new ConnectError(await
|
|
373
|
+
throw new ConnectError(await readErrorCode2(res), `passkey-login ${res.status}`);
|
|
225
374
|
}
|
|
226
375
|
const data = await res.json();
|
|
227
376
|
const session = {
|
|
@@ -233,7 +382,7 @@ async function submitLogin(apiBase, credential) {
|
|
|
233
382
|
};
|
|
234
383
|
return data.vault ? { session, vault: data.vault } : { session };
|
|
235
384
|
}
|
|
236
|
-
async function
|
|
385
|
+
async function readErrorCode2(res) {
|
|
237
386
|
try {
|
|
238
387
|
const body = await res.json();
|
|
239
388
|
if (body?.error) return body.error;
|
|
@@ -242,10 +391,62 @@ async function readErrorCode(res) {
|
|
|
242
391
|
return `http_${res.status}`;
|
|
243
392
|
}
|
|
244
393
|
|
|
394
|
+
// src/base64.ts
|
|
395
|
+
function base64ToBytes(s) {
|
|
396
|
+
const bin = atob(s);
|
|
397
|
+
const out = new Uint8Array(bin.length);
|
|
398
|
+
for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i);
|
|
399
|
+
return out;
|
|
400
|
+
}
|
|
401
|
+
function base64urlToBytes(s) {
|
|
402
|
+
const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - s.length % 4);
|
|
403
|
+
const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + pad;
|
|
404
|
+
return base64ToBytes(b64);
|
|
405
|
+
}
|
|
406
|
+
function bytesToBase64(bytes) {
|
|
407
|
+
let bin = "";
|
|
408
|
+
for (let i = 0; i < bytes.length; i += 1) bin += String.fromCharCode(bytes[i]);
|
|
409
|
+
return btoa(bin);
|
|
410
|
+
}
|
|
411
|
+
function bytesToBase64url(bytes) {
|
|
412
|
+
return bytesToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
413
|
+
}
|
|
414
|
+
function compactSignatureToDer(compact) {
|
|
415
|
+
if (compact.length !== 64) {
|
|
416
|
+
throw new Error(`expected 64-byte compact signature, got ${compact.length}`);
|
|
417
|
+
}
|
|
418
|
+
const r = derInteger(compact.subarray(0, 32));
|
|
419
|
+
const s = derInteger(compact.subarray(32, 64));
|
|
420
|
+
const body = new Uint8Array(r.length + s.length);
|
|
421
|
+
body.set(r, 0);
|
|
422
|
+
body.set(s, r.length);
|
|
423
|
+
const out = new Uint8Array(2 + body.length);
|
|
424
|
+
out[0] = 48;
|
|
425
|
+
out[1] = body.length;
|
|
426
|
+
out.set(body, 2);
|
|
427
|
+
return out;
|
|
428
|
+
}
|
|
429
|
+
function derInteger(component) {
|
|
430
|
+
let start = 0;
|
|
431
|
+
while (start < component.length - 1 && component[start] === 0) start += 1;
|
|
432
|
+
let content = component.subarray(start);
|
|
433
|
+
if (content[0] & 128) {
|
|
434
|
+
const padded = new Uint8Array(content.length + 1);
|
|
435
|
+
padded[0] = 0;
|
|
436
|
+
padded.set(content, 1);
|
|
437
|
+
content = padded;
|
|
438
|
+
}
|
|
439
|
+
const out = new Uint8Array(2 + content.length);
|
|
440
|
+
out[0] = 2;
|
|
441
|
+
out[1] = content.length;
|
|
442
|
+
out.set(content, 2);
|
|
443
|
+
return out;
|
|
444
|
+
}
|
|
445
|
+
|
|
245
446
|
// src/anon-policy.ts
|
|
246
|
-
var
|
|
447
|
+
var DEFAULT_API_BASE3 = "https://api.dexter.cash";
|
|
247
448
|
var ANON_SIGN_BASE2 = "/api/passkey-anon/sign";
|
|
248
|
-
function createAnonServerPolicy(apiBase =
|
|
449
|
+
function createAnonServerPolicy(apiBase = DEFAULT_API_BASE3) {
|
|
249
450
|
const base = apiBase.replace(/\/$/, "");
|
|
250
451
|
return {
|
|
251
452
|
async issueChallenge({ userHandle, operationHash }) {
|
|
@@ -257,7 +458,7 @@ function createAnonServerPolicy(apiBase = DEFAULT_API_BASE2) {
|
|
|
257
458
|
operationHash: bytesToBase64url(operationHash)
|
|
258
459
|
})
|
|
259
460
|
});
|
|
260
|
-
if (!res.ok) throw new ConnectError(await
|
|
461
|
+
if (!res.ok) throw new ConnectError(await readErrorCode3(res), `challenge ${res.status}`);
|
|
261
462
|
const data = await res.json();
|
|
262
463
|
const options = data?.options;
|
|
263
464
|
if (!options?.challenge) {
|
|
@@ -294,7 +495,7 @@ function createAnonServerPolicy(apiBase = DEFAULT_API_BASE2) {
|
|
|
294
495
|
headers: { "content-type": "application/json" },
|
|
295
496
|
body: JSON.stringify({ credential, userHandle: bytesToBase64url(userHandle) })
|
|
296
497
|
});
|
|
297
|
-
if (!res.ok) throw new ConnectError(await
|
|
498
|
+
if (!res.ok) throw new ConnectError(await readErrorCode3(res), `verify ${res.status}`);
|
|
298
499
|
const data = await res.json();
|
|
299
500
|
if (data?.verified === false) {
|
|
300
501
|
throw new ConnectError("verification_failed", "server returned verified=false");
|
|
@@ -302,7 +503,7 @@ function createAnonServerPolicy(apiBase = DEFAULT_API_BASE2) {
|
|
|
302
503
|
}
|
|
303
504
|
};
|
|
304
505
|
}
|
|
305
|
-
async function
|
|
506
|
+
async function readErrorCode3(res) {
|
|
306
507
|
try {
|
|
307
508
|
const body = await res.json();
|
|
308
509
|
if (body?.error) return body.error;
|
|
@@ -336,122 +537,6 @@ function resolveIdentity(input) {
|
|
|
336
537
|
return { kind, userHandle, accountToken, hasPasskeyVault, hasAccount, hasWallet };
|
|
337
538
|
}
|
|
338
539
|
|
|
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
540
|
// src/phase.ts
|
|
456
541
|
var PHASE_LABEL = {
|
|
457
542
|
challenge: "Preparing\u2026",
|
|
@@ -525,14 +610,6 @@ async function syncAcceptedPasskeys(args) {
|
|
|
525
610
|
|
|
526
611
|
export {
|
|
527
612
|
ConnectError,
|
|
528
|
-
base64urlToBytes,
|
|
529
|
-
bytesToBase64url,
|
|
530
|
-
shouldUsePopup,
|
|
531
|
-
openCeremonyPopup,
|
|
532
|
-
passkeyLogin,
|
|
533
|
-
createAnonServerPolicy,
|
|
534
|
-
createPasskeySigner,
|
|
535
|
-
resolveIdentity,
|
|
536
613
|
getActiveHandle,
|
|
537
614
|
setActiveHandle,
|
|
538
615
|
getCredentialId,
|
|
@@ -542,6 +619,12 @@ export {
|
|
|
542
619
|
forgetWallet,
|
|
543
620
|
subscribe,
|
|
544
621
|
ACTIVE_WALLET_STORAGE_KEY,
|
|
622
|
+
createWallet,
|
|
623
|
+
passkeyLogin,
|
|
624
|
+
continueWithDexter,
|
|
625
|
+
createAnonServerPolicy,
|
|
626
|
+
createPasskeySigner,
|
|
627
|
+
resolveIdentity,
|
|
545
628
|
ceremonyPhaseLabel,
|
|
546
629
|
passkeySignalSupport,
|
|
547
630
|
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-RLVMYLHC.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';
|
package/dist/react.js
CHANGED
|
@@ -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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dexterai/connect",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Sign in with Dexter — passkey connector. Composes @dexterai/vault.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -32,5 +32,8 @@
|
|
|
32
32
|
"tsup": "^8.5.0",
|
|
33
33
|
"typescript": "^5.6.2",
|
|
34
34
|
"vitest": "^4.1.9"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@simplewebauthn/browser": "^13.3.0"
|
|
35
38
|
}
|
|
36
39
|
}
|