@cavos/kit 0.0.3 → 0.0.5
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/{Cavos-BH2_tOQ2.d.mts → Cavos-D20EtgOK.d.mts} +312 -182
- package/dist/{Cavos-BH2_tOQ2.d.ts → Cavos-D20EtgOK.d.ts} +312 -182
- package/dist/{chunk-BNGLH3Q3.mjs → chunk-M5BGBODC.mjs} +1660 -582
- package/dist/chunk-M5BGBODC.mjs.map +1 -0
- package/dist/index.d.mts +293 -89
- package/dist/index.d.ts +293 -89
- package/dist/index.js +1601 -593
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -88
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +42 -29
- package/dist/react/index.d.ts +42 -29
- package/dist/react/index.js +2002 -655
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +353 -92
- package/dist/react/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-BNGLH3Q3.mjs.map +0 -1
package/dist/react/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CavosAuth, HttpRecoveryClient, Cavos, generateRecoveryCode, CavosSolana } from '../chunk-
|
|
1
|
+
import { CavosAuth, PasskeySigner, HttpRecoveryClient, Cavos, PasskeyPrf, generateRecoveryCode, CavosSolana } from '../chunk-M5BGBODC.mjs';
|
|
2
2
|
import { createContext, useState, useRef, useEffect, useCallback, useContext } from 'react';
|
|
3
3
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
|
|
@@ -105,6 +105,17 @@ var EmailIcon = () => /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", v
|
|
|
105
105
|
/* @__PURE__ */ jsx("rect", { x: "2", y: "4", width: "20", height: "16", rx: "2" }),
|
|
106
106
|
/* @__PURE__ */ jsx("path", { d: "m22 7-8.97 5.7a1.94 1.94 0 01-2.06 0L2 7" })
|
|
107
107
|
] });
|
|
108
|
+
var FingerprintIcon = ({ size = 22, color = "currentColor" }) => /* @__PURE__ */ jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
109
|
+
/* @__PURE__ */ jsx("path", { d: "M2 12C2 6.5 6.5 2 12 2a10 10 0 0 1 8 4" }),
|
|
110
|
+
/* @__PURE__ */ jsx("path", { d: "M5 19.5C5.5 18 6 15 6 12c0-.7.12-1.37.34-2" }),
|
|
111
|
+
/* @__PURE__ */ jsx("path", { d: "M17.29 21.02c.12-.6.43-2.3.5-3.02" }),
|
|
112
|
+
/* @__PURE__ */ jsx("path", { d: "M12 10a2 2 0 0 0-2 2c0 1.02-.1 2.51-.26 4" }),
|
|
113
|
+
/* @__PURE__ */ jsx("path", { d: "M8.65 22c.21-.66.45-1.32.57-2" }),
|
|
114
|
+
/* @__PURE__ */ jsx("path", { d: "M14 13.12c0 2.38 0 6.38-1 8.88" }),
|
|
115
|
+
/* @__PURE__ */ jsx("path", { d: "M2 16h.01" }),
|
|
116
|
+
/* @__PURE__ */ jsx("path", { d: "M21.8 16c.2-2 .131-5.354 0-6" }),
|
|
117
|
+
/* @__PURE__ */ jsx("path", { d: "M9 6.8a6 6 0 0 1 9 5.2c0 .47 0 1.17-.02 2" })
|
|
118
|
+
] });
|
|
108
119
|
var CavosLogo = ({ invert }) => /* @__PURE__ */ jsx("img", { src: `data:image/png;base64,${cavosLogoBase64}`, alt: "Cavos", style: { width: "auto", height: "16px", objectFit: "contain", opacity: 0.55, flexShrink: 0, filter: invert ? "invert(1)" : "none" } });
|
|
109
120
|
function Spinner({ size = 16, color = "#888" }) {
|
|
110
121
|
return /* @__PURE__ */ jsxs(
|
|
@@ -255,7 +266,11 @@ function CavosAuthModal({
|
|
|
255
266
|
authError,
|
|
256
267
|
clearAuthError,
|
|
257
268
|
resendDeviceApproval,
|
|
258
|
-
recover
|
|
269
|
+
recover,
|
|
270
|
+
passkeySupported,
|
|
271
|
+
enrollPasskeyDefault,
|
|
272
|
+
approveDeviceWithPasskey,
|
|
273
|
+
setupRecovery
|
|
259
274
|
} = useCavos();
|
|
260
275
|
const isMobile = useIsMobile();
|
|
261
276
|
const isLight = theme !== "dark";
|
|
@@ -283,7 +298,12 @@ function CavosAuthModal({
|
|
|
283
298
|
const [deviceResendBusy, setDeviceResendBusy] = useState(false);
|
|
284
299
|
const [recoverCode, setRecoverCode] = useState("");
|
|
285
300
|
const [recoverBusy, setRecoverBusy] = useState(false);
|
|
301
|
+
const [pkBusy, setPkBusy] = useState(false);
|
|
302
|
+
const [pkError, setPkError] = useState("");
|
|
303
|
+
const [savedRecoveryCode, setSavedRecoveryCode] = useState("");
|
|
304
|
+
const [copied, setCopied] = useState(false);
|
|
286
305
|
const doneHandledRef = useRef(false);
|
|
306
|
+
const secureHandledRef = useRef(false);
|
|
287
307
|
const closeTimerRef = useRef(null);
|
|
288
308
|
const otpBoxRefs = useRef([null, null, null, null, null, null]);
|
|
289
309
|
const setOtpDigit = (i, raw) => {
|
|
@@ -344,6 +364,7 @@ function CavosAuthModal({
|
|
|
344
364
|
setOtpCode("");
|
|
345
365
|
setError("");
|
|
346
366
|
doneHandledRef.current = false;
|
|
367
|
+
secureHandledRef.current = false;
|
|
347
368
|
}, 1600);
|
|
348
369
|
}, [onClose]);
|
|
349
370
|
useEffect(() => {
|
|
@@ -354,13 +375,26 @@ function CavosAuthModal({
|
|
|
354
375
|
doneHandledRef.current = false;
|
|
355
376
|
}
|
|
356
377
|
if (isAuthenticated && address && walletStatus.isReady) {
|
|
357
|
-
|
|
378
|
+
if (walletStatus.isNewAccount && !secureHandledRef.current) {
|
|
379
|
+
if (screen !== "secure-account" && screen !== "recovery-code" && !doneHandledRef.current) {
|
|
380
|
+
setScreen("secure-account");
|
|
381
|
+
}
|
|
382
|
+
} else {
|
|
383
|
+
triggerDone(address);
|
|
384
|
+
}
|
|
358
385
|
}
|
|
359
|
-
if (walletStatus.
|
|
360
|
-
|
|
361
|
-
|
|
386
|
+
if (walletStatus.needsDeviceApproval && // A deploy in progress owns the screen; never fight the deploying branch
|
|
387
|
+
// above (both flags true would oscillate deploying ↔ approval → loop).
|
|
388
|
+
!walletStatus.isDeploying && screen !== "recover" && screen !== "device-approval" && screen !== "passkey-approval") {
|
|
389
|
+
if (walletStatus.hasPasskey && passkeySupported) {
|
|
390
|
+
setScreen("passkey-approval");
|
|
391
|
+
doneHandledRef.current = false;
|
|
392
|
+
} else if (walletStatus.awaitingApproval) {
|
|
393
|
+
setScreen("device-approval");
|
|
394
|
+
doneHandledRef.current = false;
|
|
395
|
+
}
|
|
362
396
|
}
|
|
363
|
-
}, [open, isAuthenticated, address, walletStatus.isReady, walletStatus.isDeploying, walletStatus.awaitingApproval, screen, triggerDone]);
|
|
397
|
+
}, [open, isAuthenticated, address, walletStatus.isReady, walletStatus.isDeploying, walletStatus.awaitingApproval, walletStatus.needsDeviceApproval, walletStatus.hasPasskey, walletStatus.isNewAccount, passkeySupported, screen, triggerDone]);
|
|
364
398
|
useEffect(() => () => {
|
|
365
399
|
if (closeTimerRef.current) clearTimeout(closeTimerRef.current);
|
|
366
400
|
}, []);
|
|
@@ -370,9 +404,61 @@ function CavosAuthModal({
|
|
|
370
404
|
setEmail("");
|
|
371
405
|
setOtpCode("");
|
|
372
406
|
setError("");
|
|
407
|
+
setPkError("");
|
|
408
|
+
setSavedRecoveryCode("");
|
|
409
|
+
setCopied(false);
|
|
373
410
|
doneHandledRef.current = false;
|
|
411
|
+
secureHandledRef.current = false;
|
|
374
412
|
onClose();
|
|
375
413
|
};
|
|
414
|
+
const finishSecureStep = () => {
|
|
415
|
+
secureHandledRef.current = true;
|
|
416
|
+
if (address) triggerDone(address);
|
|
417
|
+
};
|
|
418
|
+
const handleSetupPasskey = async () => {
|
|
419
|
+
setPkBusy(true);
|
|
420
|
+
setPkError("");
|
|
421
|
+
try {
|
|
422
|
+
await enrollPasskeyDefault();
|
|
423
|
+
finishSecureStep();
|
|
424
|
+
} catch (e) {
|
|
425
|
+
setPkError(e instanceof Error ? e.message : "We couldn't set up your passkey. Try again.");
|
|
426
|
+
} finally {
|
|
427
|
+
setPkBusy(false);
|
|
428
|
+
}
|
|
429
|
+
};
|
|
430
|
+
const handleSaveRecovery = async () => {
|
|
431
|
+
setPkBusy(true);
|
|
432
|
+
setPkError("");
|
|
433
|
+
try {
|
|
434
|
+
const code = await setupRecovery();
|
|
435
|
+
setSavedRecoveryCode(code);
|
|
436
|
+
setScreen("recovery-code");
|
|
437
|
+
} catch (e) {
|
|
438
|
+
setPkError(e instanceof Error ? e.message : "We couldn't create your recovery phrase. Try again.");
|
|
439
|
+
} finally {
|
|
440
|
+
setPkBusy(false);
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
const handleCopyRecovery = async () => {
|
|
444
|
+
try {
|
|
445
|
+
await navigator.clipboard.writeText(savedRecoveryCode);
|
|
446
|
+
setCopied(true);
|
|
447
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
448
|
+
} catch {
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
const handlePasskeyApprove = async () => {
|
|
452
|
+
setPkBusy(true);
|
|
453
|
+
setPkError("");
|
|
454
|
+
try {
|
|
455
|
+
await approveDeviceWithPasskey();
|
|
456
|
+
setPkBusy(false);
|
|
457
|
+
} catch (e) {
|
|
458
|
+
setPkError(e instanceof Error ? e.message : "We couldn't verify your passkey. Try again or use email.");
|
|
459
|
+
setPkBusy(false);
|
|
460
|
+
}
|
|
461
|
+
};
|
|
376
462
|
const handleOAuth = async (provider) => {
|
|
377
463
|
setError("");
|
|
378
464
|
setBusy(true);
|
|
@@ -463,6 +549,85 @@ function CavosAuthModal({
|
|
|
463
549
|
setDeviceResendBusy(false);
|
|
464
550
|
}
|
|
465
551
|
};
|
|
552
|
+
if (screen === "secure-account") {
|
|
553
|
+
return /* @__PURE__ */ jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, children: /* @__PURE__ */ jsxs("div", { style: { ...card, position: "relative" }, children: [
|
|
554
|
+
isMobile && /* @__PURE__ */ jsx("div", { style: handle }),
|
|
555
|
+
/* @__PURE__ */ jsxs("div", { style: { padding: isMobile ? "28px 24px 28px" : "44px 24px 28px", textAlign: "center" }, children: [
|
|
556
|
+
/* @__PURE__ */ jsx("div", { style: { width: 48, height: 48, borderRadius: "50%", margin: "0 auto 16px", background: isLight ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.06)", border: `1px solid ${isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)"}`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsxs("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: "none", stroke: textColor, strokeWidth: "1.7", strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
557
|
+
/* @__PURE__ */ jsx("path", { d: "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" }),
|
|
558
|
+
/* @__PURE__ */ jsx("path", { d: "M9 12l2 2 4-4" })
|
|
559
|
+
] }) }),
|
|
560
|
+
/* @__PURE__ */ jsx("h2", { style: { margin: "0 0 8px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em" }, children: "Keep your account safe" }),
|
|
561
|
+
/* @__PURE__ */ jsx("p", { style: { margin: "0 0 24px", fontSize: "13px", color: subTextColor, lineHeight: 1.55 }, children: "Set up a passkey so you can sign in on a new phone or computer in one tap. It takes a few seconds." }),
|
|
562
|
+
pkError && /* @__PURE__ */ jsx("div", { style: { background: errBg, border: `1px solid ${errBorder}`, borderRadius: "10px", padding: "9px 13px", fontSize: "13px", color: errColor, marginBottom: "14px", textAlign: "left" }, children: pkError }),
|
|
563
|
+
passkeySupported ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
564
|
+
/* @__PURE__ */ jsxs("button", { className: "cavos-primary-btn", style: { width: "100%", padding: "13px", borderRadius: "12px", border: "none", background: primaryColor, color: "#fff", fontSize: "14px", fontWeight: 600, cursor: pkBusy ? "default" : "pointer", fontFamily: "inherit", display: "flex", alignItems: "center", justifyContent: "center", gap: "9px", opacity: pkBusy ? 0.65 : 1, transition: "opacity 0.15s, transform 0.1s" }, onClick: handleSetupPasskey, disabled: pkBusy, children: [
|
|
565
|
+
pkBusy ? /* @__PURE__ */ jsx(Spinner, { size: 16, color: "#fff" }) : /* @__PURE__ */ jsx(FingerprintIcon, { size: 18, color: "#fff" }),
|
|
566
|
+
pkBusy ? "Setting up\u2026" : "Set up a passkey"
|
|
567
|
+
] }),
|
|
568
|
+
/* @__PURE__ */ jsx("p", { style: { margin: "10px 0 0", fontSize: "12px", color: subTextColor, lineHeight: 1.5 }, children: "Uses Face ID, Touch ID, or your device PIN." }),
|
|
569
|
+
/* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "12px", margin: "18px 0" }, children: [
|
|
570
|
+
/* @__PURE__ */ jsx("span", { className: "cavos-divider-line", style: { background: isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)" } }),
|
|
571
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: "11px", color: subTextColor, textTransform: "uppercase", letterSpacing: "0.06em" }, children: "or" }),
|
|
572
|
+
/* @__PURE__ */ jsx("span", { className: "cavos-divider-line", style: { background: isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)" } })
|
|
573
|
+
] }),
|
|
574
|
+
/* @__PURE__ */ jsx("button", { className: "cavos-submit-btn", style: { width: "100%", padding: "12px", borderRadius: "12px", border: inputBorder, background: "transparent", color: textColor, fontSize: "14px", fontWeight: 500, cursor: pkBusy ? "default" : "pointer", fontFamily: "inherit", transition: "background 0.15s", opacity: pkBusy ? 0.6 : 1 }, onClick: handleSaveRecovery, disabled: pkBusy, children: "Save a recovery phrase instead" })
|
|
575
|
+
] }) : /* @__PURE__ */ jsxs("button", { className: "cavos-primary-btn", style: { width: "100%", padding: "13px", borderRadius: "12px", border: "none", background: primaryColor, color: "#fff", fontSize: "14px", fontWeight: 600, cursor: pkBusy ? "default" : "pointer", fontFamily: "inherit", display: "flex", alignItems: "center", justifyContent: "center", gap: "9px", opacity: pkBusy ? 0.65 : 1, transition: "opacity 0.15s" }, onClick: handleSaveRecovery, disabled: pkBusy, children: [
|
|
576
|
+
pkBusy && /* @__PURE__ */ jsx(Spinner, { size: 16, color: "#fff" }),
|
|
577
|
+
pkBusy ? "Setting up\u2026" : "Save a recovery phrase"
|
|
578
|
+
] }),
|
|
579
|
+
/* @__PURE__ */ jsx("button", { className: "cavos-sub-btn", style: { background: "none", border: "none", cursor: pkBusy ? "default" : "pointer", fontSize: "13px", color: subTextColor, width: "100%", textAlign: "center", padding: "18px 0 0", fontFamily: "inherit", transition: "color 0.15s", opacity: pkBusy ? 0.6 : 1 }, onClick: finishSecureStep, disabled: pkBusy, children: "Skip for now" })
|
|
580
|
+
] }),
|
|
581
|
+
/* @__PURE__ */ jsxs("div", { style: footer, children: [
|
|
582
|
+
/* @__PURE__ */ jsx(CavosLogo, { invert: !isLight }),
|
|
583
|
+
/* @__PURE__ */ jsx("span", { children: "Secured by Cavos" })
|
|
584
|
+
] })
|
|
585
|
+
] }) });
|
|
586
|
+
}
|
|
587
|
+
if (screen === "recovery-code") {
|
|
588
|
+
return /* @__PURE__ */ jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, children: /* @__PURE__ */ jsxs("div", { style: { ...card, position: "relative" }, children: [
|
|
589
|
+
isMobile && /* @__PURE__ */ jsx("div", { style: handle }),
|
|
590
|
+
/* @__PURE__ */ jsxs("div", { style: { padding: isMobile ? "28px 24px 28px" : "44px 24px 28px", textAlign: "center" }, children: [
|
|
591
|
+
/* @__PURE__ */ jsx("h2", { style: { margin: "0 0 8px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em" }, children: "Save your recovery phrase" }),
|
|
592
|
+
/* @__PURE__ */ jsx("p", { style: { margin: "0 0 18px", fontSize: "13px", color: subTextColor, lineHeight: 1.55 }, children: "Write this down and keep it somewhere safe. It's the only way to get back in if you lose your devices, and we can't recover it for you." }),
|
|
593
|
+
/* @__PURE__ */ jsx("div", { style: { background: isLight ? "rgba(0,0,0,0.03)" : "rgba(255,255,255,0.05)", border: `1px solid ${isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)"}`, borderRadius: "12px", padding: "16px", marginBottom: "12px", fontSize: "14px", fontWeight: 500, color: textColor, wordBreak: "break-word", lineHeight: 1.6, fontFamily: "ui-monospace, SFMono-Regular, Menlo, monospace", textAlign: "center" }, children: savedRecoveryCode }),
|
|
594
|
+
/* @__PURE__ */ jsx("button", { className: "cavos-provider", style: { ...pBtn, marginBottom: "10px" }, onClick: handleCopyRecovery, children: /* @__PURE__ */ jsx("span", { children: copied ? "Copied \u2713" : "Copy to clipboard" }) }),
|
|
595
|
+
/* @__PURE__ */ jsx("button", { className: "cavos-primary-btn", style: { width: "100%", padding: "12px", borderRadius: "12px", border: "none", background: primaryColor, color: "#fff", fontSize: "14px", fontWeight: 500, cursor: "pointer", fontFamily: "inherit", transition: "opacity 0.15s" }, onClick: finishSecureStep, children: "I've saved it" })
|
|
596
|
+
] }),
|
|
597
|
+
/* @__PURE__ */ jsxs("div", { style: footer, children: [
|
|
598
|
+
/* @__PURE__ */ jsx(CavosLogo, { invert: !isLight }),
|
|
599
|
+
/* @__PURE__ */ jsx("span", { children: "Secured by Cavos" })
|
|
600
|
+
] })
|
|
601
|
+
] }) });
|
|
602
|
+
}
|
|
603
|
+
if (screen === "passkey-approval") {
|
|
604
|
+
return /* @__PURE__ */ jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, children: /* @__PURE__ */ jsxs("div", { style: { ...card, position: "relative" }, children: [
|
|
605
|
+
isMobile && /* @__PURE__ */ jsx("div", { style: handle }),
|
|
606
|
+
/* @__PURE__ */ jsx("button", { className: "cavos-close", style: close, onClick: handleClose, "aria-label": "Close", children: /* @__PURE__ */ jsx(CloseX, {}) }),
|
|
607
|
+
/* @__PURE__ */ jsxs("div", { style: { padding: isMobile ? "28px 24px 32px" : "48px 24px 32px", textAlign: "center" }, children: [
|
|
608
|
+
/* @__PURE__ */ jsx("div", { style: { width: 48, height: 48, borderRadius: "50%", margin: "0 auto 16px", background: isLight ? "rgba(0,0,0,0.04)" : "rgba(255,255,255,0.06)", border: `1px solid ${isLight ? "rgba(0,0,0,0.08)" : "rgba(255,255,255,0.1)"}`, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx(FingerprintIcon, { size: 24, color: textColor }) }),
|
|
609
|
+
/* @__PURE__ */ jsx("h2", { style: { margin: "0 0 8px", fontSize: "17px", fontWeight: 600, color: textColor, letterSpacing: "-0.02em" }, children: "Verify it's you" }),
|
|
610
|
+
/* @__PURE__ */ jsx("p", { style: { margin: "0 0 20px", fontSize: "13px", color: subTextColor, lineHeight: 1.55 }, children: "Confirm with Face ID, Touch ID, or your device PIN to add this device to your account." }),
|
|
611
|
+
pkError && /* @__PURE__ */ jsx("div", { style: { background: errBg, border: `1px solid ${errBorder}`, borderRadius: "10px", padding: "9px 13px", fontSize: "13px", color: errColor, marginBottom: "14px" }, children: pkError }),
|
|
612
|
+
/* @__PURE__ */ jsxs("button", { className: "cavos-primary-btn", style: { width: "100%", padding: "12px", borderRadius: "12px", border: "none", background: primaryColor, color: "#fff", fontSize: "14px", fontWeight: 500, cursor: pkBusy ? "default" : "pointer", fontFamily: "inherit", display: "flex", alignItems: "center", justifyContent: "center", gap: "8px", opacity: pkBusy ? 0.65 : 1 }, onClick: handlePasskeyApprove, disabled: pkBusy, children: [
|
|
613
|
+
pkBusy && /* @__PURE__ */ jsx(Spinner, { size: 15, color: "#fff" }),
|
|
614
|
+
pkBusy ? "Verifying\u2026" : "Continue with passkey"
|
|
615
|
+
] }),
|
|
616
|
+
walletStatus.awaitingApproval && /* @__PURE__ */ jsx("button", { className: "cavos-sub-btn", style: { background: "none", border: "none", cursor: "pointer", fontSize: "13px", color: subTextColor, width: "100%", textAlign: "center", padding: "16px 0 0", fontFamily: "inherit", transition: "color 0.15s" }, onClick: () => {
|
|
617
|
+
setScreen("device-approval");
|
|
618
|
+
setPkError("");
|
|
619
|
+
}, children: "Approve by email instead" }),
|
|
620
|
+
/* @__PURE__ */ jsx("button", { className: "cavos-sub-btn", style: { background: "none", border: "none", cursor: "pointer", fontSize: "13px", color: subTextColor, width: "100%", textAlign: "center", padding: `${walletStatus.awaitingApproval ? "10px" : "16px"} 0 0`, fontFamily: "inherit", transition: "color 0.15s" }, onClick: () => {
|
|
621
|
+
setScreen("recover");
|
|
622
|
+
setPkError("");
|
|
623
|
+
}, children: "Use a recovery phrase" })
|
|
624
|
+
] }),
|
|
625
|
+
/* @__PURE__ */ jsxs("div", { style: footer, children: [
|
|
626
|
+
/* @__PURE__ */ jsx(CavosLogo, { invert: !isLight }),
|
|
627
|
+
/* @__PURE__ */ jsx("span", { children: "Secured by Cavos" })
|
|
628
|
+
] })
|
|
629
|
+
] }) });
|
|
630
|
+
}
|
|
466
631
|
if (screen === "device-approval") {
|
|
467
632
|
const expired = walletStatus.awaitingApproval && !walletStatus.pendingRequestId;
|
|
468
633
|
return /* @__PURE__ */ jsx("div", { style: overlay, "data-cavos-theme": theme, role: "dialog", "aria-modal": true, children: /* @__PURE__ */ jsxs("div", { style: { ...card, position: "relative" }, children: [
|
|
@@ -522,7 +687,7 @@ function CavosAuthModal({
|
|
|
522
687
|
}, children: /* @__PURE__ */ jsxs("div", { style: { ...card, position: "relative" }, children: [
|
|
523
688
|
isMobile && /* @__PURE__ */ jsx("div", { style: handle }),
|
|
524
689
|
/* @__PURE__ */ jsx("button", { className: "cavos-close", style: { ...close, left: "16px", right: "auto" }, onClick: () => {
|
|
525
|
-
setScreen("device-approval");
|
|
690
|
+
setScreen(walletStatus.hasPasskey && passkeySupported ? "passkey-approval" : "device-approval");
|
|
526
691
|
setError("");
|
|
527
692
|
setRecoverCode("");
|
|
528
693
|
}, "aria-label": "Back", children: /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M19 12H5M12 5l-7 7 7 7" }) }) }),
|
|
@@ -761,23 +926,36 @@ var INITIAL_STATUS = {
|
|
|
761
926
|
isReady: false,
|
|
762
927
|
needsDeviceApproval: false,
|
|
763
928
|
awaitingApproval: false,
|
|
764
|
-
pendingRequestId: null
|
|
929
|
+
pendingRequestId: null,
|
|
930
|
+
hasPasskey: false,
|
|
931
|
+
isNewAccount: false
|
|
765
932
|
};
|
|
766
933
|
function CavosProvider({ config, modal, children }) {
|
|
767
934
|
const [auth] = useState(
|
|
768
935
|
() => new CavosAuth({ appId: config.appId, backendUrl: config.authBackendUrl })
|
|
769
936
|
);
|
|
770
|
-
const [
|
|
937
|
+
const [wallet, setWallet] = useState(null);
|
|
771
938
|
const [identity, setIdentity] = useState(null);
|
|
772
939
|
const [walletStatus, setWalletStatus] = useState(INITIAL_STATUS);
|
|
773
940
|
const [isLoading, setIsLoading] = useState(false);
|
|
774
941
|
const [authError, setAuthError] = useState(null);
|
|
775
942
|
const [modalOpen, setModalOpen] = useState(false);
|
|
943
|
+
const [passkeySupported, setPasskeySupported] = useState(false);
|
|
776
944
|
const [branding, setBranding] = useState({});
|
|
945
|
+
useEffect(() => {
|
|
946
|
+
let cancelled = false;
|
|
947
|
+
PasskeySigner.isSupported().then((ok) => {
|
|
948
|
+
if (!cancelled) setPasskeySupported(ok);
|
|
949
|
+
}).catch(() => {
|
|
950
|
+
});
|
|
951
|
+
return () => {
|
|
952
|
+
cancelled = true;
|
|
953
|
+
};
|
|
954
|
+
}, []);
|
|
777
955
|
const configRef = useRef(config);
|
|
778
956
|
useEffect(() => {
|
|
779
957
|
configRef.current = config;
|
|
780
|
-
}
|
|
958
|
+
});
|
|
781
959
|
useEffect(() => {
|
|
782
960
|
if (!config.appId || typeof window === "undefined") return;
|
|
783
961
|
const base = config.authBackendUrl ?? "https://cavos.xyz";
|
|
@@ -791,41 +969,52 @@ function CavosProvider({ config, modal, children }) {
|
|
|
791
969
|
const closeModal = useCallback(() => setModalOpen(false), []);
|
|
792
970
|
const clearAuthError = useCallback(() => setAuthError(null), []);
|
|
793
971
|
const resendDeviceApproval = useCallback(async () => {
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
972
|
+
const cfg = configRef.current;
|
|
973
|
+
if (!identity || !wallet || wallet.chain !== "starknet" || !wallet.pendingRequestId) return;
|
|
974
|
+
const backendUrl = cfg.authBackendUrl ?? "https://cavos.xyz";
|
|
975
|
+
if (!cfg.appId) return;
|
|
976
|
+
const recovery = new HttpRecoveryClient({ baseUrl: backendUrl, appId: cfg.appId });
|
|
798
977
|
await recovery.requestDeviceAddition({
|
|
799
978
|
userId: identity.userId,
|
|
800
|
-
accountAddress:
|
|
801
|
-
newSigner:
|
|
979
|
+
accountAddress: wallet.address,
|
|
980
|
+
newSigner: wallet.publicKey,
|
|
802
981
|
...identity.email ? { email: identity.email } : {}
|
|
803
982
|
});
|
|
804
|
-
}, [identity,
|
|
805
|
-
const connect = useCallback(async (id) => {
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
983
|
+
}, [identity, wallet]);
|
|
984
|
+
const connect = useCallback(async (id, opts) => {
|
|
985
|
+
const cfg = configRef.current;
|
|
986
|
+
if (!opts?.silent) setWalletStatus({ ...INITIAL_STATUS, isDeploying: true });
|
|
987
|
+
const w = await Cavos.connect({
|
|
988
|
+
chain: cfg.chain ?? "starknet",
|
|
989
|
+
network: cfg.network,
|
|
810
990
|
identity: id,
|
|
811
|
-
appSalt:
|
|
812
|
-
...
|
|
813
|
-
...
|
|
814
|
-
...
|
|
815
|
-
...
|
|
991
|
+
appSalt: cfg.appSalt,
|
|
992
|
+
...cfg.paymasterApiKey ? { paymasterApiKey: cfg.paymasterApiKey } : {},
|
|
993
|
+
...cfg.appId ? { appId: cfg.appId } : {},
|
|
994
|
+
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
|
|
995
|
+
...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
|
|
816
996
|
});
|
|
817
|
-
|
|
997
|
+
setWallet(w);
|
|
818
998
|
setIdentity(id);
|
|
819
|
-
const pendingRequestId =
|
|
999
|
+
const pendingRequestId = w.chain === "starknet" ? w.pendingRequestId : null;
|
|
1000
|
+
let hasPasskey = false;
|
|
1001
|
+
if (w.status === "needs-device-approval") {
|
|
1002
|
+
try {
|
|
1003
|
+
hasPasskey = await w.hasPasskey();
|
|
1004
|
+
} catch {
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
820
1007
|
setWalletStatus({
|
|
821
1008
|
isDeploying: false,
|
|
822
|
-
isReady:
|
|
823
|
-
needsDeviceApproval:
|
|
824
|
-
awaitingApproval:
|
|
825
|
-
pendingRequestId
|
|
1009
|
+
isReady: w.status === "ready",
|
|
1010
|
+
needsDeviceApproval: w.status === "needs-device-approval",
|
|
1011
|
+
awaitingApproval: w.status === "needs-device-approval" && !!pendingRequestId,
|
|
1012
|
+
pendingRequestId,
|
|
1013
|
+
hasPasskey,
|
|
1014
|
+
isNewAccount: w.isNewAccount
|
|
826
1015
|
});
|
|
827
|
-
modal?.onSuccess?.(
|
|
828
|
-
return
|
|
1016
|
+
modal?.onSuccess?.(w.address);
|
|
1017
|
+
return w;
|
|
829
1018
|
}, [modal]);
|
|
830
1019
|
useEffect(() => {
|
|
831
1020
|
if (typeof window === "undefined") return;
|
|
@@ -876,76 +1065,145 @@ function CavosProvider({ config, modal, children }) {
|
|
|
876
1065
|
const id = await auth.verifyOtp(email, code);
|
|
877
1066
|
await connect(id);
|
|
878
1067
|
}, [auth, connect]);
|
|
879
|
-
const execute = useCallback(async (calls) => {
|
|
880
|
-
if (!
|
|
881
|
-
if (
|
|
1068
|
+
const execute = useCallback(async (calls, opts) => {
|
|
1069
|
+
if (!wallet) throw new Error("Not logged in");
|
|
1070
|
+
if (wallet.chain !== "starknet") {
|
|
882
1071
|
throw new Error(
|
|
883
|
-
"kit: useCavos().execute(calls) is Starknet-only. On Solana use the `wallet` handle: wallet.execute(amount, dest)."
|
|
1072
|
+
"kit: useCavos().execute(calls) is Starknet-only. On Solana/Stellar use the `wallet` handle: wallet.execute(amount, dest)."
|
|
884
1073
|
);
|
|
885
1074
|
}
|
|
886
|
-
return
|
|
887
|
-
}, [
|
|
1075
|
+
return wallet.execute(calls, opts);
|
|
1076
|
+
}, [wallet]);
|
|
888
1077
|
const addSigner = useCallback(
|
|
889
1078
|
async (pubkey) => {
|
|
890
|
-
if (!
|
|
891
|
-
if (
|
|
892
|
-
throw new Error("kit: addSigner via useCavos() is Starknet-only; use the `wallet` handle on
|
|
1079
|
+
if (!wallet) throw new Error("Not logged in");
|
|
1080
|
+
if (wallet.chain !== "starknet") {
|
|
1081
|
+
throw new Error("kit: addSigner via useCavos() is Starknet-only; use the `wallet` handle on other chains.");
|
|
893
1082
|
}
|
|
894
|
-
return
|
|
1083
|
+
return wallet.addSigner(pubkey);
|
|
895
1084
|
},
|
|
896
|
-
[
|
|
1085
|
+
[wallet]
|
|
897
1086
|
);
|
|
898
1087
|
const enrollPasskey = useCallback(
|
|
899
1088
|
async (passkey, params) => {
|
|
900
|
-
if (!
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
const approveThisDeviceWithPasskey = useCallback(
|
|
906
|
-
async (passkey, submit) => {
|
|
907
|
-
if (!cavos) throw new Error("Not logged in");
|
|
908
|
-
if (cavos.chain === "starknet") {
|
|
909
|
-
return cavos.approveThisDeviceWithPasskey({ passkey, ...submit ? { submit } : {} });
|
|
1089
|
+
if (!wallet) throw new Error("Not logged in");
|
|
1090
|
+
if (wallet.chain === "stellar") {
|
|
1091
|
+
throw new Error(
|
|
1092
|
+
"kit: on Stellar, use enrollPasskeyDefault() \u2014 the passkey factor is a WebAuthn PRF secret, not a signer object."
|
|
1093
|
+
);
|
|
910
1094
|
}
|
|
911
|
-
|
|
912
|
-
return { transactionHash };
|
|
1095
|
+
return wallet.enrollPasskey(passkey, params);
|
|
913
1096
|
},
|
|
914
|
-
[
|
|
1097
|
+
[wallet]
|
|
915
1098
|
);
|
|
1099
|
+
const rpName = branding.appName ?? modal?.appName ?? "Cavos";
|
|
1100
|
+
const enrollPasskeyDefault = useCallback(async () => {
|
|
1101
|
+
if (!wallet || !identity) throw new Error("Not logged in");
|
|
1102
|
+
if (wallet.status !== "ready") throw new Error("kit: no ready device to enroll a passkey on");
|
|
1103
|
+
if (wallet.chain === "stellar") {
|
|
1104
|
+
const prf = new PasskeyPrf({ rpName });
|
|
1105
|
+
const { secret } = await prf.enroll({
|
|
1106
|
+
userId: identity.userId,
|
|
1107
|
+
userName: identity.email ?? identity.userId,
|
|
1108
|
+
...identity.email ? { displayName: identity.email } : {}
|
|
1109
|
+
});
|
|
1110
|
+
await wallet.enrollPasskey(secret ?? await prf.getSecret());
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
const passkey = new PasskeySigner({ rpName });
|
|
1114
|
+
await wallet.enrollPasskey(passkey, {
|
|
1115
|
+
userId: identity.userId,
|
|
1116
|
+
userName: identity.email ?? identity.userId,
|
|
1117
|
+
...identity.email ? { displayName: identity.email } : {}
|
|
1118
|
+
});
|
|
1119
|
+
}, [wallet, identity, rpName]);
|
|
1120
|
+
const approveDeviceWithPasskey = useCallback(async () => {
|
|
1121
|
+
if (!wallet || !identity) throw new Error("Not logged in");
|
|
1122
|
+
if (wallet.status !== "needs-device-approval") {
|
|
1123
|
+
await connect(identity);
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
if (wallet.chain === "stellar") {
|
|
1127
|
+
const prf = new PasskeyPrf({ rpName });
|
|
1128
|
+
await wallet.approveThisDeviceWithPasskey(await prf.getSecret());
|
|
1129
|
+
} else if (wallet.chain === "starknet") {
|
|
1130
|
+
const passkey = new PasskeySigner({ rpName });
|
|
1131
|
+
await wallet.approveThisDeviceWithPasskey({ passkey });
|
|
1132
|
+
} else {
|
|
1133
|
+
const passkey = new PasskeySigner({ rpName });
|
|
1134
|
+
await wallet.approveThisDeviceWithPasskey(passkey);
|
|
1135
|
+
}
|
|
1136
|
+
setWalletStatus((s) => ({ ...s, isDeploying: true, needsDeviceApproval: false, awaitingApproval: false }));
|
|
1137
|
+
const deadline = Date.now() + 6e4;
|
|
1138
|
+
for (; ; ) {
|
|
1139
|
+
let ready = false;
|
|
1140
|
+
try {
|
|
1141
|
+
ready = await wallet.isReady();
|
|
1142
|
+
} catch {
|
|
1143
|
+
}
|
|
1144
|
+
if (ready) break;
|
|
1145
|
+
if (Date.now() > deadline) {
|
|
1146
|
+
setWalletStatus((s) => ({ ...s, isDeploying: false, needsDeviceApproval: true }));
|
|
1147
|
+
throw new Error(
|
|
1148
|
+
"Your device is being added, but it's taking longer than usual. Please try again in a moment."
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1151
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
1152
|
+
}
|
|
1153
|
+
await connect(identity, { silent: true });
|
|
1154
|
+
}, [wallet, identity, rpName, connect]);
|
|
916
1155
|
const setupRecovery = useCallback(async () => {
|
|
917
|
-
if (!
|
|
1156
|
+
if (!wallet) throw new Error("Not logged in");
|
|
918
1157
|
const code = generateRecoveryCode();
|
|
919
|
-
await
|
|
1158
|
+
await wallet.setupRecovery(code);
|
|
920
1159
|
return code;
|
|
921
|
-
}, [
|
|
1160
|
+
}, [wallet]);
|
|
922
1161
|
const recover = useCallback(async (code) => {
|
|
923
1162
|
if (!identity) throw new Error("Sign in first so we know which account to recover.");
|
|
1163
|
+
const cfg = configRef.current;
|
|
924
1164
|
setAuthError(null);
|
|
925
1165
|
setWalletStatus({ ...INITIAL_STATUS, isDeploying: true });
|
|
926
1166
|
try {
|
|
927
|
-
const chain =
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1167
|
+
const chain = cfg.chain ?? "starknet";
|
|
1168
|
+
let w;
|
|
1169
|
+
if (chain === "solana") {
|
|
1170
|
+
w = await CavosSolana.recover({
|
|
1171
|
+
code,
|
|
1172
|
+
identity,
|
|
1173
|
+
network: cfg.network === "mainnet" ? "solana-mainnet" : "solana-devnet",
|
|
1174
|
+
appSalt: cfg.appSalt,
|
|
1175
|
+
...cfg.appId ? { appId: cfg.appId } : {},
|
|
1176
|
+
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
|
|
1177
|
+
...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
|
|
1178
|
+
});
|
|
1179
|
+
} else if (chain === "stellar") {
|
|
1180
|
+
const sw = await Cavos.connect({
|
|
1181
|
+
chain: "stellar",
|
|
1182
|
+
network: cfg.network,
|
|
1183
|
+
identity,
|
|
1184
|
+
appSalt: cfg.appSalt,
|
|
1185
|
+
...cfg.appId ? { appId: cfg.appId } : {},
|
|
1186
|
+
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {}
|
|
1187
|
+
});
|
|
1188
|
+
if (sw.chain === "stellar" && sw.status === "needs-device-approval") {
|
|
1189
|
+
await sw.approveThisDeviceWithRecovery(code);
|
|
1190
|
+
}
|
|
1191
|
+
w = sw;
|
|
1192
|
+
} else {
|
|
1193
|
+
w = await Cavos.recover({
|
|
1194
|
+
code,
|
|
1195
|
+
identity,
|
|
1196
|
+
network: cfg.network,
|
|
1197
|
+
appSalt: cfg.appSalt,
|
|
1198
|
+
paymasterApiKey: cfg.paymasterApiKey ?? "",
|
|
1199
|
+
...cfg.appId ? { appId: cfg.appId } : {},
|
|
1200
|
+
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
|
|
1201
|
+
...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
|
|
1202
|
+
});
|
|
1203
|
+
}
|
|
1204
|
+
setWallet(w);
|
|
947
1205
|
setWalletStatus({ ...INITIAL_STATUS, isReady: true });
|
|
948
|
-
modal?.onSuccess?.(
|
|
1206
|
+
modal?.onSuccess?.(w.address);
|
|
949
1207
|
} catch (e) {
|
|
950
1208
|
const msg = e instanceof Error ? e.message : "Recovery failed. Check your code and try again.";
|
|
951
1209
|
setAuthError(msg);
|
|
@@ -955,9 +1213,10 @@ function CavosProvider({ config, modal, children }) {
|
|
|
955
1213
|
}, [identity, modal]);
|
|
956
1214
|
useEffect(() => {
|
|
957
1215
|
if (!walletStatus.awaitingApproval || !walletStatus.pendingRequestId || !identity) return;
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
const
|
|
1216
|
+
const cfg = configRef.current;
|
|
1217
|
+
if (!cfg.appId) return;
|
|
1218
|
+
const backendUrl = cfg.authBackendUrl ?? "https://cavos.xyz";
|
|
1219
|
+
const recovery = new HttpRecoveryClient({ baseUrl: backendUrl, appId: cfg.appId });
|
|
961
1220
|
const requestId = walletStatus.pendingRequestId;
|
|
962
1221
|
let cancelled = false;
|
|
963
1222
|
const tick = async () => {
|
|
@@ -981,7 +1240,7 @@ function CavosProvider({ config, modal, children }) {
|
|
|
981
1240
|
};
|
|
982
1241
|
}, [walletStatus.awaitingApproval, walletStatus.pendingRequestId, identity, connect]);
|
|
983
1242
|
const logout = useCallback(() => {
|
|
984
|
-
|
|
1243
|
+
setWallet(null);
|
|
985
1244
|
setIdentity(null);
|
|
986
1245
|
setWalletStatus(INITIAL_STATUS);
|
|
987
1246
|
setAuthError(null);
|
|
@@ -989,11 +1248,11 @@ function CavosProvider({ config, modal, children }) {
|
|
|
989
1248
|
const value = {
|
|
990
1249
|
openModal,
|
|
991
1250
|
closeModal,
|
|
992
|
-
isAuthenticated: !!
|
|
1251
|
+
isAuthenticated: !!wallet,
|
|
993
1252
|
user: identity ? { userId: identity.userId, email: identity.email, provider: identity.provider } : null,
|
|
994
1253
|
chain: config.chain ?? "starknet",
|
|
995
|
-
wallet
|
|
996
|
-
address:
|
|
1254
|
+
wallet,
|
|
1255
|
+
address: wallet?.address ?? null,
|
|
997
1256
|
walletStatus,
|
|
998
1257
|
isLoading,
|
|
999
1258
|
authError,
|
|
@@ -1006,7 +1265,9 @@ function CavosProvider({ config, modal, children }) {
|
|
|
1006
1265
|
execute,
|
|
1007
1266
|
addSigner,
|
|
1008
1267
|
enrollPasskey,
|
|
1009
|
-
|
|
1268
|
+
passkeySupported,
|
|
1269
|
+
enrollPasskeyDefault,
|
|
1270
|
+
approveDeviceWithPasskey,
|
|
1010
1271
|
resendDeviceApproval,
|
|
1011
1272
|
setupRecovery,
|
|
1012
1273
|
recover,
|