@dexterai/connect 0.14.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.
@@ -124,57 +124,8 @@ function onStorageEvent(e) {
124
124
  }
125
125
  var ACTIVE_WALLET_STORAGE_KEY = ACTIVE_HANDLE_KEY;
126
126
 
127
- // src/base64.ts
128
- function base64ToBytes(s) {
129
- const bin = atob(s);
130
- const out = new Uint8Array(bin.length);
131
- for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i);
132
- return out;
133
- }
134
- function base64urlToBytes(s) {
135
- const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - s.length % 4);
136
- const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + pad;
137
- return base64ToBytes(b64);
138
- }
139
- function bytesToBase64(bytes) {
140
- let bin = "";
141
- for (let i = 0; i < bytes.length; i += 1) bin += String.fromCharCode(bytes[i]);
142
- return btoa(bin);
143
- }
144
- function bytesToBase64url(bytes) {
145
- return bytesToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
146
- }
147
- function compactSignatureToDer(compact) {
148
- if (compact.length !== 64) {
149
- throw new Error(`expected 64-byte compact signature, got ${compact.length}`);
150
- }
151
- const r = derInteger(compact.subarray(0, 32));
152
- const s = derInteger(compact.subarray(32, 64));
153
- const body = new Uint8Array(r.length + s.length);
154
- body.set(r, 0);
155
- body.set(s, r.length);
156
- const out = new Uint8Array(2 + body.length);
157
- out[0] = 48;
158
- out[1] = body.length;
159
- out.set(body, 2);
160
- return out;
161
- }
162
- function derInteger(component) {
163
- let start = 0;
164
- while (start < component.length - 1 && component[start] === 0) start += 1;
165
- let content = component.subarray(start);
166
- if (content[0] & 128) {
167
- const padded = new Uint8Array(content.length + 1);
168
- padded[0] = 0;
169
- padded.set(content, 1);
170
- content = padded;
171
- }
172
- const out = new Uint8Array(2 + content.length);
173
- out[0] = 2;
174
- out[1] = content.length;
175
- out.set(content, 2);
176
- return out;
177
- }
127
+ // src/enroll.ts
128
+ import { startRegistration } from "@simplewebauthn/browser";
178
129
 
179
130
  // src/popup.ts
180
131
  var DEFAULT_CONNECT_HOST = "https://dexter.cash/connect";
@@ -282,9 +233,19 @@ async function createWallet(config = {}) {
282
233
  config.onPhase?.("challenge");
283
234
  const options = await fetchEnrollChallenge(apiBase);
284
235
  config.onPhase?.("passkey");
285
- const credential = await createCredential(options, name, rpId);
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
+ }
286
247
  config.onPhase?.("verifying");
287
- const enrolled = await submitEnrollComplete(apiBase, credential);
248
+ const enrolled = await submitEnrollComplete(apiBase, regResponse);
288
249
  config.onPhase?.("finalizing");
289
250
  const init = await initializeVault(apiBase, enrolled.userHandle, enrolled.credentialId);
290
251
  setActiveHandle(enrolled.userHandle, name, enrolled.credentialId);
@@ -318,38 +279,11 @@ async function fetchEnrollChallenge(apiBase) {
318
279
  }
319
280
  return data.options;
320
281
  }
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
- };
282
+ async function submitEnrollComplete(apiBase, response) {
349
283
  const res = await fetch(`${apiBase}/api/passkey-anon/enroll/complete`, {
350
284
  method: "POST",
351
285
  headers: { "content-type": "application/json" },
352
- body: JSON.stringify({ credential: credentialJson })
286
+ body: JSON.stringify({ credential: response })
353
287
  });
354
288
  if (!res.ok) throw new ConnectError(await readErrorCode(res), `enroll/complete ${res.status}`);
355
289
  return await res.json();
@@ -363,32 +297,6 @@ async function initializeVault(apiBase, userHandle, credentialId) {
363
297
  if (!res.ok) throw new ConnectError(await readErrorCode(res), `initialize ${res.status}`);
364
298
  return await res.json();
365
299
  }
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
300
  async function readErrorCode(res) {
393
301
  try {
394
302
  const body = await res.json();
@@ -399,6 +307,7 @@ async function readErrorCode(res) {
399
307
  }
400
308
 
401
309
  // src/relay.ts
310
+ import { startAuthentication, browserSupportsWebAuthn } from "@simplewebauthn/browser";
402
311
  var DEFAULT_API_BASE2 = "https://api.dexter.cash";
403
312
  var ANON_SIGN_BASE = "/api/passkey-anon/sign";
404
313
  async function passkeyLogin(config = {}, onPhase) {
@@ -408,13 +317,21 @@ async function passkeyLogin(config = {}, onPhase) {
408
317
  apiBase: config.apiBase
409
318
  });
410
319
  }
320
+ if (!browserSupportsWebAuthn()) {
321
+ throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
322
+ }
411
323
  const apiBase = (config.apiBase ?? DEFAULT_API_BASE2).replace(/\/$/, "");
412
324
  onPhase?.("challenge");
413
325
  const options = await fetchLoginChallenge(apiBase);
414
326
  onPhase?.("passkey");
415
- const credential = await getAssertion(options);
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
+ }
416
333
  onPhase?.("verifying");
417
- return submitLogin(apiBase, credential);
334
+ return submitLogin(apiBase, response);
418
335
  }
