@dexterai/connect 0.19.0 → 0.21.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/README.md +33 -1
- package/dist/{chunk-S7G2ZZOO.js → chunk-2WETR5DJ.js} +11 -12
- package/dist/{chunk-2QZ5QLGI.js → chunk-MOC5AVY7.js} +190 -27
- package/dist/index.d.ts +5 -3
- package/dist/index.js +3 -1
- package/dist/react.d.ts +34 -3
- package/dist/react.js +78 -30
- package/dist/{signals-qc4rDyQ6.d.ts → signals-pjh3z6N8.d.ts} +42 -1
- package/dist/worldid.js +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -157,10 +157,42 @@ import { VerifyPersonhood } from '@dexterai/connect/worldid';
|
|
|
157
157
|
`useVerifyPersonhood` is the headless version. Requires the optional
|
|
158
158
|
`@worldcoin/idkit` peer.
|
|
159
159
|
|
|
160
|
+
## Wallet-only sign-in (no account session)
|
|
161
|
+
|
|
162
|
+
`recoverWallet` re-points a browser at an existing Dexter Wallet — the wallet
|
|
163
|
+
IS the sign-in; nothing else is minted. Use it when your surface treats the
|
|
164
|
+
wallet as the identity (the dexter.cash header does exactly this):
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
import { recoverWallet } from '@dexterai/connect';
|
|
168
|
+
|
|
169
|
+
const outcome = await recoverWallet({ preferImmediate: true }); // fire on TAP, never on mount
|
|
170
|
+
if (outcome.ok) {
|
|
171
|
+
// outcome.vault.swigAddress, outcome.vault.walletLabel — the store + every
|
|
172
|
+
// useIdentity/useDexterWallet surface is already updated.
|
|
173
|
+
} else if (outcome.reason === 'no_credential') {
|
|
174
|
+
// this device has no wallet passkey → offer your create flow
|
|
175
|
+
} else if (outcome.reason === 'cancelled') {
|
|
176
|
+
// the user dismissed the sheet → stay silent
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
It returns a discriminated outcome instead of throwing: user cancel is a
|
|
181
|
+
normal result in WebAuthn, not an exception. `preferImmediate` uses Chrome
|
|
182
|
+
149+'s immediate UI mode to fast-fail instantly when the device holds no
|
|
183
|
+
passkey (no empty account-picker sheet); everywhere else it falls back to the
|
|
184
|
+
normal modal. Works from any website — off dexter.cash the ceremony runs in
|
|
185
|
+
the hosted popup automatically.
|
|
186
|
+
|
|
187
|
+
React: `<SignInWithDexter mode="recover" preferImmediate onRecovered={…} />`
|
|
188
|
+
(after a successful recover the element renders null — show identity with
|
|
189
|
+
`DexterWalletChip` over `useIdentity`), or `useSignInWithDexter().recover()`.
|
|
190
|
+
|
|
160
191
|
## Wallet lifecycle
|
|
161
192
|
|
|
162
193
|
- `createWallet` mints a brand-new named passkey + vault; `passkeyLogin` signs
|
|
163
|
-
an existing one in; `continueWithDexter` resumes a known wallet
|
|
194
|
+
an existing one in; `continueWithDexter` resumes a known wallet;
|
|
195
|
+
`recoverWallet` restores the wallet with no account session.
|
|
164
196
|
- The **wallet store** (`getActiveHandle`, `listWallets`, `switchWallet`,
|
|
165
197
|
`ejectActiveWallet`, `forgetWallet`, `subscribeWallet`) is the canonical
|
|
166
198
|
owner of the active-wallet handle. Read and write through it rather than
|
|
@@ -6,22 +6,21 @@ var BUTTON_CSS = `
|
|
|
6
6
|
@keyframes dx-spin { to { transform: rotate(360deg); } }
|
|
7
7
|
@keyframes dx-pulse { 0%,100% { opacity: 1; } 50% { opacity: .6; } }
|
|
8
8
|
.dx-btn{
|
|
9
|
-
--dx-ember:#f26c18; --dx-ember-2:#ba3a00; --dx-fg:#fff4ea; --dx-radius:0px;
|
|
10
9
|
position:relative; display:inline-flex; align-items:center; justify-content:center; gap:10px;
|
|
11
|
-
padding:11px 22px; border:1px solid color-mix(in srgb,var(--dx-ember) 55%,transparent);
|
|
12
|
-
border-radius:var(--dx-radius);
|
|
13
|
-
background:linear-gradient(135deg,var(--dx-ember),var(--dx-ember-2));
|
|
14
|
-
color:var(--dx-fg); font:inherit; font-weight:600; font-size:.78rem; letter-spacing:.12em;
|
|
10
|
+
padding:11px 22px; border:1px solid color-mix(in srgb,var(--dx-ember,#f26c18) 55%,transparent);
|
|
11
|
+
border-radius:var(--dx-radius,0px);
|
|
12
|
+
background:linear-gradient(135deg,var(--dx-ember,#f26c18),var(--dx-ember-2,#ba3a00));
|
|
13
|
+
color:var(--dx-fg,#fff4ea); font:inherit; font-weight:600; font-size:.78rem; letter-spacing:.12em;
|
|
15
14
|
text-transform:uppercase; cursor:pointer; -webkit-tap-highlight-color:transparent;
|
|
16
|
-
box-shadow:0 14px 26px color-mix(in srgb,var(--dx-ember) 24%,transparent);
|
|
15
|
+
box-shadow:0 14px 26px color-mix(in srgb,var(--dx-ember,#f26c18) 24%,transparent);
|
|
17
16
|
transition:transform .16s ease, box-shadow .16s ease, filter .16s ease, background .16s ease;
|
|
18
17
|
}
|
|
19
|
-
.dx-btn:hover{ transform:translateY(-1px); filter:brightness(1.07); box-shadow:0 20px 34px color-mix(in srgb,var(--dx-ember) 32%,transparent); }
|
|
20
|
-
.dx-btn:active{ transform:translateY(0); filter:brightness(.97); box-shadow:0 8px 16px color-mix(in srgb,var(--dx-ember) 22%,transparent); }
|
|
21
|
-
.dx-btn:focus-visible{ outline:none; box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember) 38%,transparent); }
|
|
18
|
+
.dx-btn:hover{ transform:translateY(-1px); filter:brightness(1.07); box-shadow:0 20px 34px color-mix(in srgb,var(--dx-ember,#f26c18) 32%,transparent); }
|
|
19
|
+
.dx-btn:active{ transform:translateY(0); filter:brightness(.97); box-shadow:0 8px 16px color-mix(in srgb,var(--dx-ember,#f26c18) 22%,transparent); }
|
|
20
|
+
.dx-btn:focus-visible{ outline:none; box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember,#f26c18) 38%,transparent); }
|
|
22
21
|
.dx-btn:disabled{ cursor:default; filter:saturate(.85) brightness(.98); }
|
|
23
|
-
.dx-btn--secondary{ background:transparent; color:var(--dx-ember); box-shadow:none; border-color:color-mix(in srgb,var(--dx-ember) 45%,transparent); }
|
|
24
|
-
.dx-btn--secondary:hover{ background:color-mix(in srgb,var(--dx-ember) 9%,transparent); filter:none; box-shadow:none; }
|
|
22
|
+
.dx-btn--secondary{ background:transparent; color:var(--dx-ember,#f26c18); box-shadow:none; border-color:color-mix(in srgb,var(--dx-ember,#f26c18) 45%,transparent); }
|
|
23
|
+
.dx-btn--secondary:hover{ background:color-mix(in srgb,var(--dx-ember,#f26c18) 9%,transparent); filter:none; box-shadow:none; }
|
|
25
24
|
.dx-btn--block{ width:100%; }
|
|
26
25
|
.dx-btn__mark{ flex-shrink:0; }
|
|
27
26
|
.dx-btn__spin{ width:15px; height:15px; flex-shrink:0; border-radius:50%;
|
|
@@ -29,7 +28,7 @@ var BUTTON_CSS = `
|
|
|
29
28
|
animation:dx-spin .7s linear infinite; }
|
|
30
29
|
.dx-btn__doing{ animation:dx-pulse 1.4s ease-in-out infinite; }
|
|
31
30
|
.dx-chip{ display:inline-flex; align-items:center; gap:8px; padding:6px 10px; font:inherit;
|
|
32
|
-
font-variant-numeric:tabular-nums; border-radius:var(--dx-radius,
|
|
31
|
+
font-variant-numeric:tabular-nums; border-radius:var(--dx-radius,0px);
|
|
33
32
|
border:1px solid color-mix(in srgb,var(--dx-ember,#f26c18) 35%,transparent); }
|
|
34
33
|
.dx-chip__dot{ width:7px; height:7px; border-radius:50%; background:var(--dx-ember,#f26c18); }
|
|
35
34
|
.dx-chip__bal{ font-weight:600; opacity:.85; }
|
|
@@ -166,6 +166,7 @@ function openCeremonyPopup(op, config = {}) {
|
|
|
166
166
|
const params = new URLSearchParams({ v: "1", op, requestId, origin: openerOrigin });
|
|
167
167
|
if (config.name) params.set("name", config.name);
|
|
168
168
|
if (config.apiBase) params.set("apiBase", config.apiBase);
|
|
169
|
+
if (config.preferImmediate) params.set("preferImmediate", "1");
|
|
169
170
|
const url = `${host}?${params.toString()}`;
|
|
170
171
|
return new Promise((resolve, reject) => {
|
|
171
172
|
const popup = window.open(url, "dexter-connect", popupFeatures());
|
|
@@ -230,6 +231,16 @@ function makeNonce() {
|
|
|
230
231
|
return `rid-${new URL(location.href).searchParams.get("v") ?? ""}-${typeof performance !== "undefined" ? performance.now() : 0}`;
|
|
231
232
|
}
|
|
232
233
|
|
|
234
|
+
// src/httpError.ts
|
|
235
|
+
async function readErrorCode(res) {
|
|
236
|
+
try {
|
|
237
|
+
const body = await res.json();
|
|
238
|
+
if (body?.error) return body.error;
|
|
239
|
+
} catch {
|
|
240
|
+
}
|
|
241
|
+
return `http_${res.status}`;
|
|
242
|
+
}
|
|
243
|
+
|
|
233
244
|
// src/enroll.ts
|
|
234
245
|
var DEFAULT_API_BASE = "https://api.dexter.cash";
|
|
235
246
|
var DEFAULT_RP_ID = "dexter.cash";
|
|
@@ -327,14 +338,6 @@ async function initializeVault(apiBase, userHandle, credentialId, spendPolicy) {
|
|
|
327
338
|
if (!res.ok) throw new ConnectError(await readErrorCode(res), `initialize ${res.status}`);
|
|
328
339
|
return await res.json();
|
|
329
340
|
}
|
|
330
|
-
async function readErrorCode(res) {
|
|
331
|
-
try {
|
|
332
|
-
const body = await res.json();
|
|
333
|
-
if (body?.error) return body.error;
|
|
334
|
-
} catch {
|
|
335
|
-
}
|
|
336
|
-
return `http_${res.status}`;
|
|
337
|
-
}
|
|
338
341
|
|
|
339
342
|
// src/relay.ts
|
|
340
343
|
import { startAuthentication, browserSupportsWebAuthn } from "@simplewebauthn/browser";
|
|
@@ -414,7 +417,7 @@ async function submitLogin(apiBase, response) {
|
|
|
414
417
|
body: JSON.stringify({ credential: response })
|
|
415
418
|
});
|
|
416
419
|
if (!res.ok) {
|
|
417
|
-
throw new ConnectError(await
|
|
420
|
+
throw new ConnectError(await readErrorCode(res), `passkey-login ${res.status}`);
|
|
418
421
|
}
|
|
419
422
|
const data = await res.json();
|
|
420
423
|
const session = {
|
|
@@ -426,14 +429,6 @@ async function submitLogin(apiBase, response) {
|
|
|
426
429
|
};
|
|
427
430
|
return data.vault ? { session, vault: data.vault } : { session };
|
|
428
431
|
}
|
|
429
|
-
async function readErrorCode2(res) {
|
|
430
|
-
try {
|
|
431
|
-
const body = await res.json();
|
|
432
|
-
if (body?.error) return body.error;
|
|
433
|
-
} catch {
|
|
434
|
-
}
|
|
435
|
-
return `http_${res.status}`;
|
|
436
|
-
}
|
|
437
432
|
|
|
438
433
|
// src/base64.ts
|
|
439
434
|
function base64ToBytes(s) {
|
|
@@ -506,7 +501,7 @@ function createAnonServerPolicy(apiBase = DEFAULT_API_BASE3) {
|
|
|
506
501
|
operationHash: bytesToBase64url(operationHash)
|
|
507
502
|
})
|
|
508
503
|
});
|
|
509
|
-
if (!res.ok) throw new ConnectError(await
|
|
504
|
+
if (!res.ok) throw new ConnectError(await readErrorCode(res), `challenge ${res.status}`);
|
|
510
505
|
const data = await res.json();
|
|
511
506
|
const options = data?.options;
|
|
512
507
|
if (!options?.challenge) {
|
|
@@ -543,7 +538,7 @@ function createAnonServerPolicy(apiBase = DEFAULT_API_BASE3) {
|
|
|
543
538
|
headers: { "content-type": "application/json" },
|
|
544
539
|
body: JSON.stringify({ credential, userHandle: bytesToBase64url(userHandle) })
|
|
545
540
|
});
|
|
546
|
-
if (!res.ok) throw new ConnectError(await
|
|
541
|
+
if (!res.ok) throw new ConnectError(await readErrorCode(res), `verify ${res.status}`);
|
|
547
542
|
const data = await res.json();
|
|
548
543
|
if (data?.verified === false) {
|
|
549
544
|
throw new ConnectError("verification_failed", "server returned verified=false");
|
|
@@ -551,14 +546,6 @@ function createAnonServerPolicy(apiBase = DEFAULT_API_BASE3) {
|
|
|
551
546
|
}
|
|
552
547
|
};
|
|
553
548
|
}
|
|
554
|
-
async function readErrorCode3(res) {
|
|
555
|
-
try {
|
|
556
|
-
const body = await res.json();
|
|
557
|
-
if (body?.error) return body.error;
|
|
558
|
-
} catch {
|
|
559
|
-
}
|
|
560
|
-
return `http_${res.status}`;
|
|
561
|
-
}
|
|
562
549
|
|
|
563
550
|
// src/signer.ts
|
|
564
551
|
import { DexterApiBrowserPasskeySigner } from "@dexterai/vault/signers/browser";
|
|
@@ -585,6 +572,181 @@ function resolveIdentity(input) {
|
|
|
585
572
|
return { kind, userHandle, accountToken, hasPasskeyVault, hasAccount, hasWallet };
|
|
586
573
|
}
|
|
587
574
|
|
|
575
|
+
// src/recover.ts
|
|
576
|
+
import { startAuthentication as startAuthentication2, browserSupportsWebAuthn as browserSupportsWebAuthn2 } from "@simplewebauthn/browser";
|
|
577
|
+
|
|
578
|
+
// src/immediate.ts
|
|
579
|
+
var immediateCapPromise = null;
|
|
580
|
+
function immediateGetSupported() {
|
|
581
|
+
if (!immediateCapPromise) {
|
|
582
|
+
immediateCapPromise = (async () => {
|
|
583
|
+
try {
|
|
584
|
+
if (typeof window === "undefined") return false;
|
|
585
|
+
const pkc2 = window.PublicKeyCredential;
|
|
586
|
+
if (!pkc2 || typeof pkc2.getClientCapabilities !== "function") return false;
|
|
587
|
+
const caps = await pkc2.getClientCapabilities.call(pkc2);
|
|
588
|
+
return caps?.immediateGet === true;
|
|
589
|
+
} catch {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
})();
|
|
593
|
+
}
|
|
594
|
+
return immediateCapPromise;
|
|
595
|
+
}
|
|
596
|
+
function primeImmediateSupport() {
|
|
597
|
+
void immediateGetSupported();
|
|
598
|
+
}
|
|
599
|
+
async function immediateAuthentication(options) {
|
|
600
|
+
const getOptions = {
|
|
601
|
+
publicKey: {
|
|
602
|
+
challenge: base64urlToBytes(options.challenge),
|
|
603
|
+
rpId: options.rpId,
|
|
604
|
+
timeout: options.timeout,
|
|
605
|
+
userVerification: options.userVerification,
|
|
606
|
+
allowCredentials: options.allowCredentials?.map((c) => ({
|
|
607
|
+
id: base64urlToBytes(c.id),
|
|
608
|
+
type: c.type,
|
|
609
|
+
transports: c.transports
|
|
610
|
+
}))
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
getOptions.uiMode = "immediate";
|
|
614
|
+
const cred = await navigator.credentials.get(getOptions);
|
|
615
|
+
if (!cred) throw new DOMException("no credential", "NotAllowedError");
|
|
616
|
+
const a = cred.response;
|
|
617
|
+
return {
|
|
618
|
+
id: cred.id,
|
|
619
|
+
rawId: bytesToBase64url(new Uint8Array(cred.rawId)),
|
|
620
|
+
response: {
|
|
621
|
+
clientDataJSON: bytesToBase64url(new Uint8Array(a.clientDataJSON)),
|
|
622
|
+
authenticatorData: bytesToBase64url(new Uint8Array(a.authenticatorData)),
|
|
623
|
+
signature: bytesToBase64url(new Uint8Array(a.signature)),
|
|
624
|
+
userHandle: a.userHandle ? bytesToBase64url(new Uint8Array(a.userHandle)) : void 0
|
|
625
|
+
},
|
|
626
|
+
clientExtensionResults: cred.getClientExtensionResults?.() ?? {},
|
|
627
|
+
type: "public-key",
|
|
628
|
+
authenticatorAttachment: cred.authenticatorAttachment ?? void 0
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
function classifyWebAuthnRejection(err) {
|
|
632
|
+
const e = err;
|
|
633
|
+
if (!e) return false;
|
|
634
|
+
const blob = [e.name, e.cause?.name, typeof e.code === "string" ? e.code : "", e.message].filter(Boolean).join(" ").toLowerCase();
|
|
635
|
+
return /notallowed|not ?allowed|abort|cancel|timed out|timeout|denied|ceremony/.test(blob);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/recover.ts
|
|
639
|
+
var DEFAULT_API_BASE4 = "https://api.dexter.cash";
|
|
640
|
+
var ANON_SIGN_BASE3 = "/api/passkey-anon/sign";
|
|
641
|
+
primeImmediateSupport();
|
|
642
|
+
async function recoverWallet(config = {}) {
|
|
643
|
+
if (shouldUsePopup(config.transport)) {
|
|
644
|
+
let outcome;
|
|
645
|
+
try {
|
|
646
|
+
outcome = await openCeremonyPopup("recover", {
|
|
647
|
+
connectHost: config.connectHost,
|
|
648
|
+
apiBase: config.apiBase,
|
|
649
|
+
preferImmediate: config.preferImmediate
|
|
650
|
+
});
|
|
651
|
+
} catch (err) {
|
|
652
|
+
const ce = err instanceof ConnectError ? err : new ConnectError("popup_failed", err instanceof Error ? err.message : String(err));
|
|
653
|
+
if (ce.code === "popup_closed") return { ok: false, reason: "cancelled" };
|
|
654
|
+
return { ok: false, reason: "error", error: ce };
|
|
655
|
+
}
|
|
656
|
+
if (outcome.ok) {
|
|
657
|
+
setActiveHandle(outcome.userHandle, outcome.vault.walletLabel ?? void 0, outcome.credentialId);
|
|
658
|
+
}
|
|
659
|
+
return outcome;
|
|
660
|
+
}
|
|
661
|
+
if (typeof navigator === "undefined" || !browserSupportsWebAuthn2()) {
|
|
662
|
+
return {
|
|
663
|
+
ok: false,
|
|
664
|
+
reason: "error",
|
|
665
|
+
error: new ConnectError("webauthn_unsupported", "WebAuthn unavailable in this environment")
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
const apiBase = (config.apiBase ?? DEFAULT_API_BASE4).replace(/\/$/, "");
|
|
669
|
+
const onPhase = config.onPhase;
|
|
670
|
+
onPhase?.("challenge");
|
|
671
|
+
let options;
|
|
672
|
+
try {
|
|
673
|
+
const res = await fetch(`${apiBase}${ANON_SIGN_BASE3}/recover-challenge`, {
|
|
674
|
+
method: "POST",
|
|
675
|
+
headers: { "content-type": "application/json" },
|
|
676
|
+
body: "{}"
|
|
677
|
+
});
|
|
678
|
+
if (!res.ok) return { ok: false, reason: "error", error: new ConnectError(await readErrorCode(res)) };
|
|
679
|
+
options = (await res.json()).options;
|
|
680
|
+
} catch (err) {
|
|
681
|
+
return netError("recover_challenge_failed", err);
|
|
682
|
+
}
|
|
683
|
+
onPhase?.("passkey");
|
|
684
|
+
const useImmediate = Boolean(config.preferImmediate) && await immediateGetSupported();
|
|
685
|
+
let credential;
|
|
686
|
+
try {
|
|
687
|
+
credential = useImmediate ? await immediateAuthentication(options) : await startAuthentication2({ optionsJSON: options });
|
|
688
|
+
} catch (err) {
|
|
689
|
+
if (classifyWebAuthnRejection(err)) {
|
|
690
|
+
return { ok: false, reason: useImmediate ? "no_credential" : "cancelled" };
|
|
691
|
+
}
|
|
692
|
+
return {
|
|
693
|
+
ok: false,
|
|
694
|
+
reason: "error",
|
|
695
|
+
error: new ConnectError("webauthn_failed", err instanceof Error ? err.message : String(err))
|
|
696
|
+
};
|
|
697
|
+
}
|
|
698
|
+
onPhase?.("verifying");
|
|
699
|
+
let userHandle;
|
|
700
|
+
let credentialId;
|
|
701
|
+
try {
|
|
702
|
+
const res = await fetch(`${apiBase}${ANON_SIGN_BASE3}/recover-verify`, {
|
|
703
|
+
method: "POST",
|
|
704
|
+
headers: { "content-type": "application/json" },
|
|
705
|
+
body: JSON.stringify({ credential })
|
|
706
|
+
});
|
|
707
|
+
if (res.status === 404) return { ok: false, reason: "no_credential" };
|
|
708
|
+
if (!res.ok) return { ok: false, reason: "error", error: new ConnectError(await readErrorCode(res)) };
|
|
709
|
+
const data = await res.json();
|
|
710
|
+
userHandle = data.userHandle;
|
|
711
|
+
credentialId = data.credentialId;
|
|
712
|
+
} catch (err) {
|
|
713
|
+
return netError("recover_verify_failed", err);
|
|
714
|
+
}
|
|
715
|
+
let vault;
|
|
716
|
+
try {
|
|
717
|
+
const res = await fetch(
|
|
718
|
+
`${apiBase}/api/passkey-vault-anon/status?user_handle=${encodeURIComponent(userHandle)}`
|
|
719
|
+
);
|
|
720
|
+
if (!res.ok) return { ok: false, reason: "error", error: new ConnectError(await readErrorCode(res)) };
|
|
721
|
+
const data = await res.json();
|
|
722
|
+
if (!data.hasVault || !data.vault) {
|
|
723
|
+
return {
|
|
724
|
+
ok: false,
|
|
725
|
+
reason: "error",
|
|
726
|
+
error: new ConnectError("vault_not_found", "this sign-in key has no wallet attached")
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
vault = {
|
|
730
|
+
vaultPda: data.vault.vaultPda,
|
|
731
|
+
swigAddress: data.vault.swigAddress,
|
|
732
|
+
receiveAddress: data.vault.receiveAddress ?? null,
|
|
733
|
+
isActivated: Boolean(data.vault.isActivated),
|
|
734
|
+
walletLabel: data.vault.walletLabel ?? null
|
|
735
|
+
};
|
|
736
|
+
} catch (err) {
|
|
737
|
+
return netError("vault_status_failed", err);
|
|
738
|
+
}
|
|
739
|
+
setActiveHandle(userHandle, vault.walletLabel ?? void 0, credentialId);
|
|
740
|
+
return { ok: true, userHandle, credentialId, vault };
|
|
741
|
+
}
|
|
742
|
+
function netError(code, err) {
|
|
743
|
+
return {
|
|
744
|
+
ok: false,
|
|
745
|
+
reason: "error",
|
|
746
|
+
error: new ConnectError(code, err instanceof Error ? err.message : String(err))
|
|
747
|
+
};
|
|
748
|
+
}
|
|
749
|
+
|
|
588
750
|
// src/phase.ts
|
|
589
751
|
var PHASE_LABEL = {
|
|
590
752
|
challenge: "Preparing\u2026",
|
|
@@ -678,6 +840,7 @@ export {
|
|
|
678
840
|
createAnonServerPolicy,
|
|
679
841
|
createPasskeySigner,
|
|
680
842
|
resolveIdentity,
|
|
843
|
+
recoverWallet,
|
|
681
844
|
ceremonyPhaseLabel,
|
|
682
845
|
passkeySignalSupport,
|
|
683
846
|
renamePasskey,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { S as SignInResult, C as CreateWalletResult, a as CreateWalletConfig, b as CeremonyPhase, D as DexterConnectConfig, c as ConnectVault, I as IdentityKind } from './signals-
|
|
2
|
-
export { A as ACTIVE_WALLET_STORAGE_KEY,
|
|
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-pjh3z6N8.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-pjh3z6N8.js';
|
|
3
3
|
import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -82,6 +82,8 @@ declare function createPasskeySigner(vault: ConnectVault, apiBase?: string, opts
|
|
|
82
82
|
__assertion?: AssertionLike;
|
|
83
83
|
}): DexterApiBrowserPasskeySigner;
|
|
84
84
|
|
|
85
|
+
declare function recoverWallet(config?: RecoverWalletConfig): Promise<RecoverOutcome>;
|
|
86
|
+
|
|
85
87
|
/**
|
|
86
88
|
* Human-readable label for a ceremony phase — the live "connecting step" copy.
|
|
87
89
|
* ONE source of truth so sign-in (SignInWithDexter) and create (consumer setup
|
|
@@ -221,4 +223,4 @@ interface EnableAgentSpendResult {
|
|
|
221
223
|
*/
|
|
222
224
|
declare function enableAgentSpend(id: AgentSpendIdentity, vaultPda: string, apiOrigin: string, credentialId?: string | null): Promise<EnableAgentSpendResult>;
|
|
223
225
|
|
|
224
|
-
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, type RevokeAgentSpendResult, SignInResult, assembleAgentSpendStatus, ceremonyPhaseLabel, continueWithDexter, createAnonServerPolicy, createPasskeySigner, describeAgentSpendError, enableAgentSpend, passkeyLogin, revokeAgentSpend };
|
|
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 };
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
passkeyLogin,
|
|
19
19
|
passkeySignalSupport,
|
|
20
20
|
prunePasskey,
|
|
21
|
+
recoverWallet,
|
|
21
22
|
renamePasskey,
|
|
22
23
|
resolveIdentity,
|
|
23
24
|
setActiveHandle,
|
|
@@ -25,7 +26,7 @@ import {
|
|
|
25
26
|
switchWallet,
|
|
26
27
|
syncAcceptedPasskeys,
|
|
27
28
|
usdToAtomic
|
|
28
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-MOC5AVY7.js";
|
|
29
30
|
|
|
30
31
|
// src/agentSpend.ts
|
|
31
32
|
import { PublicKey } from "@solana/web3.js";
|
|
@@ -196,6 +197,7 @@ export {
|
|
|
196
197
|
passkeyLogin,
|
|
197
198
|
passkeySignalSupport,
|
|
198
199
|
prunePasskey,
|
|
200
|
+
recoverWallet,
|
|
199
201
|
renamePasskey,
|
|
200
202
|
resolveIdentity,
|
|
201
203
|
revokeAgentSpend,
|
package/dist/react.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DexterApiBrowserPasskeySigner } from '@dexterai/vault/signers/browser';
|
|
2
|
-
import { b as CeremonyPhase, S as SignInResult, P as PasskeyLoginTokens, c as ConnectVault,
|
|
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-pjh3z6N8.js';
|
|
3
|
+
export { h as RecoverVault } from './signals-pjh3z6N8.js';
|
|
3
4
|
import { ReactElement, ReactNode } from 'react';
|
|
4
5
|
|
|
5
6
|
type ConnectStatus = 'idle' | 'pending' | 'done' | 'error';
|
|
@@ -8,6 +9,16 @@ interface UseSignInWithDexterConfig {
|
|
|
8
9
|
apiBase?: string;
|
|
9
10
|
/** RPC for the connected-chip balance read. Default: Dexter's Helius proxy. */
|
|
10
11
|
rpcUrl?: string;
|
|
12
|
+
/** Ceremony transport (both verbs). 'auto' (default) = inline on dexter.cash,
|
|
13
|
+
* popup anywhere else. Exposed so tests/staging can force a leg —
|
|
14
|
+
* CreateWalletPanel already had this knob; the sign-in surface was the odd
|
|
15
|
+
* one out. */
|
|
16
|
+
transport?: 'auto' | 'popup' | 'inline';
|
|
17
|
+
/** Hosted ceremony page for the popup transport. Default dexter.cash/connect. */
|
|
18
|
+
connectHost?: string;
|
|
19
|
+
/** Chrome-149+ immediate UI mode for the wallet-only verb: instant fast-fail
|
|
20
|
+
* when this device holds no passkey. Ignored by signIn(). */
|
|
21
|
+
preferImmediate?: boolean;
|
|
11
22
|
}
|
|
12
23
|
interface UseSignInWithDexter {
|
|
13
24
|
status: ConnectStatus;
|
|
@@ -18,6 +29,14 @@ interface UseSignInWithDexter {
|
|
|
18
29
|
/** Run the ceremony. Resolves with the result; throws ConnectError on failure
|
|
19
30
|
* (error is also captured in `error` + `status==='error'` for declarative UI). */
|
|
20
31
|
signIn: () => Promise<SignInResult>;
|
|
32
|
+
/** Wallet-only sign-in (P0c): re-points this browser at an existing wallet,
|
|
33
|
+
* mints NO session. Returns a discriminated outcome — cancel is a normal
|
|
34
|
+
* result, never a throw. Identity surfaces (useIdentity/useDexterWallet)
|
|
35
|
+
* light up via the wallet store; `session`/`vault` here stay null. Fire on
|
|
36
|
+
* tap only — never on mount (iOS gesture rule). */
|
|
37
|
+
recover: () => Promise<RecoverOutcome>;
|
|
38
|
+
/** Last recover outcome; null until recover() settles. */
|
|
39
|
+
recovered: RecoverOutcome | null;
|
|
21
40
|
disconnect: () => void;
|
|
22
41
|
session: PasskeyLoginTokens | null;
|
|
23
42
|
vault: ConnectVault | null;
|
|
@@ -45,8 +64,18 @@ interface UseSignInWithDexter {
|
|
|
45
64
|
declare function useSignInWithDexter(config?: UseSignInWithDexterConfig): UseSignInWithDexter;
|
|
46
65
|
|
|
47
66
|
interface SignInWithDexterProps extends UseSignInWithDexterConfig {
|
|
67
|
+
/** 'signin' (default) = full sign-in minting the account session.
|
|
68
|
+
* 'recover' = wallet-only: re-points this browser at an existing wallet and
|
|
69
|
+
* mints NOTHING else (the wallet IS the sign-in). After a successful
|
|
70
|
+
* recover the element renders null — identity display belongs to
|
|
71
|
+
* DexterWalletChip/useIdentity, which light up via the wallet store. */
|
|
72
|
+
mode?: 'signin' | 'recover';
|
|
48
73
|
/** Fired with the result the moment sign-in completes. */
|
|
49
74
|
onSuccess?: (result: SignInResult) => void;
|
|
75
|
+
/** Recover mode: fired with EVERY settled outcome (ok, no_credential,
|
|
76
|
+
* cancelled, error) — the single channel; onError is not used in this mode.
|
|
77
|
+
* Branch on `reason`: no_credential → offer create; cancelled → stay silent. */
|
|
78
|
+
onRecovered?: (outcome: RecoverOutcome) => void;
|
|
50
79
|
/** Fired with the typed error if the ceremony fails. */
|
|
51
80
|
onError?: (error: ConnectError) => void;
|
|
52
81
|
/** Button label when signed out. Default "Sign in with Dexter". */
|
|
@@ -149,8 +178,10 @@ interface DexterWalletMenuProps {
|
|
|
149
178
|
avatarInitial?: string;
|
|
150
179
|
/** "Manage wallet" → the consumer's wallet page. Row hidden if omitted. */
|
|
151
180
|
onManageWallet?: () => void;
|
|
152
|
-
/**
|
|
181
|
+
/** Eject row → the consumer ejects/resets (guard + perform on its side). Row hidden if omitted. */
|
|
153
182
|
onStartFresh?: () => void;
|
|
183
|
+
/** Label for the eject/reset row. Default "Eject wallet". */
|
|
184
|
+
startFreshLabel?: string;
|
|
154
185
|
/** Sign-in/save UI revealed when "Save your wallet" is tapped (e.g. <SignInWithDexter/>). Row hidden if omitted. */
|
|
155
186
|
saveSlot?: ReactNode;
|
|
156
187
|
/** Short hint shown above the save UI. */
|
|
@@ -198,4 +229,4 @@ interface CreateWalletPanelProps {
|
|
|
198
229
|
/** The turnkey consent-at-birth create panel. */
|
|
199
230
|
declare function CreateWalletPanel(props: CreateWalletPanelProps): ReactElement;
|
|
200
231
|
|
|
201
|
-
export { AllowanceChips, type AllowanceChipsProps, type ConnectStatus, CreateWalletPanel, type CreateWalletPanelProps, DexterButton, type DexterButtonProps, DexterMark, DexterWalletChip, type DexterWalletChipProps, DexterWalletMenu, type DexterWalletMenuProps, SignInWithDexter, type SignInWithDexterProps, type UseDexterWallet, type UseIdentityConfig, type UseSignInWithDexter, type UseSignInWithDexterConfig, useDexterWallet, useIdentity, useSignInWithDexter };
|
|
232
|
+
export { AllowanceChips, type AllowanceChipsProps, type ConnectStatus, CreateWalletPanel, type CreateWalletPanelProps, DexterButton, type DexterButtonProps, DexterMark, DexterWalletChip, type DexterWalletChipProps, DexterWalletMenu, type DexterWalletMenuProps, RecoverOutcome, SignInWithDexter, type SignInWithDexterProps, type UseDexterWallet, type UseIdentityConfig, type UseSignInWithDexter, type UseSignInWithDexterConfig, useDexterWallet, useIdentity, useSignInWithDexter };
|
package/dist/react.js
CHANGED
|
@@ -11,18 +11,19 @@ import {
|
|
|
11
11
|
passkeyLogin,
|
|
12
12
|
passkeySignalSupport,
|
|
13
13
|
prunePasskey,
|
|
14
|
+
recoverWallet,
|
|
14
15
|
renamePasskey,
|
|
15
16
|
resolveIdentity,
|
|
16
17
|
setActiveHandle,
|
|
17
18
|
subscribe,
|
|
18
19
|
switchWallet
|
|
19
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-MOC5AVY7.js";
|
|
20
21
|
import {
|
|
21
22
|
DexterButton,
|
|
22
23
|
DexterMark,
|
|
23
24
|
cx,
|
|
24
25
|
ensureDexterButtonStyles
|
|
25
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-2WETR5DJ.js";
|
|
26
27
|
|
|
27
28
|
// src/useSignInWithDexter.ts
|
|
28
29
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
@@ -67,13 +68,14 @@ function base64ToBytes(s) {
|
|
|
67
68
|
// src/useSignInWithDexter.ts
|
|
68
69
|
var DEFAULT_RPC = "https://api.dexter.cash/proxy/helius/rpc";
|
|
69
70
|
function useSignInWithDexter(config = {}) {
|
|
70
|
-
const { apiBase, rpcUrl = DEFAULT_RPC } = config;
|
|
71
|
+
const { apiBase, rpcUrl = DEFAULT_RPC, transport, connectHost, preferImmediate } = config;
|
|
71
72
|
const [status, setStatus] = useState("idle");
|
|
72
73
|
const [phase, setPhase] = useState(null);
|
|
73
74
|
const [session, setSession] = useState(null);
|
|
74
75
|
const [vault, setVault] = useState(null);
|
|
75
76
|
const [usdcBalance, setUsdcBalance] = useState(null);
|
|
76
77
|
const [error, setError] = useState(null);
|
|
78
|
+
const [recovered, setRecovered] = useState(null);
|
|
77
79
|
const refreshBalance = useCallback(async () => {
|
|
78
80
|
const ata = vault?.usdcAta;
|
|
79
81
|
if (!ata) return;
|
|
@@ -84,7 +86,14 @@ function useSignInWithDexter(config = {}) {
|
|
|
84
86
|
setPhase(null);
|
|
85
87
|
setStatus("pending");
|
|
86
88
|
try {
|
|
87
|
-
const result = await passkeyLogin(
|
|
89
|
+
const result = await passkeyLogin(
|
|
90
|
+
{
|
|
91
|
+
...apiBase ? { apiBase } : {},
|
|
92
|
+
...transport ? { transport } : {},
|
|
93
|
+
...connectHost ? { connectHost } : {}
|
|
94
|
+
},
|
|
95
|
+
setPhase
|
|
96
|
+
);
|
|
88
97
|
setSession(result.session);
|
|
89
98
|
setVault(result.vault ?? null);
|
|
90
99
|
setStatus("done");
|
|
@@ -97,12 +106,36 @@ function useSignInWithDexter(config = {}) {
|
|
|
97
106
|
setPhase(null);
|
|
98
107
|
throw e;
|
|
99
108
|
}
|
|
100
|
-
}, [apiBase]);
|
|
109
|
+
}, [apiBase, transport, connectHost]);
|
|
110
|
+
const recover = useCallback(async () => {
|
|
111
|
+
setError(null);
|
|
112
|
+
setPhase(null);
|
|
113
|
+
setStatus("pending");
|
|
114
|
+
const outcome = await recoverWallet({
|
|
115
|
+
...apiBase ? { apiBase } : {},
|
|
116
|
+
...transport ? { transport } : {},
|
|
117
|
+
...connectHost ? { connectHost } : {},
|
|
118
|
+
...preferImmediate ? { preferImmediate } : {},
|
|
119
|
+
onPhase: setPhase
|
|
120
|
+
});
|
|
121
|
+
setPhase(null);
|
|
122
|
+
setRecovered(outcome);
|
|
123
|
+
if (outcome.ok) {
|
|
124
|
+
setStatus("done");
|
|
125
|
+
} else if (outcome.reason === "error") {
|
|
126
|
+
setError(outcome.error ?? new ConnectError("recover_failed"));
|
|
127
|
+
setStatus("error");
|
|
128
|
+
} else {
|
|
129
|
+
setStatus("idle");
|
|
130
|
+
}
|
|
131
|
+
return outcome;
|
|
132
|
+
}, [apiBase, transport, connectHost, preferImmediate]);
|
|
101
133
|
const disconnect = useCallback(() => {
|
|
102
134
|
setSession(null);
|
|
103
135
|
setVault(null);
|
|
104
136
|
setUsdcBalance(null);
|
|
105
137
|
setError(null);
|
|
138
|
+
setRecovered(null);
|
|
106
139
|
setStatus("idle");
|
|
107
140
|
}, []);
|
|
108
141
|
const passkeySigner = useMemo(
|
|
@@ -117,6 +150,8 @@ function useSignInWithDexter(config = {}) {
|
|
|
117
150
|
phase,
|
|
118
151
|
isVaultConnected: status === "done" && vault !== null,
|
|
119
152
|
signIn,
|
|
153
|
+
recover,
|
|
154
|
+
recovered,
|
|
120
155
|
disconnect,
|
|
121
156
|
session,
|
|
122
157
|
vault,
|
|
@@ -141,8 +176,10 @@ function formatUsd(n) {
|
|
|
141
176
|
}
|
|
142
177
|
function SignInWithDexter(props) {
|
|
143
178
|
const {
|
|
179
|
+
mode = "signin",
|
|
144
180
|
onSuccess,
|
|
145
181
|
onError,
|
|
182
|
+
onRecovered,
|
|
146
183
|
label = "Sign in with Dexter",
|
|
147
184
|
variant = "primary",
|
|
148
185
|
block = false,
|
|
@@ -153,12 +190,17 @@ function SignInWithDexter(props) {
|
|
|
153
190
|
useEffect2(ensureDexterButtonStyles, []);
|
|
154
191
|
const c = useSignInWithDexter(config);
|
|
155
192
|
const handleClick = async () => {
|
|
193
|
+
if (mode === "recover") {
|
|
194
|
+
onRecovered?.(await c.recover());
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
156
197
|
try {
|
|
157
198
|
onSuccess?.(await c.signIn());
|
|
158
199
|
} catch (err) {
|
|
159
200
|
onError?.(err);
|
|
160
201
|
}
|
|
161
202
|
};
|
|
203
|
+
if (mode === "recover" && c.recovered?.ok) return null;
|
|
162
204
|
if (c.isVaultConnected) {
|
|
163
205
|
if (!showConnectedChip) return null;
|
|
164
206
|
return /* @__PURE__ */ jsxs("span", { className: cx("dx-chip", className), children: [
|
|
@@ -235,35 +277,34 @@ import { useEffect as useEffect4 } from "react";
|
|
|
235
277
|
var STYLE_ID = "dexter-connect-wallet-kit-styles";
|
|
236
278
|
var WALLET_KIT_CSS = `
|
|
237
279
|
.dx-wchip{
|
|
238
|
-
--dx-ember:#f26c18; --dx-ember-2:#ba3a00; --dx-fg:#fff4ea; --dx-radius:0px;
|
|
239
280
|
display:inline-flex; align-items:center; gap:9px; max-width:240px;
|
|
240
281
|
padding:6px 12px 6px 7px; font:inherit; font-weight:600; font-size:.74rem;
|
|
241
282
|
letter-spacing:.1em; text-transform:uppercase; cursor:pointer; background:transparent;
|
|
242
|
-
color:inherit; border:1px solid color-mix(in srgb,var(--dx-ember) 42%,transparent);
|
|
243
|
-
border-radius:var(--dx-radius); -webkit-tap-highlight-color:transparent;
|
|
283
|
+
color:inherit; border:1px solid color-mix(in srgb,var(--dx-ember,#f26c18) 42%,transparent);
|
|
284
|
+
border-radius:var(--dx-radius,0px); -webkit-tap-highlight-color:transparent;
|
|
244
285
|
transition:filter .16s ease, box-shadow .16s ease, border-color .16s ease;
|
|
245
286
|
}
|
|
246
|
-
.dx-wchip:hover{ filter:brightness(1.08); box-shadow:0 8px 18px color-mix(in srgb,var(--dx-ember) 18%,transparent); }
|
|
247
|
-
.dx-wchip:focus-visible{ outline:none; box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember) 34%,transparent); }
|
|
287
|
+
.dx-wchip:hover{ filter:brightness(1.08); box-shadow:0 8px 18px color-mix(in srgb,var(--dx-ember,#f26c18) 18%,transparent); }
|
|
288
|
+
.dx-wchip:focus-visible{ outline:none; box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember,#f26c18) 34%,transparent); }
|
|
248
289
|
.dx-wchip--signedout{ border-color:color-mix(in srgb,currentColor 22%,transparent); padding-left:12px; }
|
|
249
290
|
.dx-wchip__avatar{
|
|
250
291
|
width:22px; height:22px; flex-shrink:0; display:inline-flex; align-items:center; justify-content:center;
|
|
251
292
|
border-radius:50%; overflow:hidden; font-size:.72rem; font-weight:700; letter-spacing:0; line-height:1;
|
|
252
|
-
background:linear-gradient(135deg,var(--dx-ember),var(--dx-ember-2)); color:var(--dx-fg);
|
|
293
|
+
background:linear-gradient(135deg,var(--dx-ember,#f26c18),var(--dx-ember-2,#ba3a00)); color:var(--dx-fg,#fff4ea);
|
|
253
294
|
}
|
|
254
295
|
.dx-wchip__avatar img{ width:100%; height:100%; object-fit:cover; }
|
|
255
296
|
.dx-wchip__label{ overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
256
297
|
|
|
257
|
-
.dx-wmenu{
|
|
298
|
+
.dx-wmenu{ display:flex; flex-direction:column; min-width:248px; }
|
|
258
299
|
.dx-wmenu__id{ display:flex; align-items:center; gap:10px; padding:13px 15px; border-bottom:1px solid color-mix(in srgb,currentColor 12%,transparent); }
|
|
259
|
-
.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); }
|
|
300
|
+
.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,#f26c18),var(--dx-ember-2,#ba3a00)); color:var(--dx-fg,#fff4ea); }
|
|
260
301
|
.dx-wmenu__meta{ display:flex; flex-direction:column; gap:2px; min-width:0; }
|
|
261
302
|
.dx-wmenu__name{ font-weight:700; font-size:.9rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
262
303
|
.dx-wmenu__sub{ font-size:.66rem; letter-spacing:.12em; text-transform:uppercase; opacity:.55; }
|
|
263
304
|
.dx-wmenu__list{ display:flex; flex-direction:column; padding:6px; }
|
|
264
305
|
.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; }
|
|
265
|
-
.dx-wmenu__item:hover{ background:color-mix(in srgb,var(--dx-ember) 10%,transparent); }
|
|
266
|
-
.dx-wmenu__item--danger{ color
|
|
306
|
+
.dx-wmenu__item:hover{ background:color-mix(in srgb,var(--dx-ember,#f26c18) 10%,transparent); }
|
|
307
|
+
.dx-wmenu__item--danger{ color:var(--dx-danger,#e5552e); }
|
|
267
308
|
.dx-wmenu__icon{ opacity:.45; font-size:.9rem; }
|
|
268
309
|
.dx-wmenu__back{ border-bottom:1px solid color-mix(in srgb,currentColor 10%,transparent); opacity:.85; }
|
|
269
310
|
.dx-wmenu__save{ display:flex; flex-direction:column; gap:9px; padding:13px 15px; }
|
|
@@ -304,7 +345,16 @@ function DexterWalletChip(props) {
|
|
|
304
345
|
import { useEffect as useEffect5, useState as useState3 } from "react";
|
|
305
346
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
306
347
|
function DexterWalletMenu(props) {
|
|
307
|
-
const {
|
|
348
|
+
const {
|
|
349
|
+
walletLabel,
|
|
350
|
+
avatarInitial,
|
|
351
|
+
onManageWallet,
|
|
352
|
+
onStartFresh,
|
|
353
|
+
startFreshLabel = "Eject wallet",
|
|
354
|
+
saveSlot,
|
|
355
|
+
saveHint,
|
|
356
|
+
className
|
|
357
|
+
} = props;
|
|
308
358
|
useEffect5(ensureWalletKitStyles, []);
|
|
309
359
|
const [showSave, setShowSave] = useState3(false);
|
|
310
360
|
const initial = (avatarInitial ?? walletLabel.trim().charAt(0) ?? "D").toUpperCase();
|
|
@@ -349,7 +399,7 @@ function DexterWalletMenu(props) {
|
|
|
349
399
|
className: "dx-wmenu__item dx-wmenu__item--danger",
|
|
350
400
|
onClick: onStartFresh,
|
|
351
401
|
children: [
|
|
352
|
-
/* @__PURE__ */ jsx3("span", { children:
|
|
402
|
+
/* @__PURE__ */ jsx3("span", { children: startFreshLabel }),
|
|
353
403
|
/* @__PURE__ */ jsx3("span", { className: "dx-wmenu__icon", "aria-hidden": "true", children: "\u27F5" })
|
|
354
404
|
]
|
|
355
405
|
}
|
|
@@ -378,39 +428,37 @@ import { useEffect as useEffect6, useState as useState4 } from "react";
|
|
|
378
428
|
var STYLE_ID2 = "dexter-connect-consent-styles";
|
|
379
429
|
var CONSENT_CSS = `
|
|
380
430
|
.dx-allow{
|
|
381
|
-
--dx-ember:#f26c18; --dx-ember-2:#ba3a00; --dx-fg:#fff4ea; --dx-radius:0px;
|
|
382
431
|
display:flex; flex-wrap:wrap; gap:8px; align-items:center;
|
|
383
432
|
}
|
|
384
433
|
.dx-allow__chip{
|
|
385
434
|
display:inline-flex; align-items:center; justify-content:center; padding:9px 15px;
|
|
386
435
|
font:inherit; font-weight:600; font-size:.78rem; letter-spacing:.12em; text-transform:uppercase;
|
|
387
436
|
font-variant-numeric:tabular-nums; cursor:pointer; background:transparent; color:inherit;
|
|
388
|
-
border:1px solid color-mix(in srgb,var(--dx-ember) 45%,transparent);
|
|
437
|
+
border:1px solid color-mix(in srgb,var(--dx-ember,#f26c18) 45%,transparent);
|
|
389
438
|
border-radius:var(--dx-radius,0px); -webkit-tap-highlight-color:transparent;
|
|
390
439
|
transition:filter .16s ease, box-shadow .16s ease, border-color .16s ease, background .16s ease, color .16s ease;
|
|
391
440
|
}
|
|
392
|
-
.dx-allow__chip:hover{ border-color:color-mix(in srgb,var(--dx-ember) 70%,transparent); filter:brightness(1.05); }
|
|
393
|
-
.dx-allow__chip:focus-visible{ outline:none; box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember) 34%,transparent); }
|
|
441
|
+
.dx-allow__chip:hover{ border-color:color-mix(in srgb,var(--dx-ember,#f26c18) 70%,transparent); filter:brightness(1.05); }
|
|
442
|
+
.dx-allow__chip:focus-visible{ outline:none; box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember,#f26c18) 34%,transparent); }
|
|
394
443
|
.dx-allow__chip--active{
|
|
395
444
|
background:linear-gradient(135deg,var(--dx-ember,#f26c18),var(--dx-ember-2,#ba3a00));
|
|
396
|
-
color:var(--dx-fg,#fff4ea); border-color:color-mix(in srgb,var(--dx-ember) 55%,transparent);
|
|
397
|
-
box-shadow:0 10px 22px color-mix(in srgb,var(--dx-ember) 22%,transparent);
|
|
445
|
+
color:var(--dx-fg,#fff4ea); border-color:color-mix(in srgb,var(--dx-ember,#f26c18) 55%,transparent);
|
|
446
|
+
box-shadow:0 10px 22px color-mix(in srgb,var(--dx-ember,#f26c18) 22%,transparent);
|
|
398
447
|
}
|
|
399
448
|
.dx-allow__input{
|
|
400
449
|
flex:1 1 130px; min-width:120px; padding:9px 13px; font:inherit; font-size:.9rem;
|
|
401
450
|
font-variant-numeric:tabular-nums; color:inherit; background:transparent;
|
|
402
|
-
border:1px solid color-mix(in srgb,var(--dx-ember) 45%,transparent);
|
|
451
|
+
border:1px solid color-mix(in srgb,var(--dx-ember,#f26c18) 45%,transparent);
|
|
403
452
|
border-radius:var(--dx-radius,0px); -webkit-tap-highlight-color:transparent;
|
|
404
453
|
transition:border-color .16s ease, box-shadow .16s ease;
|
|
405
454
|
}
|
|
406
455
|
.dx-allow__input:focus, .dx-allow__input:focus-visible{
|
|
407
|
-
outline:none; border-color:color-mix(in srgb,var(--dx-ember) 70%,transparent);
|
|
408
|
-
box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember) 30%,transparent);
|
|
456
|
+
outline:none; border-color:color-mix(in srgb,var(--dx-ember,#f26c18) 70%,transparent);
|
|
457
|
+
box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember,#f26c18) 30%,transparent);
|
|
409
458
|
}
|
|
410
459
|
.dx-allow__input::placeholder{ color:currentColor; opacity:.5; letter-spacing:.02em; text-transform:none; }
|
|
411
460
|
|
|
412
461
|
.dx-cwp{
|
|
413
|
-
--dx-ember:#f26c18; --dx-ember-2:#ba3a00; --dx-fg:#fff4ea; --dx-radius:0px;
|
|
414
462
|
display:flex; flex-direction:column; gap:16px; font:inherit;
|
|
415
463
|
}
|
|
416
464
|
.dx-cwp__field{ display:flex; flex-direction:column; gap:8px; }
|
|
@@ -421,12 +469,12 @@ var CONSENT_CSS = `
|
|
|
421
469
|
-webkit-tap-highlight-color:transparent; transition:border-color .16s ease, box-shadow .16s ease;
|
|
422
470
|
}
|
|
423
471
|
.dx-cwp__name:focus, .dx-cwp__name:focus-visible{
|
|
424
|
-
outline:none; border-color:color-mix(in srgb,var(--dx-ember) 60%,transparent);
|
|
425
|
-
box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember) 26%,transparent);
|
|
472
|
+
outline:none; border-color:color-mix(in srgb,var(--dx-ember,#f26c18) 60%,transparent);
|
|
473
|
+
box-shadow:0 0 0 3px color-mix(in srgb,var(--dx-ember,#f26c18) 26%,transparent);
|
|
426
474
|
}
|
|
427
475
|
.dx-cwp__name::placeholder{ color:currentColor; opacity:.42; }
|
|
428
476
|
.dx-cwp__fine{ font-size:.76rem; line-height:1.5; opacity:.66; }
|
|
429
|
-
.dx-cwp__err{ font-size:.78rem; line-height:1.45; color
|
|
477
|
+
.dx-cwp__err{ font-size:.78rem; line-height:1.45; color:var(--dx-danger,#e5552e); }
|
|
430
478
|
`;
|
|
431
479
|
function ensureConsentStyles() {
|
|
432
480
|
if (typeof document === "undefined") return;
|
|
@@ -38,6 +38,47 @@ interface SignInResult {
|
|
|
38
38
|
* challenge → passkey (the OS prompt) → verifying → finalizing (create only).
|
|
39
39
|
*/
|
|
40
40
|
type CeremonyPhase = 'challenge' | 'passkey' | 'verifying' | 'finalizing';
|
|
41
|
+
/**
|
|
42
|
+
* Vault identity on the wallet-only recover leg, as reported by
|
|
43
|
+
* /api/passkey-vault-anon/status. Narrower than ConnectVault on purpose —
|
|
44
|
+
* the status endpoint carries no publicKey/usdcAta, so a recover cannot
|
|
45
|
+
* construct a passkey signer; it re-points this browser at the wallet and
|
|
46
|
+
* lets useIdentity/useDexterWallet light the UI.
|
|
47
|
+
*/
|
|
48
|
+
interface RecoverVault {
|
|
49
|
+
vaultPda: string;
|
|
50
|
+
/** Swig state address, base58 — the user-facing Dexter Wallet address. */
|
|
51
|
+
swigAddress: string;
|
|
52
|
+
/** Deposit address; null until the swig is deployed. */
|
|
53
|
+
receiveAddress: string | null;
|
|
54
|
+
isActivated: boolean;
|
|
55
|
+
walletLabel: string | null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Result of a wallet-only recover ceremony. A discriminated RESULT, not a
|
|
59
|
+
* throw: user-cancel is a normal outcome in WebAuthn, and consumers branch on
|
|
60
|
+
* `reason` (no_credential → offer create; cancelled → stay silent; error →
|
|
61
|
+
* retry copy). `no_credential` covers both the immediate-mode instant
|
|
62
|
+
* rejection (no passkey on this device) and a verify 404 (a passkey the
|
|
63
|
+
* server has no row for).
|
|
64
|
+
*/
|
|
65
|
+
type RecoverOutcome = {
|
|
66
|
+
ok: true;
|
|
67
|
+
userHandle: string;
|
|
68
|
+
credentialId: string;
|
|
69
|
+
vault: RecoverVault;
|
|
70
|
+
} | {
|
|
71
|
+
ok: false;
|
|
72
|
+
reason: 'no_credential' | 'cancelled' | 'error';
|
|
73
|
+
error?: ConnectError;
|
|
74
|
+
};
|
|
75
|
+
interface RecoverWalletConfig extends DexterConnectConfig {
|
|
76
|
+
/** Chrome-149+ immediate UI mode: instant fast-fail when this device has no
|
|
77
|
+
* discoverable passkey (no empty account-picker sheet). Falls back to the
|
|
78
|
+
* normal modal wherever unsupported. */
|
|
79
|
+
preferImmediate?: boolean;
|
|
80
|
+
onPhase?: (phase: CeremonyPhase) => void;
|
|
81
|
+
}
|
|
41
82
|
interface DexterConnectConfig {
|
|
42
83
|
/** dexter-api base. Default https://api.dexter.cash. */
|
|
43
84
|
apiBase?: string;
|
|
@@ -229,4 +270,4 @@ declare function syncAcceptedPasskeys(args: {
|
|
|
229
270
|
rpId?: string;
|
|
230
271
|
}): Promise<boolean>;
|
|
231
272
|
|
|
232
|
-
export { ACTIVE_WALLET_STORAGE_KEY as A, type CreateWalletResult as C, type DexterConnectConfig as D, type IdentityKind as I, type PasskeyLoginTokens as P, type
|
|
273
|
+
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 };
|
package/dist/worldid.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dexterai/connect",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Sign in with Dexter
|
|
3
|
+
"version": "0.21.0",
|
|
4
|
+
"description": "Sign in with Dexter \u2014 passkey connector. Composes @dexterai/vault.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|