@dexterai/connect 0.24.3 → 0.25.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.
@@ -345,8 +345,127 @@ async function initializeVault(apiBase, userHandle, credentialId, spendPolicy, l
345
345
 
346
346
  // src/relay.ts
347
347
  import { startAuthentication, browserSupportsWebAuthn } from "@simplewebauthn/browser";
348
+
349
+ // src/base64.ts
350
+ function base64ToBytes(s) {
351
+ const bin = atob(s);
352
+ const out = new Uint8Array(bin.length);
353
+ for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i);
354
+ return out;
355
+ }
356
+ function base64urlToBytes(s) {
357
+ const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - s.length % 4);
358
+ const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + pad;
359
+ return base64ToBytes(b64);
360
+ }
361
+ function bytesToBase64(bytes) {
362
+ let bin = "";
363
+ for (let i = 0; i < bytes.length; i += 1) bin += String.fromCharCode(bytes[i]);
364
+ return btoa(bin);
365
+ }
366
+ function bytesToBase64url(bytes) {
367
+ return bytesToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
368
+ }
369
+ function base64urlToBase64(s) {
370
+ const t = s.replace(/-/g, "+").replace(/_/g, "/");
371
+ return t + "=".repeat((4 - t.length % 4) % 4);
372
+ }
373
+ function compactSignatureToDer(compact) {
374
+ if (compact.length !== 64) {
375
+ throw new Error(`expected 64-byte compact signature, got ${compact.length}`);
376
+ }
377
+ const r = derInteger(compact.subarray(0, 32));
378
+ const s = derInteger(compact.subarray(32, 64));
379
+ const body = new Uint8Array(r.length + s.length);
380
+ body.set(r, 0);
381
+ body.set(s, r.length);
382
+ const out = new Uint8Array(2 + body.length);
383
+ out[0] = 48;
384
+ out[1] = body.length;
385
+ out.set(body, 2);
386
+ return out;
387
+ }
388
+ function derInteger(component) {
389
+ let start = 0;
390
+ while (start < component.length - 1 && component[start] === 0) start += 1;
391
+ let content = component.subarray(start);
392
+ if (content[0] & 128) {
393
+ const padded = new Uint8Array(content.length + 1);
394
+ padded[0] = 0;
395
+ padded.set(content, 1);
396
+ content = padded;
397
+ }
398
+ const out = new Uint8Array(2 + content.length);
399
+ out[0] = 2;
400
+ out[1] = content.length;
401
+ out.set(content, 2);
402
+ return out;
403
+ }
404
+
405
+ // src/immediate.ts
406
+ var immediateCapPromise = null;
407
+ function immediateGetSupported() {
408
+ if (!immediateCapPromise) {
409
+ immediateCapPromise = (async () => {
410
+ try {
411
+ if (typeof window === "undefined") return false;
412
+ const pkc2 = window.PublicKeyCredential;
413
+ if (!pkc2 || typeof pkc2.getClientCapabilities !== "function") return false;
414
+ const caps = await pkc2.getClientCapabilities.call(pkc2);
415
+ return caps?.immediateGet === true;
416
+ } catch {
417
+ return false;
418
+ }
419
+ })();
420
+ }
421
+ return immediateCapPromise;
422
+ }
423
+ function primeImmediateSupport() {
424
+ void immediateGetSupported();
425
+ }
426
+ async function immediateAuthentication(options) {
427
+ const getOptions = {
428
+ publicKey: {
429
+ challenge: base64urlToBytes(options.challenge),
430
+ rpId: options.rpId,
431
+ timeout: options.timeout,
432
+ userVerification: options.userVerification,
433
+ allowCredentials: options.allowCredentials?.map((c) => ({
434
+ id: base64urlToBytes(c.id),
435
+ type: c.type,
436
+ transports: c.transports
437
+ }))
438
+ }
439
+ };
440
+ getOptions.uiMode = "immediate";
441
+ const cred = await navigator.credentials.get(getOptions);
442
+ if (!cred) throw new DOMException("no credential", "NotAllowedError");
443
+ const a = cred.response;
444
+ return {
445
+ id: cred.id,
446
+ rawId: bytesToBase64url(new Uint8Array(cred.rawId)),
447
+ response: {
448
+ clientDataJSON: bytesToBase64url(new Uint8Array(a.clientDataJSON)),
449
+ authenticatorData: bytesToBase64url(new Uint8Array(a.authenticatorData)),
450
+ signature: bytesToBase64url(new Uint8Array(a.signature)),
451
+ userHandle: a.userHandle ? bytesToBase64url(new Uint8Array(a.userHandle)) : void 0
452
+ },
453
+ clientExtensionResults: cred.getClientExtensionResults?.() ?? {},
454
+ type: "public-key",
455
+ authenticatorAttachment: cred.authenticatorAttachment ?? void 0
456
+ };
457
+ }
458
+ function classifyWebAuthnRejection(err) {
459
+ const e = err;
460
+ if (!e) return false;
461
+ const blob = [e.name, e.cause?.name, typeof e.code === "string" ? e.code : "", e.message].filter(Boolean).join(" ").toLowerCase();
462
+ return /notallowed|not ?allowed|abort|cancel|timed out|timeout|denied|ceremony/.test(blob);
463
+ }
464
+
465
+ // src/relay.ts
348
466
  var DEFAULT_API_BASE2 = "https://api.dexter.cash";