419
336
  async function continueWithDexter(config = {}, onPhase) {
420
337
  if (shouldUsePopup(config.transport)) {
@@ -446,47 +363,11 @@ async function fetchLoginChallenge(apiBase) {
446
363
  }
447
364
  return data.options;
448
365
  }
449
- async function getAssertion(options) {
450
- if (typeof navigator === "undefined" || !navigator.credentials) {
451
- throw new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment");
452
- }
453
- let credential;
454
- try {
455
- credential = await navigator.credentials.get({
456
- publicKey: {
457
- challenge: base64urlToBytes(options.challenge).buffer.slice(0),
458
- rpId: options.rpId,
459
- timeout: options.timeout ?? 6e4,
460
- userVerification: options.userVerification ?? "required"
461
- // No allowCredentials — discoverable resident-key login.
462
- }
463
- });
464
- } catch (err) {
465
- throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
466
- }
467
- if (!credential || credential.type !== "public-key") {
468
- throw new ConnectError("no_credential", "WebAuthn returned no credential");
469
- }
470
- return credential;
471
- }
472
- async function submitLogin(apiBase, credential) {
473
- const assertion = credential.response;
474
- const credentialJson = {
475
- id: credential.id,
476
- rawId: bytesToBase64url(new Uint8Array(credential.rawId)),
477
- type: credential.type,
478
- response: {
479
- clientDataJSON: bytesToBase64url(new Uint8Array(assertion.clientDataJSON)),
480
- authenticatorData: bytesToBase64url(new Uint8Array(assertion.authenticatorData)),
481
- signature: bytesToBase64url(new Uint8Array(assertion.signature)),
482
- userHandle: assertion.userHandle ? bytesToBase64url(new Uint8Array(assertion.userHandle)) : null
483
- },
484
- clientExtensionResults: credential.getClientExtensionResults?.() ?? {}
485
- };
366
+ async function submitLogin(apiBase, response) {
486
367
  const res = await fetch(`${apiBase}${ANON_SIGN_BASE}/passkey-login`, {
487
368
  method: "POST",
488
369
  headers: { "content-type": "application/json" },
489
- body: JSON.stringify({ credential: credentialJson })
370
+ body: JSON.stringify({ credential: response })
490
371
  });
491
372
  if (!res.ok) {
492
373
  throw new ConnectError(await readErrorCode2(res), `passkey-login ${res.status}`);
@@ -510,6 +391,58 @@ async function readErrorCode2(res) {
510
391
  return `http_${res.status}`;
511
392
  }
512
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
+
513
446
  // src/anon-policy.ts
514
447
  var DEFAULT_API_BASE3 = "https://api.dexter.cash";
515
448
  var ANON_SIGN_BASE2 = "/api/passkey-anon/sign";
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  subscribe,
21
21
  switchWallet,
22
22
  syncAcceptedPasskeys
23
- } from "./chunk-IUZBHWTP.js";
23
+ } from "./chunk-RLVMYLHC.js";
24
24
  export {
25
25
  ACTIVE_WALLET_STORAGE_KEY,
26
26
  ConnectError,
package/dist/react.js CHANGED
@@ -14,7 +14,7 @@ import {
14
14
  setActiveHandle,
15
15
  subscribe,
16
16
  switchWallet
17
- } from "./chunk-IUZBHWTP.js";
17
+ } from "./chunk-RLVMYLHC.js";
18
18
 
19
19
  // src/useSignInWithDexter.ts
20
20
  import { useCallback, useEffect, useMemo, useState } from "react";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dexterai/connect",
3
- "version": "0.14.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
  }