@orangecheck/auth-client 2.25.3 → 2.27.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/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import * as React3 from 'react';
1
+ import * as React4 from 'react';
2
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
3
3
  import { startRegistration, startAuthentication } from '@simplewebauthn/browser';
4
4
 
@@ -199,7 +199,7 @@ async function consumeTabAccountHint(authOrigin) {
199
199
  }
200
200
  return null;
201
201
  }
202
- var SessionContext = React3.createContext(null);
202
+ var SessionContext = React4.createContext(null);
203
203
  function normalizeDisplayIdentity(raw, didOc) {
204
204
  const di = raw.display_identity ?? raw.displayIdentity;
205
205
  if (di && typeof di === "object" && typeof di.value === "string" && di.value.length > 0 && typeof di.kind === "string" && DISPLAY_IDENTITY_KINDS.includes(di.kind)) {
@@ -257,14 +257,14 @@ function OcSessionProvider({
257
257
  config,
258
258
  defaultReturnTo
259
259
  }) {
260
- const cfg = React3.useMemo(() => resolveConfig(config), [config]);
261
- const [account, setAccount] = React3.useState(null);
262
- const [roster, setRoster] = React3.useState([]);
263
- const [status, setStatus] = React3.useState("loading");
264
- const [error, setError] = React3.useState(null);
265
- const [tabPinned, setTabPinned] = React3.useState(false);
266
- const pinInFlightRef = React3.useRef(false);
267
- const pinThisTab = React3.useCallback(
260
+ const cfg = React4.useMemo(() => resolveConfig(config), [config]);
261
+ const [account, setAccount] = React4.useState(null);
262
+ const [roster, setRoster] = React4.useState([]);
263
+ const [status, setStatus] = React4.useState("loading");
264
+ const [error, setError] = React4.useState(null);
265
+ const [tabPinned, setTabPinned] = React4.useState(false);
266
+ const pinInFlightRef = React4.useRef(false);
267
+ const pinThisTab = React4.useCallback(
268
268
  async (didOc) => {
269
269
  if (pinInFlightRef.current || readTabSession()) return;
270
270
  pinInFlightRef.current = true;
@@ -287,7 +287,7 @@ function OcSessionProvider({
287
287
  },
288
288
  [cfg.authOrigin]
289
289
  );
290
- const refresh = React3.useCallback(async () => {
290
+ const refresh = React4.useCallback(async () => {
291
291
  if (typeof window === "undefined") return;
292
292
  try {
293
293
  let pin = readTabSession();
@@ -341,7 +341,7 @@ function OcSessionProvider({
341
341
  setError(err instanceof Error ? err : new Error(String(err)));
342
342
  }
343
343
  }, [cfg, pinThisTab]);
344
- React3.useEffect(() => {
344
+ React4.useEffect(() => {
345
345
  let cancelled = false;
346
346
  consumeTabAdoptMarker();
347
347
  void (async () => {
@@ -352,9 +352,9 @@ function OcSessionProvider({
352
352
  cancelled = true;
353
353
  };
354
354
  }, [refresh, cfg.authOrigin]);
355
- React3.useEffect(() => installTabFetchInterceptor(cfg.authOrigin), [cfg.authOrigin]);
356
- React3.useEffect(() => installTabLinkDecorator(cfg.authOrigin), [cfg.authOrigin]);
357
- const signOut = React3.useCallback(
355
+ React4.useEffect(() => installTabFetchInterceptor(cfg.authOrigin), [cfg.authOrigin]);
356
+ React4.useEffect(() => installTabLinkDecorator(cfg.authOrigin), [cfg.authOrigin]);
357
+ const signOut = React4.useCallback(
358
358
  async (opts) => {
359
359
  const scope = opts?.scope ?? "all";
360
360
  try {
@@ -390,7 +390,7 @@ function OcSessionProvider({
390
390
  },
391
391
  [cfg.authOrigin, cfg.logoutPath, refresh]
392
392
  );
393
- const switchAccount = React3.useCallback(
393
+ const switchAccount = React4.useCallback(
394
394
  async (didOc) => {
395
395
  if (typeof window === "undefined") return;
396
396
  const res = await fetch(`${cfg.authOrigin}/api/auth/switch`, {
@@ -420,14 +420,14 @@ function OcSessionProvider({
420
420
  },
421
421
  [cfg.authOrigin, refresh]
422
422
  );
423
- const addAccountUrl = React3.useCallback(
423
+ const addAccountUrl = React4.useCallback(
424
424
  (returnTo) => {
425
425
  const rt = returnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
426
426
  return buildAddAccountUrl(cfg, rt);
427
427
  },
428
428
  [cfg]
429
429
  );
430
- const setDisplayIdentity = React3.useCallback(
430
+ const setDisplayIdentity = React4.useCallback(
431
431
  async (kind) => {
432
432
  if (typeof window === "undefined") return;
433
433
  const res = await fetch(`${cfg.authOrigin}/api/auth/account`, {
@@ -457,7 +457,7 @@ function OcSessionProvider({
457
457
  },
458
458
  [cfg.authOrigin, refresh]
459
459
  );
460
- const value = React3.useMemo(() => {
460
+ const value = React4.useMemo(() => {
461
461
  const returnTo = defaultReturnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
462
462
  return {
463
463
  status,
@@ -489,7 +489,7 @@ function OcSessionProvider({
489
489
  return /* @__PURE__ */ jsx(SessionContext.Provider, { value, children });
490
490
  }
491
491
  function useOcSession() {
492
- const ctx = React3.useContext(SessionContext);
492
+ const ctx = React4.useContext(SessionContext);
493
493
  if (!ctx) {
494
494
  throw new Error(
495
495
  "[@orangecheck/auth-client] useOcSession() must be called inside <OcSessionProvider>"
@@ -498,7 +498,7 @@ function useOcSession() {
498
498
  return ctx;
499
499
  }
500
500
  function useOptionalOcSession() {
501
- return React3.useContext(SessionContext);
501
+ return React4.useContext(SessionContext);
502
502
  }
503
503
  function shortenAddress(addr) {
504
504
  if (addr.length <= 12) return addr;
@@ -513,7 +513,7 @@ function isPrefixOf(value, target) {
513
513
  }
514
514
  var listboxIdCounter = 0;
515
515
  function useUniqueId(prefix) {
516
- const [id] = React3.useState(() => `${prefix}-${++listboxIdCounter}`);
516
+ const [id] = React4.useState(() => `${prefix}-${++listboxIdCounter}`);
517
517
  return id;
518
518
  }
519
519
  function OcAccountChip({
@@ -527,9 +527,9 @@ function OcAccountChip({
527
527
  }) {
528
528
  const { status, account, signInUrl: sessionSignInUrl, signOut } = useOcSession();
529
529
  const signInUrl = signInUrlOverride ?? sessionSignInUrl;
530
- const [open, setOpen] = React3.useState(false);
531
- const wrapRef = React3.useRef(null);
532
- React3.useEffect(() => {
530
+ const [open, setOpen] = React4.useState(false);
531
+ const wrapRef = React4.useRef(null);
532
+ React4.useEffect(() => {
533
533
  if (!open) return;
534
534
  function handleClickOutside(e) {
535
535
  if (wrapRef.current && !wrapRef.current.contains(e.target)) {
@@ -852,9 +852,9 @@ function useOcAddressSuggestion(options) {
852
852
  } = options;
853
853
  const { status, account } = useOcSession();
854
854
  const sessionAddress = status === "authenticated" ? account?.primaryBtc ?? null : null;
855
- const [open, setOpen] = React3.useState(false);
856
- const [highlighted, setHighlighted] = React3.useState(false);
857
- const blurTimer = React3.useRef(null);
855
+ const [open, setOpen] = React4.useState(false);
856
+ const [highlighted, setHighlighted] = React4.useState(false);
857
+ const blurTimer = React4.useRef(null);
858
858
  const listboxId = useUniqueId("oc-addr-listbox");
859
859
  const optionId = `${listboxId}-opt`;
860
860
  const valueMatchesSession = sessionAddress != null && value.toLowerCase() === sessionAddress.toLowerCase();
@@ -866,7 +866,7 @@ function useOcAddressSuggestion(options) {
866
866
  setOpen(false);
867
867
  setHighlighted(false);
868
868
  }
869
- React3.useEffect(() => {
869
+ React4.useEffect(() => {
870
870
  return () => {
871
871
  if (blurTimer.current != null) window.clearTimeout(blurTimer.current);
872
872
  };
@@ -969,7 +969,7 @@ function useOcAddressSuggestion(options) {
969
969
  ) : null;
970
970
  return { inputProps, popover };
971
971
  }
972
- var OcAddressInput = React3.forwardRef(
972
+ var OcAddressInput = React4.forwardRef(
973
973
  function OcAddressInput2({
974
974
  value,
975
975
  onValueChange,
@@ -984,15 +984,15 @@ var OcAddressInput = React3.forwardRef(
984
984
  }, forwardedRef) {
985
985
  const { status, account } = useOcSession();
986
986
  const sessionAddress = status === "authenticated" ? account?.primaryBtc ?? null : null;
987
- const [open, setOpen] = React3.useState(false);
988
- const [highlighted, setHighlighted] = React3.useState(false);
989
- const innerRef = React3.useRef(null);
987
+ const [open, setOpen] = React4.useState(false);
988
+ const [highlighted, setHighlighted] = React4.useState(false);
989
+ const innerRef = React4.useRef(null);
990
990
  const setRef = (node) => {
991
991
  innerRef.current = node;
992
992
  if (typeof forwardedRef === "function") forwardedRef(node);
993
993
  else if (forwardedRef) forwardedRef.current = node;
994
994
  };
995
- const blurTimer = React3.useRef(null);
995
+ const blurTimer = React4.useRef(null);
996
996
  const listboxId = useUniqueId("oc-addr-listbox");
997
997
  const optionId = `${listboxId}-opt`;
998
998
  const valueMatchesSession = sessionAddress != null && value.toLowerCase() === sessionAddress.toLowerCase();
@@ -1039,7 +1039,7 @@ var OcAddressInput = React3.forwardRef(
1039
1039
  }
1040
1040
  onKeyDown?.(e);
1041
1041
  }
1042
- React3.useEffect(() => {
1042
+ React4.useEffect(() => {
1043
1043
  return () => {
1044
1044
  if (blurTimer.current != null) window.clearTimeout(blurTimer.current);
1045
1045
  };
@@ -1158,6 +1158,15 @@ var SUDO_ACCOUNT_MISMATCH_MESSAGE = "Switch this tab to that account first \u201
1158
1158
  function isSudoAccountMismatch(body) {
1159
1159
  return body?.reason === "sudo_account_mismatch";
1160
1160
  }
1161
+
1162
+ // src/appearance.ts
1163
+ var SIGNIN_FONT = "var(--oc-signin-font, ui-monospace, SFMono-Regular, monospace)";
1164
+ var SIGNIN_CASE = "var(--oc-signin-case, uppercase)";
1165
+ var signinTracking = (em) => `var(--oc-signin-tracking, ${em})`;
1166
+ var signinSize = (px) => `calc(${px}px * var(--oc-signin-scale, 1))`;
1167
+ var signinRadius = (px) => `var(--oc-signin-radius, ${px}px)`;
1168
+ var SIGNIN_BUTTON_RADIUS = "var(--oc-signin-button-radius, 6px)";
1169
+ var SIGNIN_STRONG = "var(--oc-signin-strong, 600)";
1161
1170
  var DEFAULT_AUTH_ORIGIN = "https://ochk.io";
1162
1171
  async function fetchOcLinkedIdentities(opts) {
1163
1172
  const authOrigin = opts?.authOrigin ?? DEFAULT_AUTH_ORIGIN;
@@ -1173,13 +1182,14 @@ async function fetchOcLinkedIdentities(opts) {
1173
1182
  function OcLinkedIdentities({
1174
1183
  authOrigin = DEFAULT_AUTH_ORIGIN,
1175
1184
  className,
1176
- onChange
1185
+ onChange,
1186
+ attestationLink = true
1177
1187
  }) {
1178
- const [resp, setResp] = React3.useState(null);
1179
- const [error, setError] = React3.useState(null);
1180
- const [showEmailLink, setShowEmailLink] = React3.useState(false);
1181
- const [showBtcLink, setShowBtcLink] = React3.useState(false);
1182
- const refresh = React3.useCallback(async () => {
1188
+ const [resp, setResp] = React4.useState(null);
1189
+ const [error, setError] = React4.useState(null);
1190
+ const [showEmailLink, setShowEmailLink] = React4.useState(false);
1191
+ const [showBtcLink, setShowBtcLink] = React4.useState(false);
1192
+ const refresh = React4.useCallback(async () => {
1183
1193
  setError(null);
1184
1194
  try {
1185
1195
  const r = await fetch(`${authOrigin}/api/auth/identities`, {
@@ -1198,10 +1208,10 @@ function OcLinkedIdentities({
1198
1208
  setError(e instanceof Error ? e.message : "load failed");
1199
1209
  }
1200
1210
  }, [authOrigin]);
1201
- React3.useEffect(() => {
1211
+ React4.useEffect(() => {
1202
1212
  void refresh();
1203
1213
  }, [refresh]);
1204
- const afterChange = React3.useCallback(() => {
1214
+ const afterChange = React4.useCallback(() => {
1205
1215
  setShowEmailLink(false);
1206
1216
  setShowBtcLink(false);
1207
1217
  void refresh();
@@ -1233,11 +1243,7 @@ function OcLinkedIdentities({
1233
1243
  return /* @__PURE__ */ jsxs("div", { className, "data-oc-li": "", style: { display: "grid", gap: 16 }, children: [
1234
1244
  /* @__PURE__ */ jsxs("div", { style: panelStyle(), children: [
1235
1245
  /* @__PURE__ */ jsx(SectionLabel, { children: "\xA7 what this is" }),
1236
- /* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
1237
- "Your public identity is the opaque ",
1238
- /* @__PURE__ */ jsx("code", { style: codeStyle, children: "did:oc:" }),
1239
- " DID. Below it sit your private linked identities \u2014 your email and your Bitcoin address. They never appear in dashboard chrome and never reach an integrator unless you grant the matching scope. Linking a second identity is also your account-recovery path: if you lose one, the other still signs you in."
1240
- ] })
1246
+ /* @__PURE__ */ jsx("p", { style: bodyStyle, children: "These are the ways you can sign in to this account. An app you sign in to sees one only if you grant it. Linking a second one is also your way back in: lose one, and the other still signs you in." })
1241
1247
  ] }),
1242
1248
  mergedFrom.length > 0 && /* @__PURE__ */ jsx(ConsolidationPanel, { entries: mergedFrom }),
1243
1249
  /* @__PURE__ */ jsxs("div", { style: panelStyle("plain"), "data-oc-li-rows": "", children: [
@@ -1290,7 +1296,7 @@ function OcLinkedIdentities({
1290
1296
  onCancel: () => setShowBtcLink(false)
1291
1297
  }
1292
1298
  ),
1293
- hasBtc && /* @__PURE__ */ jsx(AttestationLink, {})
1299
+ hasBtc && attestationLink && /* @__PURE__ */ jsx(AttestationLink, {})
1294
1300
  ] });
1295
1301
  }
1296
1302
  function IdentityGroup({
@@ -1334,10 +1340,10 @@ function IdentityRow({
1334
1340
  authOrigin,
1335
1341
  onChange
1336
1342
  }) {
1337
- const [shown, setShown] = React3.useState(false);
1338
- const [confirming, setConfirming] = React3.useState(false);
1339
- const [busy, setBusy] = React3.useState(false);
1340
- const [err, setErr] = React3.useState(null);
1343
+ const [shown, setShown] = React4.useState(false);
1344
+ const [confirming, setConfirming] = React4.useState(false);
1345
+ const [busy, setBusy] = React4.useState(false);
1346
+ const [err, setErr] = React4.useState(null);
1341
1347
  const masked = row.value ? mask(row.value, kind) : "\u2014 pending recovery \u2014";
1342
1348
  const display = shown && row.value ? row.value : masked;
1343
1349
  async function unlink() {
@@ -1375,8 +1381,8 @@ function IdentityRow({
1375
1381
  style: {
1376
1382
  marginTop: 4,
1377
1383
  color: V.foreground,
1378
- fontFamily: MONO,
1379
- fontSize: 12,
1384
+ fontFamily: SIGNIN_FONT,
1385
+ fontSize: signinSize(12),
1380
1386
  wordBreak: "break-all"
1381
1387
  },
1382
1388
  children: display
@@ -1454,8 +1460,8 @@ function ConsolidationPanel({ entries }) {
1454
1460
  gap: 8,
1455
1461
  padding: "8px 0",
1456
1462
  borderTop: `1px solid ${V.border}`,
1457
- fontFamily: MONO,
1458
- fontSize: 11
1463
+ fontFamily: SIGNIN_FONT,
1464
+ fontSize: signinSize(11)
1459
1465
  },
1460
1466
  children: [
1461
1467
  /* @__PURE__ */ jsx("code", { style: { color: V.foreground, wordBreak: "break-all" }, children: e.did_oc }),
@@ -1536,13 +1542,13 @@ function EmailLinkForm({
1536
1542
  onDone,
1537
1543
  onCancel
1538
1544
  }) {
1539
- const [step, setStep] = React3.useState("email");
1540
- const [email, setEmail] = React3.useState("");
1541
- const [code, setCode] = React3.useState("");
1542
- const [token, setToken] = React3.useState("");
1543
- const [busy, setBusy] = React3.useState(false);
1544
- const [err, setErr] = React3.useState(null);
1545
- const [transfer, setTransfer] = React3.useState(false);
1545
+ const [step, setStep] = React4.useState("email");
1546
+ const [email, setEmail] = React4.useState("");
1547
+ const [code, setCode] = React4.useState("");
1548
+ const [token, setToken] = React4.useState("");
1549
+ const [busy, setBusy] = React4.useState(false);
1550
+ const [err, setErr] = React4.useState(null);
1551
+ const [transfer, setTransfer] = React4.useState(false);
1546
1552
  async function start() {
1547
1553
  setBusy(true);
1548
1554
  setErr(null);
@@ -1685,12 +1691,12 @@ function BtcLinkForm({
1685
1691
  onDone,
1686
1692
  onCancel
1687
1693
  }) {
1688
- const [step, setStep] = React3.useState("request");
1689
- const [addr, setAddr] = React3.useState("");
1690
- const [challenge, setChallenge] = React3.useState("");
1691
- const [busy, setBusy] = React3.useState(false);
1692
- const [err, setErr] = React3.useState(null);
1693
- const [pendingSig, setPendingSig] = React3.useState(null);
1694
+ const [step, setStep] = React4.useState("request");
1695
+ const [addr, setAddr] = React4.useState("");
1696
+ const [challenge, setChallenge] = React4.useState("");
1697
+ const [busy, setBusy] = React4.useState(false);
1698
+ const [err, setErr] = React4.useState(null);
1699
+ const [pendingSig, setPendingSig] = React4.useState(null);
1694
1700
  async function getChallenge() {
1695
1701
  if (!didOc) {
1696
1702
  setErr("session not loaded yet \xB7 refresh and retry");
@@ -1758,7 +1764,7 @@ function BtcLinkForm({
1758
1764
  placeholder: "bc1q\u2026",
1759
1765
  autoCapitalize: "none",
1760
1766
  spellCheck: false,
1761
- style: { ...inputStyle, fontFamily: MONO }
1767
+ style: { ...inputStyle, fontFamily: SIGNIN_FONT }
1762
1768
  }
1763
1769
  ),
1764
1770
  err && /* @__PURE__ */ jsx(ErrorLine, { children: err }),
@@ -1788,7 +1794,7 @@ function BtcLinkForm({
1788
1794
  /* @__PURE__ */ jsxs("p", { style: bodyStyle, children: [
1789
1795
  "Sign the challenge with the wallet that controls",
1790
1796
  " ",
1791
- /* @__PURE__ */ jsx("span", { style: { fontFamily: MONO, wordBreak: "break-all" }, children: addr }),
1797
+ /* @__PURE__ */ jsx("span", { style: { fontFamily: SIGNIN_FONT, wordBreak: "break-all" }, children: addr }),
1792
1798
  "."
1793
1799
  ] }),
1794
1800
  /* @__PURE__ */ jsx(
@@ -1850,9 +1856,9 @@ function TransferConfirm({
1850
1856
  ] });
1851
1857
  }
1852
1858
  function LazyWalletButton(props) {
1853
- const [Comp, setComp] = React3.useState(null);
1854
- const [failed, setFailed] = React3.useState(false);
1855
- React3.useEffect(() => {
1859
+ const [Comp, setComp] = React4.useState(null);
1860
+ const [failed, setFailed] = React4.useState(false);
1861
+ React4.useEffect(() => {
1856
1862
  let alive = true;
1857
1863
  import('@orangecheck/wallet-adapter/react').then((m) => {
1858
1864
  if (alive) {
@@ -1896,10 +1902,10 @@ function SectionLabel({
1896
1902
  {
1897
1903
  style: {
1898
1904
  color,
1899
- fontFamily: MONO,
1900
- fontSize: 10,
1901
- letterSpacing: "0.18em",
1902
- textTransform: "uppercase",
1905
+ fontFamily: SIGNIN_FONT,
1906
+ fontSize: signinSize(10),
1907
+ letterSpacing: signinTracking("0.18em"),
1908
+ textTransform: SIGNIN_CASE,
1903
1909
  marginBottom: 8
1904
1910
  },
1905
1911
  children
@@ -1914,8 +1920,8 @@ function ErrorLine({ children }) {
1914
1920
  style: {
1915
1921
  margin: "8px 0 0",
1916
1922
  color: V.destructive,
1917
- fontFamily: MONO,
1918
- fontSize: 11
1923
+ fontFamily: SIGNIN_FONT,
1924
+ fontSize: signinSize(11)
1919
1925
  },
1920
1926
  children
1921
1927
  }
@@ -1924,7 +1930,6 @@ function ErrorLine({ children }) {
1924
1930
  function FormButtons({ children }) {
1925
1931
  return /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 8, marginTop: 12 }, children });
1926
1932
  }
1927
- var MONO = "ui-monospace, SFMono-Regular, monospace";
1928
1933
  var V = {
1929
1934
  primary: "var(--primary, #f97316)",
1930
1935
  foreground: "var(--foreground, #fafafa)",
@@ -1956,14 +1961,10 @@ var bodyStyle = {
1956
1961
  };
1957
1962
  var hintStyle = {
1958
1963
  color: V.muted,
1959
- fontFamily: MONO,
1960
- fontSize: 10.5,
1964
+ fontFamily: SIGNIN_FONT,
1965
+ fontSize: signinSize(10.5),
1961
1966
  opacity: 0.8
1962
1967
  };
1963
- var codeStyle = {
1964
- fontFamily: MONO,
1965
- color: V.foreground
1966
- };
1967
1968
  var rowStyle = {
1968
1969
  display: "flex",
1969
1970
  flexWrap: "wrap",
@@ -1974,10 +1975,10 @@ var rowStyle = {
1974
1975
  };
1975
1976
  var rowLabelStyle = {
1976
1977
  color: V.foreground,
1977
- fontFamily: MONO,
1978
- fontSize: 10.5,
1979
- letterSpacing: "0.12em",
1980
- textTransform: "uppercase",
1978
+ fontFamily: SIGNIN_FONT,
1979
+ fontSize: signinSize(10.5),
1980
+ letterSpacing: signinTracking("0.12em"),
1981
+ textTransform: SIGNIN_CASE,
1981
1982
  opacity: 0.8
1982
1983
  };
1983
1984
  var inputStyle = {
@@ -1986,8 +1987,8 @@ var inputStyle = {
1986
1987
  background: V.background,
1987
1988
  color: V.foreground,
1988
1989
  border: `1px solid ${V.border}`,
1989
- borderRadius: 6,
1990
- fontFamily: MONO,
1990
+ borderRadius: signinRadius(6),
1991
+ fontFamily: SIGNIN_FONT,
1991
1992
  // 16px — never lower · suppresses iOS Safari focus auto-zoom.
1992
1993
  fontSize: 16,
1993
1994
  outline: "none",
@@ -1996,11 +1997,11 @@ var inputStyle = {
1996
1997
  function btnBase(disabled) {
1997
1998
  return {
1998
1999
  padding: "0.5rem 0.875rem",
1999
- borderRadius: 6,
2000
- fontFamily: MONO,
2001
- fontSize: 10.5,
2002
- letterSpacing: "0.12em",
2003
- textTransform: "uppercase",
2000
+ borderRadius: SIGNIN_BUTTON_RADIUS,
2001
+ fontFamily: SIGNIN_FONT,
2002
+ fontSize: signinSize(10.5),
2003
+ letterSpacing: signinTracking("0.12em"),
2004
+ textTransform: SIGNIN_CASE,
2004
2005
  cursor: disabled ? "not-allowed" : "pointer",
2005
2006
  opacity: disabled ? 0.5 : 1
2006
2007
  };
@@ -2070,6 +2071,50 @@ function hardNavigate(target) {
2070
2071
  if (typeof window === "undefined") return;
2071
2072
  window.location.assign(target);
2072
2073
  }
2074
+ var SIGNIN_ERRORS = {
2075
+ bad_request: "That doesn't look like a Bitcoin address.",
2076
+ invalid_challenge: "That signature doesn't match this address. Sign with the wallet that holds it.",
2077
+ sig_invalid: "That signature doesn't match this address. Sign with the wallet that holds it.",
2078
+ sig_unsupported_scheme: "We can't check this wallet's signature format yet. Try another wallet, or use email.",
2079
+ expired: "That request timed out. Try again for a fresh one.",
2080
+ not_yet_valid: "That request is not valid yet. Check your device clock, then try again.",
2081
+ nonce_mismatch: "That request didn't match. Try again for a fresh one.",
2082
+ audience_mismatch: "That request didn't match this site. Reload the page and try again.",
2083
+ purpose_mismatch: "That request didn't match. Try again for a fresh one.",
2084
+ malformed: "That signature couldn't be read. Copy it again, whole.",
2085
+ cross_site_blocked: "Your browser blocked this request. Reload the page and try again.",
2086
+ rate_limited: "Too many tries. Wait a minute, then try again.",
2087
+ invalid_email: "That email address doesn't look right.",
2088
+ mailer_unavailable: "We couldn't send the email right now. Try again in a minute.",
2089
+ mailer_error: "We couldn't send the email right now. Try again in a minute.",
2090
+ wrong_code: "That code isn't right. Check the newest email from us.",
2091
+ replay: "That code was already used. Send a new one.",
2092
+ invalid_token: "That code is no longer valid. Send a new one.",
2093
+ email_mismatch: "That code was sent to a different address. Send a new one."
2094
+ };
2095
+ var EMAIL_ERRORS = {
2096
+ expired: "That code expired. Send a new one."
2097
+ };
2098
+ var SUBMIT_ERRORS = {
2099
+ bad_request: "That signature couldn't be read. Copy it again, whole."
2100
+ };
2101
+ var NETWORK_ERROR = "We couldn't reach the server. Check your connection and try again.";
2102
+ function humanSigninError(reason, path, step = "challenge") {
2103
+ if (!/^[a-z0-9_]+$/.test(reason)) return reason;
2104
+ if (path === "wallet" && step === "submit" && SUBMIT_ERRORS[reason]) return SUBMIT_ERRORS[reason];
2105
+ if (path === "email" && EMAIL_ERRORS[reason]) return EMAIL_ERRORS[reason];
2106
+ return SIGNIN_ERRORS[reason] ?? (path === "email" ? "That didn't work. Try again, or send a new code." : "That didn't work. Try again.");
2107
+ }
2108
+ function failureMessage(err, path, step) {
2109
+ if (err instanceof TypeError || err instanceof SyntaxError) return NETWORK_ERROR;
2110
+ return humanSigninError(err instanceof Error ? err.message : "failed", path, step);
2111
+ }
2112
+ var BECH32_ADDRESS = /^(bc1|tb1|bcrt1)[qpzry9x8gf2tvdw0s3jn54khce6mua7l]{8,87}$/i;
2113
+ var BASE58_ADDRESS = /^[123mn][1-9A-HJ-NP-Za-km-z]{25,34}$/;
2114
+ function looksLikeBitcoinAddress(input) {
2115
+ const s = input.trim();
2116
+ return BECH32_ADDRESS.test(s) || BASE58_ADDRESS.test(s);
2117
+ }
2073
2118
  function OcSignIn({
2074
2119
  audience,
2075
2120
  returnTo,
@@ -2085,10 +2130,10 @@ function OcSignIn({
2085
2130
  }) {
2086
2131
  const walletEnabled = paths?.wallet ?? true;
2087
2132
  const emailEnabled = paths?.email ?? true;
2088
- const [resolvedReturn, setResolvedReturn] = React3.useState(
2133
+ const [resolvedReturn, setResolvedReturn] = React4.useState(
2089
2134
  () => familyReturnTarget(returnTo) ?? safeReturnTo(returnTo)
2090
2135
  );
2091
- React3.useEffect(() => {
2136
+ React4.useEffect(() => {
2092
2137
  const fromProp = familyReturnTarget(returnTo);
2093
2138
  if (fromProp) {
2094
2139
  setResolvedReturn(fromProp);
@@ -2099,8 +2144,8 @@ function OcSignIn({
2099
2144
  const fromQuery = familyReturnTarget(q.get("return_to")) ?? familyReturnTarget(q.get("next"));
2100
2145
  setResolvedReturn(fromQuery ?? "/");
2101
2146
  }, [returnTo]);
2102
- const [addMode, setAddMode] = React3.useState(Boolean(addProp));
2103
- React3.useEffect(() => {
2147
+ const [addMode, setAddMode] = React4.useState(Boolean(addProp));
2148
+ React4.useEffect(() => {
2104
2149
  if (typeof window === "undefined") return;
2105
2150
  if (addProp !== void 0) {
2106
2151
  setAddMode(Boolean(addProp));
@@ -2108,19 +2153,19 @@ function OcSignIn({
2108
2153
  }
2109
2154
  setAddMode(new URLSearchParams(window.location.search).get("add") === "1");
2110
2155
  }, [addProp]);
2111
- const [path, setPath] = React3.useState(
2156
+ const [path, setPath] = React4.useState(
2112
2157
  initialPath ?? (providersFirst ? "email" : "wallet")
2113
2158
  );
2114
- const [signedIn, setSignedIn] = React3.useState(null);
2115
- const [linkAlso, setLinkAlso] = React3.useState(false);
2116
- const [oauthError, setOauthError] = React3.useState(false);
2117
- React3.useEffect(() => {
2159
+ const [signedIn, setSignedIn] = React4.useState(null);
2160
+ const [linkAlso, setLinkAlso] = React4.useState(false);
2161
+ const [oauthError, setOauthError] = React4.useState(false);
2162
+ React4.useEffect(() => {
2118
2163
  if (typeof window === "undefined") return;
2119
2164
  if (new URLSearchParams(window.location.search).has("oauth_error")) {
2120
2165
  setOauthError(true);
2121
2166
  }
2122
2167
  }, []);
2123
- const navigate = React3.useCallback(
2168
+ const navigate = React4.useCallback(
2124
2169
  async (account) => {
2125
2170
  if (resolveReturnTo) {
2126
2171
  try {
@@ -2134,7 +2179,7 @@ function OcSignIn({
2134
2179
  },
2135
2180
  [resolveReturnTo, resolvedReturn]
2136
2181
  );
2137
- const handleSuccess = React3.useCallback(
2182
+ const handleSuccess = React4.useCallback(
2138
2183
  async (account, token, via) => {
2139
2184
  const proceed = () => {
2140
2185
  clearTabSession();
@@ -2176,8 +2221,8 @@ function OcSignIn({
2176
2221
  padding: "1rem",
2177
2222
  border: "1px solid var(--border, #27272a)",
2178
2223
  color: "var(--foreground, #fafafa)",
2179
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2180
- fontSize: 12
2224
+ fontFamily: SIGNIN_FONT,
2225
+ fontSize: signinSize(12)
2181
2226
  },
2182
2227
  children: [
2183
2228
  "no signin paths enabled \u2014 set ",
@@ -2215,12 +2260,12 @@ function OcSignIn({
2215
2260
  marginBottom: 14,
2216
2261
  padding: "0.6rem 0.75rem",
2217
2262
  border: "1px solid var(--destructive, #ef4444)",
2218
- borderRadius: 6,
2263
+ borderRadius: signinRadius(6),
2219
2264
  color: "var(--destructive, #ef4444)",
2220
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2221
- fontSize: 11
2265
+ fontFamily: SIGNIN_FONT,
2266
+ fontSize: signinSize(11)
2222
2267
  },
2223
- children: "That sign-in didn't complete. Please try again."
2268
+ children: "That sign-in didn't finish. Try again, or use another way below."
2224
2269
  }
2225
2270
  ),
2226
2271
  addMode && /* @__PURE__ */ jsxs(
@@ -2232,11 +2277,11 @@ function OcSignIn({
2232
2277
  padding: "0.6rem 0.75rem",
2233
2278
  border: "1px solid var(--primary, #f97316)",
2234
2279
  borderLeftWidth: 3,
2235
- borderRadius: 4,
2280
+ borderRadius: signinRadius(4),
2236
2281
  background: "color-mix(in srgb, var(--primary, #f97316) 8%, transparent)",
2237
2282
  color: "var(--foreground, #fafafa)",
2238
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2239
- fontSize: 11,
2283
+ fontFamily: SIGNIN_FONT,
2284
+ fontSize: signinSize(11),
2240
2285
  lineHeight: 1.6
2241
2286
  },
2242
2287
  children: [
@@ -2245,9 +2290,9 @@ function OcSignIn({
2245
2290
  {
2246
2291
  style: {
2247
2292
  color: "var(--primary, #f97316)",
2248
- letterSpacing: "0.12em",
2249
- textTransform: "uppercase",
2250
- fontSize: 10,
2293
+ letterSpacing: signinTracking("0.12em"),
2294
+ textTransform: SIGNIN_CASE,
2295
+ fontSize: signinSize(10),
2251
2296
  display: "block",
2252
2297
  marginBottom: 3
2253
2298
  },
@@ -2285,7 +2330,7 @@ function OcSignIn({
2285
2330
  {
2286
2331
  active: path === "email",
2287
2332
  onClick: () => setPath("email"),
2288
- children: "email + otp"
2333
+ children: "email"
2289
2334
  }
2290
2335
  ),
2291
2336
  /* @__PURE__ */ jsx(
@@ -2293,7 +2338,7 @@ function OcSignIn({
2293
2338
  {
2294
2339
  active: path === "wallet",
2295
2340
  onClick: () => setPath("wallet"),
2296
- children: "bitcoin \xB7 self-custody"
2341
+ children: "bitcoin wallet"
2297
2342
  }
2298
2343
  )
2299
2344
  ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
@@ -2310,7 +2355,7 @@ function OcSignIn({
2310
2355
  {
2311
2356
  active: path === "email",
2312
2357
  onClick: () => setPath("email"),
2313
- children: "email + otp"
2358
+ children: "email"
2314
2359
  }
2315
2360
  )
2316
2361
  ] })
@@ -2343,7 +2388,7 @@ function OcSignIn({
2343
2388
  type: "checkbox",
2344
2389
  checked: linkAlso,
2345
2390
  onChange: (e) => setLinkAlso(e.target.checked),
2346
- style: { marginTop: 2, accentColor: "var(--primary, #f97316)" }
2391
+ style: { margin: 0, accentColor: "var(--primary, #f97316)" }
2347
2392
  }
2348
2393
  ),
2349
2394
  /* @__PURE__ */ jsxs("span", { children: [
@@ -2351,7 +2396,7 @@ function OcSignIn({
2351
2396
  " ",
2352
2397
  activeMethod === "email" ? "a Bitcoin wallet" : "an email",
2353
2398
  " ",
2354
- /* @__PURE__ */ jsx("span", { style: { opacity: 0.65 }, children: "\u2014 optional, one more signature." })
2399
+ /* @__PURE__ */ jsx("span", { children: "\u2014 optional, one more signature." })
2355
2400
  ] })
2356
2401
  ] })
2357
2402
  ] });
@@ -2370,15 +2415,16 @@ function SigninTab({
2370
2415
  onClick,
2371
2416
  "data-oc-signin-tab": active ? "active" : "inactive",
2372
2417
  style: {
2418
+ minHeight: 44,
2373
2419
  padding: "0.6rem 0.875rem",
2374
2420
  background: "transparent",
2375
2421
  border: "none",
2376
2422
  borderBottom: active ? "2px solid var(--primary, #f97316)" : "2px solid transparent",
2377
2423
  color: active ? "var(--foreground, #fafafa)" : "var(--muted-foreground, #a1a1aa)",
2378
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2379
- fontSize: 11,
2380
- letterSpacing: "0.12em",
2381
- textTransform: "uppercase",
2424
+ fontFamily: SIGNIN_FONT,
2425
+ fontSize: signinSize(11),
2426
+ letterSpacing: signinTracking("0.12em"),
2427
+ textTransform: SIGNIN_CASE,
2382
2428
  cursor: "pointer",
2383
2429
  marginBottom: -1
2384
2430
  },
@@ -2409,20 +2455,21 @@ function ProviderSignIn({
2409
2455
  add,
2410
2456
  first = false
2411
2457
  }) {
2412
- const [providers, setProviders] = React3.useState([]);
2413
- const [origin, setOrigin] = React3.useState("");
2414
- React3.useEffect(() => {
2458
+ const [providers, setProviders] = React4.useState(null);
2459
+ const [origin, setOrigin] = React4.useState("");
2460
+ React4.useEffect(() => {
2415
2461
  setOrigin(window.location.origin);
2416
2462
  let cancelled = false;
2417
2463
  fetch(`${authOrigin}/api/auth/providers`, { credentials: "include" }).then((r) => r.ok ? r.json() : null).then((body) => {
2418
- if (!cancelled && body?.providers) setProviders(body.providers);
2464
+ if (!cancelled) setProviders(body?.providers ?? []);
2419
2465
  }).catch(() => {
2466
+ if (!cancelled) setProviders([]);
2420
2467
  });
2421
2468
  return () => {
2422
2469
  cancelled = true;
2423
2470
  };
2424
2471
  }, [authOrigin]);
2425
- if (providers.length === 0) return null;
2472
+ if (providers !== null && providers.length === 0) return null;
2426
2473
  const providerReturnTo = returnTo.startsWith("/") ? origin ? `${origin}${returnTo}` : returnTo : returnTo;
2427
2474
  const line = { flex: 1, height: 1, background: "var(--border, #27272a)" };
2428
2475
  const BLOCK_GAP = 16;
@@ -2439,10 +2486,10 @@ function ProviderSignIn({
2439
2486
  // marginTop owns the gap above, so the divider only pads below.
2440
2487
  margin: first ? `${BLOCK_GAP}px 0` : `0 0 ${BLOCK_GAP}px`,
2441
2488
  color: "var(--muted-foreground, #a1a1aa)",
2442
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2443
- fontSize: 10,
2444
- letterSpacing: "0.16em",
2445
- textTransform: "uppercase"
2489
+ fontFamily: SIGNIN_FONT,
2490
+ fontSize: signinSize(10),
2491
+ letterSpacing: signinTracking("0.16em"),
2492
+ textTransform: SIGNIN_CASE
2446
2493
  },
2447
2494
  children: [
2448
2495
  /* @__PURE__ */ jsx("span", { style: line }),
@@ -2451,6 +2498,31 @@ function ProviderSignIn({
2451
2498
  ]
2452
2499
  }
2453
2500
  );
2501
+ const buttonStyle = (i) => ({
2502
+ display: "flex",
2503
+ alignItems: "center",
2504
+ justifyContent: "center",
2505
+ gap: 10,
2506
+ boxSizing: "border-box",
2507
+ width: "100%",
2508
+ minHeight: 44,
2509
+ marginTop: i === 0 ? 0 : BUTTON_GAP,
2510
+ padding: "0.6rem 0.875rem",
2511
+ border: "1px solid var(--border, #27272a)",
2512
+ borderRadius: SIGNIN_BUTTON_RADIUS,
2513
+ background: "transparent",
2514
+ color: "var(--muted-foreground, #a1a1aa)",
2515
+ fontFamily: SIGNIN_FONT,
2516
+ fontSize: signinSize(12),
2517
+ textDecoration: "none"
2518
+ });
2519
+ if (providers === null) {
2520
+ if (!first) return null;
2521
+ return /* @__PURE__ */ jsxs("div", { "data-oc-signin-providers": "", "aria-hidden": "true", children: [
2522
+ [0, 1].map((i) => /* @__PURE__ */ jsx("div", { style: { ...buttonStyle(i), visibility: "hidden" }, children: "\xA0" }, i)),
2523
+ divider
2524
+ ] });
2525
+ }
2454
2526
  const buttons = providers.map((p, i) => /* @__PURE__ */ jsxs(
2455
2527
  "a",
2456
2528
  {
@@ -2458,23 +2530,7 @@ function ProviderSignIn({
2458
2530
  providerReturnTo
2459
2531
  )}${add ? "&add=1" : ""}`,
2460
2532
  "data-oc-signin-provider": p.id,
2461
- style: {
2462
- display: "flex",
2463
- alignItems: "center",
2464
- justifyContent: "center",
2465
- gap: 10,
2466
- boxSizing: "border-box",
2467
- width: "100%",
2468
- marginTop: i === 0 ? 0 : BUTTON_GAP,
2469
- padding: "0.6rem 0.875rem",
2470
- border: "1px solid var(--border, #27272a)",
2471
- borderRadius: 6,
2472
- background: "transparent",
2473
- color: "var(--muted-foreground, #a1a1aa)",
2474
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2475
- fontSize: 12,
2476
- textDecoration: "none"
2477
- },
2533
+ style: buttonStyle(i),
2478
2534
  children: [
2479
2535
  /* @__PURE__ */ jsx(ProviderIcon, { id: p.id }),
2480
2536
  /* @__PURE__ */ jsx("span", { children: p.label })
@@ -2497,82 +2553,214 @@ function ProviderSignIn({
2497
2553
  }
2498
2554
  );
2499
2555
  }
2556
+ async function loadAdapter() {
2557
+ return await import('@orangecheck/wallet-adapter');
2558
+ }
2559
+ async function detectBrowserWallets() {
2560
+ return (await loadAdapter()).detectWallets().filter((w) => w.detected && w.id !== "manual").map((w) => ({ id: w.id, name: w.name }));
2561
+ }
2500
2562
  function WalletFlow({ authOrigin, audience, add, onSuccess }) {
2501
- const [address, setAddress] = React3.useState("");
2502
- const [error, setError] = React3.useState(null);
2503
- const [submitting, setSubmitting] = React3.useState(false);
2504
- const [stage, setStage] = React3.useState("enter");
2563
+ const uid = React4.useId();
2564
+ const ids = {
2565
+ address: `${uid}-address`,
2566
+ wallet: `${uid}-wallet`,
2567
+ message: `${uid}-message`,
2568
+ signature: `${uid}-signature`
2569
+ };
2570
+ const [address, setAddress] = React4.useState("");
2571
+ const [error, setError] = React4.useState(null);
2572
+ const [submitting, setSubmitting] = React4.useState(false);
2573
+ const [wallets, setWallets] = React4.useState(null);
2574
+ const [walletId, setWalletId] = React4.useState(null);
2575
+ const [manual, setManual] = React4.useState(null);
2576
+ const [pasted, setPasted] = React4.useState("");
2577
+ const [copied, setCopied] = React4.useState("idle");
2578
+ React4.useEffect(() => {
2579
+ let cancelled = false;
2580
+ detectBrowserWallets().then((found) => {
2581
+ if (cancelled) return;
2582
+ setWallets(found);
2583
+ setWalletId(found[0]?.id ?? null);
2584
+ }).catch(() => {
2585
+ if (!cancelled) setWallets([]);
2586
+ });
2587
+ return () => {
2588
+ cancelled = true;
2589
+ };
2590
+ }, []);
2591
+ async function fetchChallenge(addr) {
2592
+ const res = await fetch(
2593
+ `${authOrigin}/api/challenge?addr=${encodeURIComponent(addr)}&audience=${encodeURIComponent(audience)}&purpose=login`
2594
+ );
2595
+ const json = await res.json();
2596
+ if (!res.ok || !json.message || !json.nonce) {
2597
+ throw new Error(json.error ?? "challenge_failed");
2598
+ }
2599
+ return { message: json.message, nonce: json.nonce };
2600
+ }
2601
+ async function submitSignature(message, nonce, signature, scheme) {
2602
+ const res = await fetch(`${authOrigin}/api/auth/signin`, {
2603
+ method: "POST",
2604
+ credentials: "include",
2605
+ headers: { "Content-Type": "application/json" },
2606
+ body: JSON.stringify({
2607
+ message,
2608
+ signature,
2609
+ ...scheme ? { scheme } : {},
2610
+ expectedNonce: nonce,
2611
+ expectedAudience: audience,
2612
+ expectedPurpose: "login",
2613
+ // Multi-account add-mode · the auth host preserves the current
2614
+ // roster_id when set, instead of minting a fresh one.
2615
+ ...add ? { add: true } : {}
2616
+ })
2617
+ });
2618
+ const json = await res.json();
2619
+ if (!res.ok || !("ok" in json) || !json.ok || !json.account) {
2620
+ throw new Error(
2621
+ "reason" in json && json.reason || "error" in json && json.error || "verify_failed"
2622
+ );
2623
+ }
2624
+ onSuccess(json.account, json.token);
2625
+ }
2505
2626
  async function signIn(e) {
2506
2627
  e.preventDefault();
2507
2628
  const addr = address.trim();
2508
2629
  if (!addr) {
2509
- setError("paste a Bitcoin address");
2630
+ setError("Paste your Bitcoin address.");
2631
+ return;
2632
+ }
2633
+ if (!looksLikeBitcoinAddress(addr)) {
2634
+ setError(humanSigninError("bad_request", "wallet"));
2510
2635
  return;
2511
2636
  }
2512
2637
  setError(null);
2513
2638
  setSubmitting(true);
2514
- setStage("signing");
2639
+ let step = "challenge";
2515
2640
  try {
2516
- const challengeRes = await fetch(
2517
- `${authOrigin}/api/challenge?addr=${encodeURIComponent(addr)}&audience=${encodeURIComponent(audience)}&purpose=login`
2518
- );
2519
- const challenge = await challengeRes.json();
2520
- if (!challengeRes.ok || !challenge.message || !challenge.nonce) {
2521
- throw new Error(challenge.error ?? "challenge_failed");
2641
+ let id = walletId;
2642
+ if (wallets === null) {
2643
+ const found = await detectBrowserWallets().catch(() => []);
2644
+ setWallets(found);
2645
+ id = found[0]?.id ?? null;
2646
+ setWalletId(id);
2522
2647
  }
2523
- let adapter;
2524
- try {
2525
- adapter = await import('@orangecheck/wallet-adapter');
2526
- } catch {
2527
- throw new Error(
2528
- "could not load wallet support \xB7 refresh to try again"
2529
- );
2530
- }
2531
- const wallets = adapter.detectWallets().filter((w) => w.detected && w.id !== "manual");
2532
- if (wallets.length === 0) {
2533
- throw new Error("no BIP-322 wallet extension detected \xB7 install one and refresh");
2648
+ const challenge = await fetchChallenge(addr);
2649
+ if (!id) {
2650
+ setManual(challenge);
2651
+ setSubmitting(false);
2652
+ return;
2534
2653
  }
2535
- const wallet = wallets[0];
2536
- const signer = adapter.getSigner(wallet.id, { address: addr });
2654
+ const signer = (await loadAdapter()).getSigner(id, { address: addr });
2537
2655
  const signature = await signer(challenge.message);
2538
- const res = await fetch(`${authOrigin}/api/auth/signin`, {
2539
- method: "POST",
2540
- credentials: "include",
2541
- headers: { "Content-Type": "application/json" },
2542
- body: JSON.stringify({
2543
- message: challenge.message,
2544
- signature,
2545
- scheme: "bip322",
2546
- expectedNonce: challenge.nonce,
2547
- expectedAudience: audience,
2548
- expectedPurpose: "login",
2549
- // Multi-account add-mode · the auth host preserves
2550
- // the current roster_id when set, instead of minting
2551
- // a fresh one. Hosts that haven't deployed the
2552
- // multi-account migration silently ignore the field.
2553
- ...add ? { add: true } : {}
2554
- })
2555
- });
2556
- const json = await res.json();
2557
- if (!res.ok || !("ok" in json) || !json.ok || !json.account) {
2558
- throw new Error(
2559
- "reason" in json && json.reason || "error" in json && json.error || `verify_failed_${res.status}`
2560
- );
2561
- }
2562
- onSuccess(json.account, json.token);
2656
+ step = "submit";
2657
+ await submitSignature(challenge.message, challenge.nonce, signature, "bip322");
2658
+ } catch (err) {
2659
+ setError(failureMessage(err, "wallet", step));
2660
+ setSubmitting(false);
2661
+ }
2662
+ }
2663
+ async function signInManual(e) {
2664
+ e.preventDefault();
2665
+ if (!manual) return;
2666
+ const sig = pasted.trim();
2667
+ if (!sig) {
2668
+ setError("Paste the signature your wallet gave you.");
2669
+ return;
2670
+ }
2671
+ setError(null);
2672
+ setSubmitting(true);
2673
+ try {
2674
+ await submitSignature(manual.message, manual.nonce, sig, void 0);
2563
2675
  } catch (err) {
2564
- const msg = err instanceof Error ? err.message : "sign-in failed";
2565
- setError(msg);
2676
+ setError(failureMessage(err, "wallet", "submit"));
2566
2677
  setSubmitting(false);
2567
- setStage("enter");
2568
2678
  }
2569
2679
  }
2680
+ async function copyMessage() {
2681
+ if (!manual) return;
2682
+ try {
2683
+ await navigator.clipboard.writeText(manual.message);
2684
+ setCopied("copied");
2685
+ } catch {
2686
+ setCopied("failed");
2687
+ }
2688
+ }
2689
+ if (manual) {
2690
+ return /* @__PURE__ */ jsxs("form", { onSubmit: signInManual, "data-oc-signin-wallet-manual": "", children: [
2691
+ /* @__PURE__ */ jsxs(FlowHeader, { children: [
2692
+ "In your wallet, use \u201Csign message\u201D (BIP-322) with ",
2693
+ address.trim(),
2694
+ ", sign the text below, and paste the signature back here. Nothing is spent."
2695
+ ] }),
2696
+ /* @__PURE__ */ jsx(Label, { htmlFor: ids.message, children: "message to sign" }),
2697
+ /* @__PURE__ */ jsx(
2698
+ "textarea",
2699
+ {
2700
+ id: ids.message,
2701
+ readOnly: true,
2702
+ value: manual.message,
2703
+ rows: 5,
2704
+ onFocus: (e) => e.currentTarget.select(),
2705
+ style: { ...inputStyle2, resize: "vertical" }
2706
+ }
2707
+ ),
2708
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: () => void copyMessage(), style: linkButtonStyle, children: copied === "copied" ? "copied" : "copy message" }),
2709
+ copied === "failed" && /* @__PURE__ */ jsx(Hint, { children: "Couldn't copy here. Select the text above and copy it." }),
2710
+ /* @__PURE__ */ jsxs("div", { style: { marginTop: 14 }, children: [
2711
+ /* @__PURE__ */ jsx(Label, { htmlFor: ids.signature, children: "signature" }),
2712
+ /* @__PURE__ */ jsx(
2713
+ "textarea",
2714
+ {
2715
+ id: ids.signature,
2716
+ value: pasted,
2717
+ onChange: (e) => {
2718
+ setPasted(e.target.value);
2719
+ if (error) setError(null);
2720
+ },
2721
+ rows: 3,
2722
+ placeholder: "paste the signature",
2723
+ spellCheck: false,
2724
+ autoFocus: true,
2725
+ "aria-invalid": error ? true : void 0,
2726
+ style: { ...inputStyle2, resize: "vertical" }
2727
+ }
2728
+ )
2729
+ ] }),
2730
+ error && /* @__PURE__ */ jsx(ErrorLine2, { children: error }),
2731
+ /* @__PURE__ */ jsx(
2732
+ "button",
2733
+ {
2734
+ type: "submit",
2735
+ disabled: submitting || !pasted.trim(),
2736
+ style: submitStyle(submitting || !pasted.trim()),
2737
+ children: submitting ? "checking\u2026" : "sign me in \u2192"
2738
+ }
2739
+ ),
2740
+ /* @__PURE__ */ jsx(
2741
+ "button",
2742
+ {
2743
+ type: "button",
2744
+ onClick: () => {
2745
+ setManual(null);
2746
+ setPasted("");
2747
+ setError(null);
2748
+ setCopied("idle");
2749
+ },
2750
+ style: linkButtonStyle,
2751
+ children: "use a different address"
2752
+ }
2753
+ )
2754
+ ] });
2755
+ }
2756
+ const noBrowserWallet = wallets !== null && wallets.length === 0;
2570
2757
  return /* @__PURE__ */ jsxs("form", { onSubmit: signIn, "data-oc-signin-wallet": "", children: [
2571
- /* @__PURE__ */ jsx(FlowHeader, { label: "\xA7 bitcoin wallet", children: "Sign in with any BIP-322-capable Bitcoin wallet (Sparrow, Xverse, Leather, UniSat, Alby, OKX, Phantom). Paste your address, click sign \u2014 your wallet extension prompts for a one-time signature on a short challenge. The address becomes your OC identity. Private key material never leaves your wallet." }),
2572
- /* @__PURE__ */ jsx(Label, { children: "bitcoin address" }),
2758
+ /* @__PURE__ */ jsx(FlowHeader, { children: "Your wallet signs a short message to prove you hold the address. Nothing is spent and your key never leaves the wallet. Works with browser wallets, or any wallet that can sign a message, like Sparrow." }),
2759
+ /* @__PURE__ */ jsx(Label, { htmlFor: ids.address, children: "bitcoin address" }),
2573
2760
  /* @__PURE__ */ jsx(
2574
2761
  "input",
2575
2762
  {
2763
+ id: ids.address,
2576
2764
  type: "text",
2577
2765
  value: address,
2578
2766
  onChange: (e) => {
@@ -2588,6 +2776,19 @@ function WalletFlow({ authOrigin, audience, add, onSuccess }) {
2588
2776
  style: inputStyle2
2589
2777
  }
2590
2778
  ),
2779
+ wallets && wallets.length > 1 && /* @__PURE__ */ jsxs("div", { style: { marginTop: 12 }, children: [
2780
+ /* @__PURE__ */ jsx(Label, { htmlFor: ids.wallet, children: "sign with" }),
2781
+ /* @__PURE__ */ jsx(
2782
+ "select",
2783
+ {
2784
+ id: ids.wallet,
2785
+ value: walletId ?? "",
2786
+ onChange: (e) => setWalletId(e.target.value),
2787
+ style: inputStyle2,
2788
+ children: wallets.map((w) => /* @__PURE__ */ jsx("option", { value: w.id, children: w.name }, w.id))
2789
+ }
2790
+ )
2791
+ ] }),
2591
2792
  error && /* @__PURE__ */ jsx(ErrorLine2, { children: error }),
2592
2793
  /* @__PURE__ */ jsx(
2593
2794
  "button",
@@ -2595,54 +2796,71 @@ function WalletFlow({ authOrigin, audience, add, onSuccess }) {
2595
2796
  type: "submit",
2596
2797
  disabled: submitting || !address.trim(),
2597
2798
  style: submitStyle(submitting || !address.trim()),
2598
- children: stage === "signing" && submitting ? "waiting for wallet\u2026" : "sign challenge \xB7 sign me in \u2192"
2799
+ children: submitting ? "waiting for wallet\u2026" : noBrowserWallet ? "get the message to sign \u2192" : "sign in with wallet \u2192"
2599
2800
  }
2600
2801
  ),
2601
- /* @__PURE__ */ jsx(Hint, { children: "Detection picks the first installed BIP-322-capable extension. Address is the one your wallet will sign for." })
2802
+ noBrowserWallet && /* @__PURE__ */ jsx(Hint, { children: "No browser wallet found here. You can sign with any wallet and paste." })
2602
2803
  ] });
2603
2804
  }
2604
2805
  function EmailFlow({ authOrigin, add, onSuccess }) {
2605
- const [stage, setStage] = React3.useState("enter");
2606
- const [email, setEmail] = React3.useState("");
2607
- const [emailError, setEmailError] = React3.useState(null);
2608
- const [code, setCode] = React3.useState("");
2609
- const [codeError, setCodeError] = React3.useState(null);
2610
- const [token, setToken] = React3.useState(null);
2611
- const [submitting, setSubmitting] = React3.useState(false);
2612
- async function start(e) {
2613
- e.preventDefault();
2614
- const trimmed = email.trim().toLowerCase();
2615
- if (!trimmed || !/.+@.+\..+/.test(trimmed)) {
2616
- setEmailError("enter a valid email");
2617
- return;
2618
- }
2619
- setEmailError(null);
2620
- setSubmitting(true);
2806
+ const uid = React4.useId();
2807
+ const [stage, setStage] = React4.useState("enter");
2808
+ const [email, setEmail] = React4.useState("");
2809
+ const [emailError, setEmailError] = React4.useState(null);
2810
+ const [code, setCode] = React4.useState("");
2811
+ const [codeError, setCodeError] = React4.useState(null);
2812
+ const [token, setToken] = React4.useState(null);
2813
+ const [submitting, setSubmitting] = React4.useState(false);
2814
+ const [resent, setResent] = React4.useState(false);
2815
+ async function sendCode(address) {
2621
2816
  try {
2622
2817
  const res = await fetch(`${authOrigin}/api/auth/email-otp/start`, {
2623
2818
  method: "POST",
2624
2819
  credentials: "include",
2625
2820
  headers: { "Content-Type": "application/json" },
2626
- body: JSON.stringify({ email: trimmed })
2821
+ body: JSON.stringify({ email: address })
2627
2822
  });
2628
2823
  const json = await res.json();
2629
- if (!res.ok || !json.token) {
2630
- throw new Error(json.reason ?? `start failed (${res.status})`);
2631
- }
2824
+ if (!res.ok || !json.token) return humanSigninError(json.reason ?? "server_error", "email");
2632
2825
  setToken(json.token);
2633
- setEmail(trimmed);
2634
- setStage("code");
2635
- } catch (err) {
2636
- setEmailError(err instanceof Error ? err.message : "failed to send code");
2637
- } finally {
2638
- setSubmitting(false);
2826
+ return null;
2827
+ } catch {
2828
+ return NETWORK_ERROR;
2829
+ }
2830
+ }
2831
+ async function start(e) {
2832
+ e.preventDefault();
2833
+ const trimmed = email.trim().toLowerCase();
2834
+ if (!trimmed || !/.+@.+\..+/.test(trimmed)) {
2835
+ setEmailError(humanSigninError("invalid_email", "email"));
2836
+ return;
2639
2837
  }
2838
+ setEmailError(null);
2839
+ setSubmitting(true);
2840
+ const err = await sendCode(trimmed);
2841
+ setSubmitting(false);
2842
+ if (err) {
2843
+ setEmailError(err);
2844
+ return;
2845
+ }
2846
+ setEmail(trimmed);
2847
+ setStage("code");
2848
+ }
2849
+ async function resend() {
2850
+ setCodeError(null);
2851
+ setResent(false);
2852
+ setSubmitting(true);
2853
+ const err = await sendCode(email);
2854
+ setSubmitting(false);
2855
+ setCode("");
2856
+ if (err) setCodeError(err);
2857
+ else setResent(true);
2640
2858
  }
2641
2859
  async function verify(e) {
2642
2860
  e.preventDefault();
2643
2861
  if (!token) return;
2644
2862
  if (code.length !== 6) {
2645
- setCodeError("6 digits");
2863
+ setCodeError("The code is 6 digits.");
2646
2864
  return;
2647
2865
  }
2648
2866
  setCodeError(null);
@@ -2663,23 +2881,24 @@ function EmailFlow({ authOrigin, add, onSuccess }) {
2663
2881
  const json = await res.json();
2664
2882
  if (!res.ok || !("ok" in json) || !json.ok || !json.account) {
2665
2883
  throw new Error(
2666
- "reason" in json && json.reason || "error" in json && json.error || `verify failed (${res.status})`
2884
+ "reason" in json && json.reason || "error" in json && json.error || "server_error"
2667
2885
  );
2668
2886
  }
2669
2887
  onSuccess(json.account, json.token);
2670
2888
  } catch (err) {
2671
- setCodeError(err instanceof Error ? err.message : "verify failed");
2889
+ setCodeError(failureMessage(err, "email", "submit"));
2672
2890
  } finally {
2673
2891
  setSubmitting(false);
2674
2892
  }
2675
2893
  }
2676
2894
  if (stage === "enter") {
2677
2895
  return /* @__PURE__ */ jsxs("form", { onSubmit: start, "data-oc-signin-email": "", children: [
2678
- /* @__PURE__ */ jsx(FlowHeader, { label: "\xA7 email + otp", children: "We email you a 6-digit code. No password, and no wallet needed to start. Link a Bitcoin address any time \u2014 that is the identity you hold yourself." }),
2679
- /* @__PURE__ */ jsx(Label, { children: "email" }),
2896
+ /* @__PURE__ */ jsx(FlowHeader, { children: "We email you a 6-digit code. No password, and no wallet needed to start. Link a Bitcoin address any time \u2014 that is the identity you hold yourself." }),
2897
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${uid}-email`, children: "email" }),
2680
2898
  /* @__PURE__ */ jsx(
2681
2899
  "input",
2682
2900
  {
2901
+ id: `${uid}-email`,
2683
2902
  type: "email",
2684
2903
  autoComplete: "email",
2685
2904
  value: email,
@@ -2694,20 +2913,20 @@ function EmailFlow({ authOrigin, add, onSuccess }) {
2694
2913
  }
2695
2914
  ),
2696
2915
  emailError && /* @__PURE__ */ jsx(ErrorLine2, { children: emailError }),
2697
- /* @__PURE__ */ jsx("button", { type: "submit", disabled: submitting, style: submitStyle(submitting), children: submitting ? "sending\u2026" : "send one-time code \u2192" }),
2698
- /* @__PURE__ */ jsx(Hint, { children: "Codes come from the auth host, expire in 10 minutes, and work once." })
2916
+ /* @__PURE__ */ jsx("button", { type: "submit", disabled: submitting, style: submitStyle(submitting), children: submitting ? "sending\u2026" : "send one-time code \u2192" })
2699
2917
  ] });
2700
2918
  }
2701
2919
  return /* @__PURE__ */ jsxs("form", { onSubmit: verify, "data-oc-signin-email-verify": "", children: [
2702
- /* @__PURE__ */ jsxs(FlowHeader, { label: "\xA7 enter the code", children: [
2703
- "Sent a 6-digit code to ",
2920
+ /* @__PURE__ */ jsxs(FlowHeader, { children: [
2921
+ resent ? "Sent a new code to " : "Sent a 6-digit code to ",
2704
2922
  /* @__PURE__ */ jsx("span", { style: { color: "var(--foreground, #fafafa)" }, children: email }),
2705
- ". Code expires in 10 minutes."
2923
+ ". It expires in 10 minutes."
2706
2924
  ] }),
2707
- /* @__PURE__ */ jsx(Label, { children: "one-time code" }),
2925
+ /* @__PURE__ */ jsx(Label, { htmlFor: `${uid}-code`, children: "one-time code" }),
2708
2926
  /* @__PURE__ */ jsx(
2709
2927
  "input",
2710
2928
  {
2929
+ id: `${uid}-code`,
2711
2930
  type: "text",
2712
2931
  inputMode: "numeric",
2713
2932
  pattern: "\\d{6}",
@@ -2735,31 +2954,32 @@ function EmailFlow({ authOrigin, add, onSuccess }) {
2735
2954
  children: submitting ? "verifying\u2026" : "verify \xB7 sign me in \u2192"
2736
2955
  }
2737
2956
  ),
2738
- /* @__PURE__ */ jsx(
2739
- "button",
2740
- {
2741
- type: "button",
2742
- onClick: () => {
2743
- setStage("enter");
2744
- setCode("");
2745
- setToken(null);
2746
- },
2747
- style: {
2748
- marginTop: 12,
2749
- background: "transparent",
2750
- border: "none",
2751
- color: "var(--muted-foreground, #a1a1aa)",
2752
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2753
- fontSize: 11,
2754
- letterSpacing: "0.12em",
2755
- textTransform: "uppercase",
2756
- cursor: "pointer",
2757
- padding: 0
2758
- },
2759
- children: "use a different email"
2760
- }
2761
- ),
2762
- /* @__PURE__ */ jsx(Hint, { children: "On verify, oc_session is set on Domain=.ochk.io family-wide." })
2957
+ /* @__PURE__ */ jsxs("div", { children: [
2958
+ /* @__PURE__ */ jsx(
2959
+ "button",
2960
+ {
2961
+ type: "button",
2962
+ onClick: () => void resend(),
2963
+ disabled: submitting,
2964
+ style: linkButtonStyle,
2965
+ children: "send a new code"
2966
+ }
2967
+ ),
2968
+ /* @__PURE__ */ jsx(
2969
+ "button",
2970
+ {
2971
+ type: "button",
2972
+ onClick: () => {
2973
+ setStage("enter");
2974
+ setCode("");
2975
+ setToken(null);
2976
+ setResent(false);
2977
+ },
2978
+ style: linkButtonStyle,
2979
+ children: "use a different email"
2980
+ }
2981
+ )
2982
+ ] })
2763
2983
  ] });
2764
2984
  }
2765
2985
  function FlowHeader({
@@ -2767,15 +2987,15 @@ function FlowHeader({
2767
2987
  children
2768
2988
  }) {
2769
2989
  return /* @__PURE__ */ jsxs("div", { style: { marginBottom: 16 }, children: [
2770
- /* @__PURE__ */ jsx(
2990
+ label && /* @__PURE__ */ jsx(
2771
2991
  "div",
2772
2992
  {
2773
2993
  style: {
2774
2994
  color: "var(--primary, #f97316)",
2775
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2776
- fontSize: 10,
2777
- letterSpacing: "0.18em",
2778
- textTransform: "uppercase",
2995
+ fontFamily: SIGNIN_FONT,
2996
+ fontSize: signinSize(10),
2997
+ letterSpacing: signinTracking("0.18em"),
2998
+ textTransform: SIGNIN_CASE,
2779
2999
  marginBottom: 6
2780
3000
  },
2781
3001
  children: label
@@ -2795,17 +3015,37 @@ function FlowHeader({
2795
3015
  )
2796
3016
  ] });
2797
3017
  }
2798
- function Label({ children }) {
3018
+ var linkButtonStyle = {
3019
+ display: "inline-flex",
3020
+ alignItems: "center",
3021
+ minHeight: 44,
3022
+ marginTop: 4,
3023
+ marginRight: 16,
3024
+ background: "transparent",
3025
+ border: "none",
3026
+ color: "var(--muted-foreground, #a1a1aa)",
3027
+ fontFamily: SIGNIN_FONT,
3028
+ fontSize: signinSize(11),
3029
+ letterSpacing: signinTracking("0.12em"),
3030
+ textTransform: SIGNIN_CASE,
3031
+ cursor: "pointer",
3032
+ padding: 0
3033
+ };
3034
+ function Label({
3035
+ htmlFor,
3036
+ children
3037
+ }) {
2799
3038
  return /* @__PURE__ */ jsx(
2800
3039
  "label",
2801
3040
  {
3041
+ htmlFor,
2802
3042
  style: {
2803
3043
  display: "block",
2804
3044
  color: "var(--foreground, #fafafa)",
2805
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2806
- fontSize: 11,
2807
- letterSpacing: "0.12em",
2808
- textTransform: "uppercase",
3045
+ fontFamily: SIGNIN_FONT,
3046
+ fontSize: signinSize(11),
3047
+ letterSpacing: signinTracking("0.12em"),
3048
+ textTransform: SIGNIN_CASE,
2809
3049
  marginBottom: 6
2810
3050
  },
2811
3051
  children
@@ -2818,8 +3058,8 @@ var inputStyle2 = {
2818
3058
  background: "var(--background, #0a0a0a)",
2819
3059
  color: "var(--foreground, #fafafa)",
2820
3060
  border: "1px solid var(--input, #27272a)",
2821
- borderRadius: 6,
2822
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
3061
+ borderRadius: signinRadius(6),
3062
+ fontFamily: SIGNIN_FONT,
2823
3063
  // 16px — never below it. A sub-16px font-size makes iOS Safari
2824
3064
  // auto-zoom the viewport on focus; the family rule is >=16px on
2825
3065
  // every form field. Desktop reads fine at 16 too.
@@ -2828,14 +3068,16 @@ var inputStyle2 = {
2828
3068
  };
2829
3069
  var linkAlsoStyle = {
2830
3070
  display: "flex",
2831
- alignItems: "flex-start",
3071
+ alignItems: "center",
3072
+ // The whole row is the checkbox's hit area; the 13px box alone was not.
3073
+ minHeight: 44,
2832
3074
  gap: 8,
2833
3075
  marginTop: 16,
2834
3076
  paddingTop: 14,
2835
3077
  borderTop: "1px solid var(--border, #27272a)",
2836
3078
  color: "var(--muted-foreground, #a1a1aa)",
2837
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2838
- fontSize: 12,
3079
+ fontFamily: SIGNIN_FONT,
3080
+ fontSize: signinSize(12),
2839
3081
  lineHeight: 1.5,
2840
3082
  cursor: "pointer"
2841
3083
  };
@@ -2846,12 +3088,12 @@ function submitStyle(disabled) {
2846
3088
  background: "var(--primary, #f97316)",
2847
3089
  color: "var(--primary-foreground, #0a0a0a)",
2848
3090
  border: "none",
2849
- borderRadius: 6,
2850
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2851
- fontSize: 11,
2852
- letterSpacing: "0.14em",
2853
- textTransform: "uppercase",
2854
- fontWeight: 600,
3091
+ borderRadius: SIGNIN_BUTTON_RADIUS,
3092
+ fontFamily: SIGNIN_FONT,
3093
+ fontSize: signinSize(11),
3094
+ letterSpacing: signinTracking("0.14em"),
3095
+ textTransform: SIGNIN_CASE,
3096
+ fontWeight: SIGNIN_STRONG,
2855
3097
  cursor: disabled ? "not-allowed" : "pointer",
2856
3098
  opacity: disabled ? 0.5 : 1,
2857
3099
  width: "100%"
@@ -2866,8 +3108,8 @@ function ErrorLine2({ children }) {
2866
3108
  marginTop: 6,
2867
3109
  marginBottom: 0,
2868
3110
  color: "var(--destructive, #ef4444)",
2869
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
2870
- fontSize: 11,
3111
+ fontFamily: SIGNIN_FONT,
3112
+ fontSize: signinSize(11),
2871
3113
  letterSpacing: "0.04em"
2872
3114
  },
2873
3115
  children
@@ -2882,10 +3124,9 @@ function Hint({ children }) {
2882
3124
  marginTop: 12,
2883
3125
  marginBottom: 0,
2884
3126
  color: "var(--muted-foreground, #a1a1aa)",
2885
- fontFamily: "ui-monospace, SFMono-Regular, monospace",
3127
+ fontFamily: SIGNIN_FONT,
2886
3128
  fontSize: 10.5,
2887
- lineHeight: 1.55,
2888
- opacity: 0.7
3129
+ lineHeight: 1.55
2889
3130
  },
2890
3131
  children: [
2891
3132
  "> ",
@@ -2904,13 +3145,13 @@ function isAbortError(err) {
2904
3145
  }
2905
3146
  function useWebAuthnRegister(opts) {
2906
3147
  const authOrigin = resolveAuthOrigin(opts);
2907
- const [status, setStatus] = React3.useState("idle");
2908
- const [error, setError] = React3.useState(null);
2909
- const reset = React3.useCallback(() => {
3148
+ const [status, setStatus] = React4.useState("idle");
3149
+ const [error, setError] = React4.useState(null);
3150
+ const reset = React4.useCallback(() => {
2910
3151
  setStatus("idle");
2911
3152
  setError(null);
2912
3153
  }, []);
2913
- const register = React3.useCallback(
3154
+ const register = React4.useCallback(
2914
3155
  async (args) => {
2915
3156
  setError(null);
2916
3157
  setStatus("requesting-options");
@@ -2972,10 +3213,10 @@ function useWebAuthnRegister(opts) {
2972
3213
  function useWebAuthnList(opts) {
2973
3214
  const authOrigin = resolveAuthOrigin(opts);
2974
3215
  const { status: sessionStatus } = useOcSession();
2975
- const [credentials, setCredentials] = React3.useState([]);
2976
- const [status, setStatus] = React3.useState("loading");
2977
- const [error, setError] = React3.useState(null);
2978
- const refetch = React3.useCallback(async () => {
3216
+ const [credentials, setCredentials] = React4.useState([]);
3217
+ const [status, setStatus] = React4.useState("loading");
3218
+ const [error, setError] = React4.useState(null);
3219
+ const refetch = React4.useCallback(async () => {
2979
3220
  setError(null);
2980
3221
  if (sessionStatus !== "authenticated") {
2981
3222
  setCredentials([]);
@@ -3003,10 +3244,10 @@ function useWebAuthnList(opts) {
3003
3244
  setStatus("error");
3004
3245
  }
3005
3246
  }, [authOrigin, sessionStatus]);
3006
- React3.useEffect(() => {
3247
+ React4.useEffect(() => {
3007
3248
  void refetch();
3008
3249
  }, [refetch]);
3009
- const rename = React3.useCallback(
3250
+ const rename = React4.useCallback(
3010
3251
  async (id, label) => {
3011
3252
  try {
3012
3253
  const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials/${id}`, {
@@ -3031,7 +3272,7 @@ function useWebAuthnList(opts) {
3031
3272
  },
3032
3273
  [authOrigin, refetch]
3033
3274
  );
3034
- const remove = React3.useCallback(
3275
+ const remove = React4.useCallback(
3035
3276
  async (id) => {
3036
3277
  try {
3037
3278
  const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials/${id}`, {
@@ -3059,13 +3300,13 @@ function useWebAuthnList(opts) {
3059
3300
  function useStepUpAuth(opts) {
3060
3301
  const authOrigin = resolveAuthOrigin(opts);
3061
3302
  const { refresh } = useOcSession();
3062
- const [status, setStatus] = React3.useState("idle");
3063
- const [error, setError] = React3.useState(null);
3064
- const reset = React3.useCallback(() => {
3303
+ const [status, setStatus] = React4.useState("idle");
3304
+ const [error, setError] = React4.useState(null);
3305
+ const reset = React4.useCallback(() => {
3065
3306
  setStatus("idle");
3066
3307
  setError(null);
3067
3308
  }, []);
3068
- const stepUp = React3.useCallback(
3309
+ const stepUp = React4.useCallback(
3069
3310
  async (args) => {
3070
3311
  setError(null);
3071
3312
  setStatus("requesting-options");