@cavos/kit 0.0.3 → 0.0.4
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-x9qFpOvJ.d.mts} +30 -1
- package/dist/{Cavos-BH2_tOQ2.d.ts → Cavos-x9qFpOvJ.d.ts} +30 -1
- package/dist/{chunk-BNGLH3Q3.mjs → chunk-F2J25XSL.mjs} +193 -24
- package/dist/chunk-F2J25XSL.mjs.map +1 -0
- package/dist/index.d.mts +8 -2
- package/dist/index.d.ts +8 -2
- package/dist/index.js +104 -19
- 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 +36 -28
- package/dist/react/index.d.ts +36 -28
- package/dist/react/index.js +549 -100
- package/dist/react/index.js.map +1 -1
- package/dist/react/index.mjs +330 -91
- 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, generateRecoveryCode, CavosSolana, CavosStellar } from '../chunk-F2J25XSL.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;
|
|
@@ -877,75 +1066,122 @@ function CavosProvider({ config, modal, children }) {
|
|
|
877
1066
|
await connect(id);
|
|
878
1067
|
}, [auth, connect]);
|
|
879
1068
|
const execute = useCallback(async (calls) => {
|
|
880
|
-
if (!
|
|
881
|
-
if (
|
|
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);
|
|
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
|
-
return
|
|
1089
|
+
if (!wallet) throw new Error("Not logged in");
|
|
1090
|
+
return wallet.enrollPasskey(passkey, params);
|
|
902
1091
|
},
|
|
903
|
-
[
|
|
1092
|
+
[wallet]
|
|
904
1093
|
);
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1094
|
+
const rpName = branding.appName ?? modal?.appName ?? "Cavos";
|
|
1095
|
+
const enrollPasskeyDefault = useCallback(async () => {
|
|
1096
|
+
if (!wallet || !identity) throw new Error("Not logged in");
|
|
1097
|
+
if (wallet.status !== "ready") throw new Error("kit: no ready device to enroll a passkey on");
|
|
1098
|
+
const passkey = new PasskeySigner({ rpName });
|
|
1099
|
+
await wallet.enrollPasskey(passkey, {
|
|
1100
|
+
userId: identity.userId,
|
|
1101
|
+
userName: identity.email ?? identity.userId,
|
|
1102
|
+
...identity.email ? { displayName: identity.email } : {}
|
|
1103
|
+
});
|
|
1104
|
+
}, [wallet, identity, rpName]);
|
|
1105
|
+
const approveDeviceWithPasskey = useCallback(async () => {
|
|
1106
|
+
if (!wallet || !identity) throw new Error("Not logged in");
|
|
1107
|
+
if (wallet.status !== "needs-device-approval") {
|
|
1108
|
+
await connect(identity);
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
const passkey = new PasskeySigner({ rpName });
|
|
1112
|
+
if (wallet.chain === "starknet") {
|
|
1113
|
+
await wallet.approveThisDeviceWithPasskey({ passkey });
|
|
1114
|
+
} else {
|
|
1115
|
+
await wallet.approveThisDeviceWithPasskey(passkey);
|
|
1116
|
+
}
|
|
1117
|
+
setWalletStatus((s) => ({ ...s, isDeploying: true, needsDeviceApproval: false, awaitingApproval: false }));
|
|
1118
|
+
const deadline = Date.now() + 6e4;
|
|
1119
|
+
for (; ; ) {
|
|
1120
|
+
let ready = false;
|
|
1121
|
+
try {
|
|
1122
|
+
ready = await wallet.isReady();
|
|
1123
|
+
} catch {
|
|
910
1124
|
}
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1125
|
+
if (ready) break;
|
|
1126
|
+
if (Date.now() > deadline) {
|
|
1127
|
+
setWalletStatus((s) => ({ ...s, isDeploying: false, needsDeviceApproval: true }));
|
|
1128
|
+
throw new Error(
|
|
1129
|
+
"Your device is being added, but it's taking longer than usual. Please try again in a moment."
|
|
1130
|
+
);
|
|
1131
|
+
}
|
|
1132
|
+
await new Promise((r) => setTimeout(r, 3e3));
|
|
1133
|
+
}
|
|
1134
|
+
await connect(identity, { silent: true });
|
|
1135
|
+
}, [wallet, identity, rpName, connect]);
|
|
916
1136
|
const setupRecovery = useCallback(async () => {
|
|
917
|
-
if (!
|
|
1137
|
+
if (!wallet) throw new Error("Not logged in");
|
|
918
1138
|
const code = generateRecoveryCode();
|
|
919
|
-
await
|
|
1139
|
+
await wallet.setupRecovery(code);
|
|
920
1140
|
return code;
|
|
921
|
-
}, [
|
|
1141
|
+
}, [wallet]);
|
|
922
1142
|
const recover = useCallback(async (code) => {
|
|
923
1143
|
if (!identity) throw new Error("Sign in first so we know which account to recover.");
|
|
1144
|
+
const cfg = configRef.current;
|
|
924
1145
|
setAuthError(null);
|
|
925
1146
|
setWalletStatus({ ...INITIAL_STATUS, isDeploying: true });
|
|
926
1147
|
try {
|
|
927
|
-
const chain =
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1148
|
+
const chain = cfg.chain ?? "starknet";
|
|
1149
|
+
let w;
|
|
1150
|
+
if (chain === "solana") {
|
|
1151
|
+
w = await CavosSolana.recover({
|
|
1152
|
+
code,
|
|
1153
|
+
identity,
|
|
1154
|
+
network: cfg.network === "mainnet" ? "solana-mainnet" : "solana-devnet",
|
|
1155
|
+
appSalt: cfg.appSalt,
|
|
1156
|
+
...cfg.appId ? { appId: cfg.appId } : {},
|
|
1157
|
+
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
|
|
1158
|
+
...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
|
|
1159
|
+
});
|
|
1160
|
+
} else if (chain === "stellar") {
|
|
1161
|
+
w = await CavosStellar.recover({
|
|
1162
|
+
code,
|
|
1163
|
+
identity,
|
|
1164
|
+
network: cfg.network === "mainnet" ? "stellar-mainnet" : "stellar-testnet",
|
|
1165
|
+
appSalt: cfg.appSalt,
|
|
1166
|
+
...cfg.appId ? { appId: cfg.appId } : {},
|
|
1167
|
+
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
|
|
1168
|
+
...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
|
|
1169
|
+
});
|
|
1170
|
+
} else {
|
|
1171
|
+
w = await Cavos.recover({
|
|
1172
|
+
code,
|
|
1173
|
+
identity,
|
|
1174
|
+
network: cfg.network,
|
|
1175
|
+
appSalt: cfg.appSalt,
|
|
1176
|
+
paymasterApiKey: cfg.paymasterApiKey ?? "",
|
|
1177
|
+
...cfg.appId ? { appId: cfg.appId } : {},
|
|
1178
|
+
...cfg.authBackendUrl ? { backendUrl: cfg.authBackendUrl } : {},
|
|
1179
|
+
...cfg.rpcUrl ? { rpcUrl: cfg.rpcUrl } : {}
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1182
|
+
setWallet(w);
|
|
947
1183
|
setWalletStatus({ ...INITIAL_STATUS, isReady: true });
|
|
948
|
-
modal?.onSuccess?.(
|
|
1184
|
+
modal?.onSuccess?.(w.address);
|
|
949
1185
|
} catch (e) {
|
|
950
1186
|
const msg = e instanceof Error ? e.message : "Recovery failed. Check your code and try again.";
|
|
951
1187
|
setAuthError(msg);
|
|
@@ -955,9 +1191,10 @@ function CavosProvider({ config, modal, children }) {
|
|
|
955
1191
|
}, [identity, modal]);
|
|
956
1192
|
useEffect(() => {
|
|
957
1193
|
if (!walletStatus.awaitingApproval || !walletStatus.pendingRequestId || !identity) return;
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
const
|
|
1194
|
+
const cfg = configRef.current;
|
|
1195
|
+
if (!cfg.appId) return;
|
|
1196
|
+
const backendUrl = cfg.authBackendUrl ?? "https://cavos.xyz";
|
|
1197
|
+
const recovery = new HttpRecoveryClient({ baseUrl: backendUrl, appId: cfg.appId });
|
|
961
1198
|
const requestId = walletStatus.pendingRequestId;
|
|
962
1199
|
let cancelled = false;
|
|
963
1200
|
const tick = async () => {
|
|
@@ -981,7 +1218,7 @@ function CavosProvider({ config, modal, children }) {
|
|
|
981
1218
|
};
|
|
982
1219
|
}, [walletStatus.awaitingApproval, walletStatus.pendingRequestId, identity, connect]);
|
|
983
1220
|
const logout = useCallback(() => {
|
|
984
|
-
|
|
1221
|
+
setWallet(null);
|
|
985
1222
|
setIdentity(null);
|
|
986
1223
|
setWalletStatus(INITIAL_STATUS);
|
|
987
1224
|
setAuthError(null);
|
|
@@ -989,11 +1226,11 @@ function CavosProvider({ config, modal, children }) {
|
|
|
989
1226
|
const value = {
|
|
990
1227
|
openModal,
|
|
991
1228
|
closeModal,
|
|
992
|
-
isAuthenticated: !!
|
|
1229
|
+
isAuthenticated: !!wallet,
|
|
993
1230
|
user: identity ? { userId: identity.userId, email: identity.email, provider: identity.provider } : null,
|
|
994
1231
|
chain: config.chain ?? "starknet",
|
|
995
|
-
wallet
|
|
996
|
-
address:
|
|
1232
|
+
wallet,
|
|
1233
|
+
address: wallet?.address ?? null,
|
|
997
1234
|
walletStatus,
|
|
998
1235
|
isLoading,
|
|
999
1236
|
authError,
|
|
@@ -1006,7 +1243,9 @@ function CavosProvider({ config, modal, children }) {
|
|
|
1006
1243
|
execute,
|
|
1007
1244
|
addSigner,
|
|
1008
1245
|
enrollPasskey,
|
|
1009
|
-
|
|
1246
|
+
passkeySupported,
|
|
1247
|
+
enrollPasskeyDefault,
|
|
1248
|
+
approveDeviceWithPasskey,
|
|
1010
1249
|
resendDeviceApproval,
|
|
1011
1250
|
setupRecovery,
|
|
1012
1251
|
recover,
|