349
467
  var ANON_SIGN_BASE = "/api/passkey-anon/sign";
468
+ primeImmediateSupport();
350
469
  async function passkeyLogin(config = {}, onPhase) {
351
470
  if (shouldUsePopup(config.transport)) {
352
471
  const result2 = await openCeremonyPopup("signin", {
@@ -380,24 +499,56 @@ async function passkeyLogin(config = {}, onPhase) {
380
499
  }
381
500
  async function continueWithDexter(config = {}, onPhase) {
382
501
  if (shouldUsePopup(config.transport)) {
383
- const result2 = await openCeremonyPopup("continue", {
502
+ const result = await openCeremonyPopup("continue", {
384
503
  connectHost: config.connectHost,
385
504
  name: config.name,
386
505
  apiBase: config.apiBase
387
506
  });
388
- if (result2.kind === "create") {
389
- setActiveHandle(result2.handle, result2.label ?? config.name, result2.credentialId);
390
- } else if (result2.vault) {
391
- setActiveHandle(result2.vault.userHandle, result2.vault.walletLabel ?? void 0, result2.vault.credentialId);
507
+ if (result.kind === "create") {
508
+ setActiveHandle(result.handle, result.label ?? config.name, result.credentialId);
509
+ } else if (result.kind === "signin" && result.vault) {
510
+ setActiveHandle(result.vault.userHandle, result.vault.walletLabel ?? void 0, result.vault.credentialId);
392
511
  }
393
- return result2;
512
+ return result;
513
+ }
514
+ if (await immediateGetSupported()) {
515
+ const probe = await immediatePasskeyLogin(config, onPhase);
516
+ if (probe.outcome === "signin") return { kind: "signin", ...probe.result };
517
+ if (probe.outcome === "cancelled") return { kind: "cancelled" };
518
+ if (!config.spendPolicy) return { kind: "needs_create" };
519
+ const created = await createWallet({ ...config, onPhase });
520
+ return { kind: "create", ...created };
394
521
  }
395
522
  if (getActiveHandle()) {
396
- const result2 = await passkeyLogin(config, onPhase);
397
- return { kind: "signin", ...result2 };
523
+ const result = await passkeyLogin(config, onPhase);
524
+ return { kind: "signin", ...result };
525
+ }
526
+ return { kind: "needs_choice" };
527
+ }
528
+ async function immediatePasskeyLogin(config, onPhase) {
529
+ const apiBase = (config.apiBase ?? DEFAULT_API_BASE2).replace(/\/$/, "");
530
+ onPhase?.("challenge");
531
+ const options = await fetchLoginChallenge(apiBase);
532
+ onPhase?.("passkey");
533
+ let response;
534
+ try {
535
+ response = await immediateAuthentication(options);
536
+ } catch (err) {
537
+ if (classifyWebAuthnRejection(err)) {
538
+ return { outcome: "no_credential" };
539
+ }
540
+ throw new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err));
541
+ }
542
+ onPhase?.("verifying");
543
+ const result = await submitLogin(apiBase, response);
544
+ if (result.vault) {
545
+ setActiveHandle(
546
+ result.vault.userHandle,
547
+ result.vault.walletLabel ?? void 0,
548
+ result.vault.credentialId
549
+ );
398
550
  }
399
- const result = await createWallet({ ...config, onPhase });
400
- return { kind: "create", ...result };
551
+ return { outcome: "signin", result };
401
552
  }
402
553
  async function fetchLoginChallenge(apiBase) {
403
554
  const res = await fetch(`${apiBase}${ANON_SIGN_BASE}/login-challenge`, {
@@ -434,62 +585,6 @@ async function submitLogin(apiBase, response) {
434
585
  return data.vault ? { session, vault: data.vault } : { session };
435
586
  }
436
587
 
437
- // src/base64.ts
438
- function base64ToBytes(s) {
439
- const bin = atob(s);
440
- const out = new Uint8Array(bin.length);
441
- for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i);
442
- return out;
443
- }
444
- function base64urlToBytes(s) {
445
- const pad = s.length % 4 === 0 ? "" : "=".repeat(4 - s.length % 4);
446
- const b64 = s.replace(/-/g, "+").replace(/_/g, "/") + pad;
447
- return base64ToBytes(b64);
448
- }
449
- function bytesToBase64(bytes) {
450
- let bin = "";
451
- for (let i = 0; i < bytes.length; i += 1) bin += String.fromCharCode(bytes[i]);
452
- return btoa(bin);
453
- }
454
- function bytesToBase64url(bytes) {
455
- return bytesToBase64(bytes).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
456
- }
457
- function base64urlToBase64(s) {
458
- const t = s.replace(/-/g, "+").replace(/_/g, "/");
459
- return t + "=".repeat((4 - t.length % 4) % 4);
460
- }
461
- function compactSignatureToDer(compact) {
462
- if (compact.length !== 64) {
463
- throw new Error(`expected 64-byte compact signature, got ${compact.length}`);
464
- }
465
- const r = derInteger(compact.subarray(0, 32));
466
- const s = derInteger(compact.subarray(32, 64));
467
- const body = new Uint8Array(r.length + s.length);
468
- body.set(r, 0);
469
- body.set(s, r.length);
470
- const out = new Uint8Array(2 + body.length);
471
- out[0] = 48;
472
- out[1] = body.length;
473
- out.set(body, 2);
474
- return out;
475
- }
476
- function derInteger(component) {
477
- let start = 0;
478
- while (start < component.length - 1 && component[start] === 0) start += 1;
479
- let content = component.subarray(start);
480
- if (content[0] & 128) {
481
- const padded = new Uint8Array(content.length + 1);
482
- padded[0] = 0;
483
- padded.set(content, 1);
484
- content = padded;
485
- }
486
- const out = new Uint8Array(2 + content.length);
487
- out[0] = 2;
488
- out[1] = content.length;
489
- out.set(content, 2);
490
- return out;
491
- }
492
-
493
588
  // src/anon-policy.ts
494
589
  var DEFAULT_API_BASE3 = "https://api.dexter.cash";
495
590
  var ANON_SIGN_BASE2 = "/api/passkey-anon/sign";
@@ -579,68 +674,6 @@ function resolveIdentity(input) {
579
674
 
580
675
  // src/recover.ts
581
676
  import { startAuthentication as startAuthentication2, browserSupportsWebAuthn as browserSupportsWebAuthn2 } from "@simplewebauthn/browser";
582
-
583
- // src/immediate.ts
584
- var immediateCapPromise = null;
585
- function immediateGetSupported() {
586
- if (!immediateCapPromise) {
587
- immediateCapPromise = (async () => {
588
- try {
589
- if (typeof window === "undefined") return false;
590
- const pkc2 = window.PublicKeyCredential;
591
- if (!pkc2 || typeof pkc2.getClientCapabilities !== "function") return false;
592
- const caps = await pkc2.getClientCapabilities.call(pkc2);
593
- return caps?.immediateGet === true;
594
- } catch {
595
- return false;
596
- }
597
- })();
598
- }
599
- return immediateCapPromise;
600
- }
601
- function primeImmediateSupport() {
602
- void immediateGetSupported();
603
- }
604
- async function immediateAuthentication(options) {
605
- const getOptions = {
606
- publicKey: {
607
- challenge: base64urlToBytes(options.challenge),
608
- rpId: options.rpId,
609
- timeout: options.timeout,
610
- userVerification: options.userVerification,
611
- allowCredentials: options.allowCredentials?.map((c) => ({
612
- id: base64urlToBytes(c.id),
613
- type: c.type,
614
- transports: c.transports
615
- }))
616
- }
617
- };
618
- getOptions.uiMode = "immediate";
619
- const cred = await navigator.credentials.get(getOptions);
620
- if (!cred) throw new DOMException("no credential", "NotAllowedError");
621
- const a = cred.response;
622
- return {
623
- id: cred.id,
624
- rawId: bytesToBase64url(new Uint8Array(cred.rawId)),
625
- response: {
626
- clientDataJSON: bytesToBase64url(new Uint8Array(a.clientDataJSON)),
627
- authenticatorData: bytesToBase64url(new Uint8Array(a.authenticatorData)),
628
- signature: bytesToBase64url(new Uint8Array(a.signature)),
629
- userHandle: a.userHandle ? bytesToBase64url(new Uint8Array(a.userHandle)) : void 0
630
- },
631
- clientExtensionResults: cred.getClientExtensionResults?.() ?? {},
632
- type: "public-key",
633
- authenticatorAttachment: cred.authenticatorAttachment ?? void 0
634
- };
635
- }
636
- function classifyWebAuthnRejection(err) {
637
- const e = err;
638
- if (!e) return false;
639
- const blob = [e.name, e.cause?.name, typeof e.code === "string" ? e.code : "", e.message].filter(Boolean).join(" ").toLowerCase();
640
- return /notallowed|not ?allowed|abort|cancel|timed out|timeout|denied|ceremony/.test(blob);
641
- }
642
-
643
- // src/recover.ts
644
677
  var DEFAULT_API_BASE4 = "https://api.dexter.cash";
645
678
  var ANON_SIGN_BASE3 = "/api/passkey-anon/sign";
646
679
  primeImmediateSupport();
@@ -838,10 +871,10 @@ export {
838
871
  usdToAtomic,
839
872
  authoredPolicy,
840
873
  createWallet,
841
- passkeyLogin,
842
- continueWithDexter,
843
874
  bytesToBase64url,
844
875
  base64urlToBase64,
876
+ passkeyLogin,
877
+ continueWithDexter,
845
878
  createAnonServerPolicy,
846
879
  createPasskeySigner,
847
880
  resolveIdentity,
package/dist/index.d.ts CHANGED
@@ -1,28 +1,7 @@
1
- import { S as SignInResult, C as CreateWalletResult, a as CreateWalletConfig, b as CeremonyPhase, D as DexterConnectConfig, c as ConnectVault, R as RecoverWalletConfig, d as RecoverOutcome, I as IdentityKind } from './signals-C05rIVey.js';
2
- export { A as ACTIVE_WALLET_STORAGE_KEY, e as ConnectError, f as IdentityInput, P as PasskeyLoginTokens, g as PasskeySignalSupport, h as RecoverVault, i as ResolvedIdentity, j as SESSION_TTL_30D, k as SpendPolicy, l as StoredWallet, m as authoredPolicy, n as createWallet, o as ejectActiveWallet, p as forgetWallet, q as getActiveHandle, r as getCredentialId, s as listWallets, t as passkeySignalSupport, u as prunePasskey, v as renamePasskey, w as resolveIdentity, x as setActiveHandle, y as subscribeWallet, z as switchWallet, B as syncAcceptedPasskeys, E as usdToAtomic } from './signals-C05rIVey.js';
1
+ import { C as ConnectVault, R as RecoverWalletConfig, a as RecoverOutcome, b as CeremonyPhase, I as IdentityKind } from './signals-C4JSE_4X.js';
2
+ export { A as ACTIVE_WALLET_STORAGE_KEY, c as ConnectError, d as ContinueResult, e as CreateWalletConfig, f as CreateWalletResult, D as DexterConnectConfig, g as IdentityInput, P as PasskeyLoginTokens, h as PasskeySignalSupport, i as RecoverVault, j as ResolvedIdentity, S as SESSION_TTL_30D, k as SignInResult, l as SpendPolicy, m as StoredWallet, n as authoredPolicy, o as continueWithDexter, p as createWallet, q as ejectActiveWallet, r as forgetWallet, s as getActiveHandle, t as getCredentialId, u as listWallets, v as passkeyLogin, w as passkeySignalSupport, x as prunePasskey, y as renamePasskey, z as resolveIdentity, B as setActiveHandle, E as subscribeWallet, F as switchWallet, G as syncAcceptedPasskeys, H as usdToAtomic } from './signals-C4JSE_4X.js';
3
3
  import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
4
4
 
5
- /**
6
- * "Sign in with Dexter" — the discoverable-credential login ceremony.
7
- *
8
- * 1. POST /login-challenge → a server-issued challenge (no allow-list:
9
- * the resident passkey itself identifies the user)
10
- * 2. navigator.credentials.get over that challenge (no allowCredentials)
11
- * 3. POST /passkey-login → the server resolves the credential + vault,
12
- * verifies the assertion, and returns a Supabase session (+ the vault
13
- * payload once vault-review ships the dexter-api change — ASK 1)
14
- *
15
- * Relays to dexter-api's ANON router — a first-time third-party user has no
16
- * Supabase session, so the Supabase-gated router would 401.
17
- */
18
- declare function passkeyLogin(config?: DexterConnectConfig, onPhase?: (phase: CeremonyPhase) => void): Promise<SignInResult>;
19
- type ContinueResult = ({
20
- kind: 'signin';
21
- } & SignInResult) | ({
22
- kind: 'create';
23
- } & CreateWalletResult);
24
- declare function continueWithDexter(config?: CreateWalletConfig, onPhase?: (phase: CeremonyPhase) => void): Promise<ContinueResult>;
25
-
26
5
  /** What `issueChallenge` returns to the SDK signer. */
27
6
  interface AnonChallengeResult {
28
7
  /** Server-issued WebAuthn challenge (=== the supplied operationHash). */
@@ -223,4 +202,4 @@ interface EnableAgentSpendResult {
223
202
  */
224
203
  declare function enableAgentSpend(id: AgentSpendIdentity, vaultPda: string, apiOrigin: string, credentialId?: string | null): Promise<EnableAgentSpendResult>;
225
204
 
226
- export { AgentSpendError, type AgentSpendIdentity, type AgentSpendStatus, type AgentSpendTab, type AnonChallengeResult, type AnonServerPolicy, type AutomaticAgentSpend, CeremonyPhase, ConnectVault, type ContinueResult, CreateWalletConfig, CreateWalletResult, DexterConnectConfig, type EnableAgentSpendResult, IdentityKind, type RawAgentSpendSession, type RawAgentSpendStatus, RecoverOutcome, RecoverWalletConfig, type RevokeAgentSpendResult, SignInResult, assembleAgentSpendStatus, ceremonyPhaseLabel, continueWithDexter, createAnonServerPolicy, createPasskeySigner, describeAgentSpendError, enableAgentSpend, passkeyLogin, recoverWallet, revokeAgentSpend };
205
+ export { AgentSpendError, type AgentSpendIdentity, type AgentSpendStatus, type AgentSpendTab, type AnonChallengeResult, type AnonServerPolicy, type AutomaticAgentSpend, CeremonyPhase, ConnectVault, type EnableAgentSpendResult, IdentityKind, type RawAgentSpendSession, type RawAgentSpendStatus, RecoverOutcome, RecoverWalletConfig, type RevokeAgentSpendResult, assembleAgentSpendStatus, ceremonyPhaseLabel, createAnonServerPolicy, createPasskeySigner, describeAgentSpendError, enableAgentSpend, recoverWallet, revokeAgentSpend };
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  switchWallet,
27
27
  syncAcceptedPasskeys,
28
28
  usdToAtomic
29
- } from "./chunk-P3ZBIO7N.js";
29
+ } from "./chunk-VGERNAQ3.js";
30
30
 
31
31
  // src/agentSpend.ts
32
32
  import { PublicKey } from "@solana/web3.js";
package/dist/react.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
2
- import { b as CeremonyPhase, S as SignInResult, d as RecoverOutcome, P as PasskeyLoginTokens, c as ConnectVault, e as ConnectError, l as StoredWallet, g as PasskeySignalSupport, i as ResolvedIdentity, C as CreateWalletResult } from './signals-C05rIVey.js';
3
- export { h as RecoverVault } from './signals-C05rIVey.js';
2
+ import { b as CeremonyPhase, k as SignInResult, a as RecoverOutcome, d as ContinueResult, P as PasskeyLoginTokens, C as ConnectVault, c as ConnectError, m as StoredWallet, h as PasskeySignalSupport, j as ResolvedIdentity, f as CreateWalletResult } from './signals-C4JSE_4X.js';
3
+ export { i as RecoverVault } from './signals-C4JSE_4X.js';
4
4
  import { ReactElement, ButtonHTMLAttributes, ReactNode } from 'react';
5
5
 
6
6
  type ConnectStatus = 'idle' | 'pending' | 'done' | 'error';
@@ -35,6 +35,10 @@ interface UseSignInWithDexter {
35
35
  * light up via the wallet store; `session`/`vault` here stay null. Fire on
36
36
  * tap only — never on mount (iOS gesture rule). */
37
37
  recover: () => Promise<RecoverOutcome>;
38
+ /** One-button register-or-sign-in (keychain-first; see continueWithDexter).
39
+ * Terminal kinds update session/vault state; needs_create / needs_choice /
40
+ * cancelled return with status back at idle for the caller to route. */
41
+ continueWith: () => Promise<ContinueResult>;
38
42
  /** Last recover outcome; null until recover() settles. */
39
43
  recovered: RecoverOutcome | null;
40
44
  disconnect: () => void;
@@ -68,14 +72,21 @@ interface SignInWithDexterProps extends UseSignInWithDexterConfig {
68
72
  * 'recover' = wallet-only: re-points this browser at an existing wallet and
69
73
  * mints NOTHING else (the wallet IS the sign-in). After a successful
70
74
  * recover the element renders null — identity display belongs to
71
- * DexterWalletChip/useIdentity, which light up via the wallet store. */
72
- mode?: 'signin' | 'recover';
75
+ * DexterWalletChip/useIdentity, which light up via the wallet store.
76
+ * 'continue' = the one-button register-or-sign-in (keychain-first): the
77
+ * hosted popup decides sign-in vs create; every settled outcome fires
78
+ * onContinue. */
79
+ mode?: 'signin' | 'recover' | 'continue';
73
80
  /** Fired with the result the moment sign-in completes. */
74
81
  onSuccess?: (result: SignInResult) => void;
75
82
  /** Recover mode: fired with EVERY settled outcome (ok, no_credential,
76
83
  * cancelled, error) — the single channel; onError is not used in this mode.
77
84
  * Branch on `reason`: no_credential → offer create; cancelled → stay silent. */
78
85
  onRecovered?: (outcome: RecoverOutcome) => void;
86
+ /** Continue mode: fired with EVERY settled ContinueResult (signin, create,
87
+ * needs_create, needs_choice, cancelled) — the single channel for routing.
88
+ * Real ceremony failures still go to onError. */
89
+ onContinue?: (result: ContinueResult) => void;
79
90
  /** Fired with the typed error if the ceremony fails. */
80
91
  onError?: (error: ConnectError) => void;
81
92
  /** Button label when signed out. Default "Sign in with Dexter". */
package/dist/react.js CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  ConnectError,
3
3
  authoredPolicy,
4
4
  ceremonyPhaseLabel,
5
+ continueWithDexter,
5
6
  createPasskeySigner,
6
7
  createWallet,
7
8
  ejectActiveWallet,
@@ -17,7 +18,7 @@ import {
17
18
  setActiveHandle,
18
19
  subscribe,
19
20
  switchWallet
20
- } from "./chunk-P3ZBIO7N.js";
21
+ } from "./chunk-VGERNAQ3.js";
21
22
  import {
22
23
  DexterButton,
23
24
  DexterMark,
@@ -130,6 +131,39 @@ function useSignInWithDexter(config = {}) {
130
131
  }
131
132
  return outcome;
132
133
  }, [apiBase, transport, connectHost, preferImmediate]);
134
+ const continueWith = useCallback(async () => {
135
+ setError(null);
136
+ setPhase(null);
137
+ setStatus("pending");
138
+ try {
139
+ const result = await continueWithDexter(
140
+ {
141
+ ...apiBase ? { apiBase } : {},
142
+ ...transport ? { transport } : {},
143
+ ...connectHost ? { connectHost } : {}
144
+ },
145
+ setPhase
146
+ );
147
+ setPhase(null);
148
+ if (result.kind === "signin") {
149
+ setSession(result.session);
150
+ setVault(result.vault ?? null);
151
+ setStatus("done");
152
+ } else if (result.kind === "create") {
153
+ setVault(result.vault);
154
+ setStatus("done");
155
+ } else {
156
+ setStatus("idle");
157
+ }
158
+ return result;
159
+ } catch (err) {
160
+ const e = err instanceof ConnectError ? err : new ConnectError("continue_failed", String(err));
161
+ setError(e);
162
+ setStatus("error");
163
+ setPhase(null);
164
+ throw e;
165
+ }
166
+ }, [apiBase, transport, connectHost]);
133
167
  const disconnect = useCallback(() => {
134
168
  setSession(null);
135
169
  setVault(null);
@@ -151,6 +185,7 @@ function useSignInWithDexter(config = {}) {
151
185
  isVaultConnected: status === "done" && vault !== null,
152
186
  signIn,
153
187
  recover,
188
+ continueWith,
154
189
  recovered,
155
190
  disconnect,
156
191
  session,
@@ -180,6 +215,7 @@ function SignInWithDexter(props) {
180
215
  onSuccess,
181
216
  onError,
182
217
  onRecovered,
218
+ onContinue,
183
219
  label = "Sign in with Dexter",
184
220
  variant = "primary",
185
221
  block = false,
@@ -195,6 +231,14 @@ function SignInWithDexter(props) {
195
231
  onRecovered?.(await c.recover());
196
232
  return;
197
233
  }
234
+ if (mode === "continue") {
235
+ try {
236
+ onContinue?.(await c.continueWith());
237
+ } catch (err) {
238
+ onError?.(err);
239
+ }
240
+ return;
241
+ }
198
242
  try {
199
243
  onSuccess?.(await c.signIn());
200
244
  } catch (err) {
@@ -687,7 +731,7 @@ function vscodeInstallUrl(name, mcpUrl) {
687
731
  return `vscode:mcp/install?${encodeURIComponent(JSON.stringify({ name, type: "http", url: mcpUrl }))}`;
688
732
  }
689
733
  function hermesInstallCommand(name, mcpUrl) {
690
- return `hermes mcp add ${name} --url ${mcpUrl} --auth oauth`;
734
+ return `hermes mcp add ${name} --url ${mcpUrl}`;
691
735
  }
692
736
  function claudeCodeInstallCommand(name, mcpUrl) {
693
737
  return `claude mcp add --transport http ${name} ${mcpUrl}`;
@@ -159,6 +159,42 @@ interface CreateWalletResult {
159
159
  */
160
160
  declare function createWallet(config?: CreateWalletConfig): Promise<CreateWalletResult>;
161
161
 
162
+ /**
163
+ * "Sign in with Dexter" — the discoverable-credential login ceremony.
164
+ *
165
+ * 1. POST /login-challenge → a server-issued challenge (no allow-list:
166
+ * the resident passkey itself identifies the user)
167
+ * 2. navigator.credentials.get over that challenge (no allowCredentials)
168
+ * 3. POST /passkey-login → the server resolves the credential + vault,
169
+ * verifies the assertion, and returns a Supabase session (+ the vault
170
+ * payload once vault-review ships the dexter-api change — ASK 1)
171
+ *
172
+ * Relays to dexter-api's ANON router — a first-time third-party user has no
173
+ * Supabase session, so the Supabase-gated router would 401.
174
+ */
175
+ declare function passkeyLogin(config?: DexterConnectConfig, onPhase?: (phase: CeremonyPhase) => void): Promise<SignInResult>;
176
+ type ContinueResult = ({
177
+ kind: 'signin';
178
+ } & SignInResult) | ({
179
+ kind: 'create';
180
+ } & CreateWalletResult)
181
+ /** This device has no passkey (proven by an immediate fast-fail) but the
182
+ * caller supplied no authored spendPolicy — collect name + allowance,
183
+ * then call createWallet. */
184
+ | {
185
+ kind: 'needs_create';
186
+ }
187
+ /** Cannot silently probe (no immediate support, no local handle): render an
188
+ * explicit "Sign in" / "I'm new" choice. Never guess. */
189
+ | {
190
+ kind: 'needs_choice';
191
+ }
192
+ /** The user dismissed the passkey sheet. Not an error — stay quiet. */
193
+ | {
194
+ kind: 'cancelled';
195
+ };
196
+ declare function continueWithDexter(config?: CreateWalletConfig, onPhase?: (phase: CeremonyPhase) => void): Promise<ContinueResult>;
197
+
162
198
  type IdentityKind = 'passkey-vault' | 'account' | 'none';
163
199
  interface IdentityInput {
164
200
  /** An account session token (e.g. Supabase access_token) when present, else null. */
@@ -286,4 +322,4 @@ declare function syncAcceptedPasskeys(args: {
286
322
  rpId?: string;
287
323
  }): Promise<boolean>;
288
324
 
289
- export { ACTIVE_WALLET_STORAGE_KEY as A, syncAcceptedPasskeys as B, type CreateWalletResult as C, type DexterConnectConfig as D, usdToAtomic as E, type IdentityKind as I, type PasskeyLoginTokens as P, type RecoverWalletConfig as R, type SignInResult as S, type CreateWalletConfig as a, type CeremonyPhase as b, type ConnectVault as c, type RecoverOutcome as d, ConnectError as e, type IdentityInput as f, type PasskeySignalSupport as g, type RecoverVault as h, type ResolvedIdentity as i, SESSION_TTL_30D as j, type SpendPolicy as k, type StoredWallet as l, authoredPolicy as m, createWallet as n, ejectActiveWallet as o, forgetWallet as p, getActiveHandle as q, getCredentialId as r, listWallets as s, passkeySignalSupport as t, prunePasskey as u, renamePasskey as v, resolveIdentity as w, setActiveHandle as x, subscribe as y, switchWallet as z };
325
+ export { ACTIVE_WALLET_STORAGE_KEY as A, setActiveHandle as B, type ConnectVault as C, type DexterConnectConfig as D, subscribe as E, switchWallet as F, syncAcceptedPasskeys as G, usdToAtomic as H, type IdentityKind as I, type PasskeyLoginTokens as P, type RecoverWalletConfig as R, SESSION_TTL_30D as S, type RecoverOutcome as a, type CeremonyPhase as b, ConnectError as c, type ContinueResult as d, type CreateWalletConfig as e, type CreateWalletResult as f, type IdentityInput as g, type PasskeySignalSupport as h, type RecoverVault as i, type ResolvedIdentity as j, type SignInResult as k, type SpendPolicy as l, type StoredWallet as m, authoredPolicy as n, continueWithDexter as o, createWallet as p, ejectActiveWallet as q, forgetWallet as r, getActiveHandle as s, getCredentialId as t, listWallets as u, passkeyLogin as v, passkeySignalSupport as w, prunePasskey as x, renamePasskey as y, resolveIdentity as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dexterai/connect",
3
- "version": "0.24.3",
3
+ "version": "0.25.0",
4
4
  "description": "Sign in with Dexter — passkey connector. Composes @dexterai/vault.",
5
5
  "type": "module",
6
6
  "exports": {