@orangecheck/auth-client 2.5.0 → 2.6.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.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var React4 = require('react');
3
+ var React3 = require('react');
4
4
  var jsxRuntime = require('react/jsx-runtime');
5
5
  var browser = require('@simplewebauthn/browser');
6
6
 
@@ -22,7 +22,7 @@ function _interopNamespace(e) {
22
22
  return Object.freeze(n);
23
23
  }
24
24
 
25
- var React4__namespace = /*#__PURE__*/_interopNamespace(React4);
25
+ var React3__namespace = /*#__PURE__*/_interopNamespace(React3);
26
26
 
27
27
  // src/provider.tsx
28
28
 
@@ -43,7 +43,7 @@ function buildSignInUrl(cfg, returnTo) {
43
43
  u.searchParams.set("return_to", returnTo);
44
44
  return u.toString();
45
45
  }
46
- var SessionContext = React4__namespace.createContext(null);
46
+ var SessionContext = React3__namespace.createContext(null);
47
47
  function normalizeAccount(raw) {
48
48
  if (!raw) return null;
49
49
  const didOc = raw.did_oc ?? raw.didOc;
@@ -66,11 +66,11 @@ function OcSessionProvider({
66
66
  config,
67
67
  defaultReturnTo
68
68
  }) {
69
- const cfg = React4__namespace.useMemo(() => resolveConfig(config), [config]);
70
- const [account, setAccount] = React4__namespace.useState(null);
71
- const [status, setStatus] = React4__namespace.useState("loading");
72
- const [error, setError] = React4__namespace.useState(null);
73
- const refresh = React4__namespace.useCallback(async () => {
69
+ const cfg = React3__namespace.useMemo(() => resolveConfig(config), [config]);
70
+ const [account, setAccount] = React3__namespace.useState(null);
71
+ const [status, setStatus] = React3__namespace.useState("loading");
72
+ const [error, setError] = React3__namespace.useState(null);
73
+ const refresh = React3__namespace.useCallback(async () => {
74
74
  if (typeof window === "undefined") return;
75
75
  try {
76
76
  const res = await fetch(cfg.mePath, {
@@ -99,10 +99,10 @@ function OcSessionProvider({
99
99
  setError(err instanceof Error ? err : new Error(String(err)));
100
100
  }
101
101
  }, [cfg.mePath]);
102
- React4__namespace.useEffect(() => {
102
+ React3__namespace.useEffect(() => {
103
103
  void refresh();
104
104
  }, [refresh]);
105
- const signOut = React4__namespace.useCallback(async () => {
105
+ const signOut = React3__namespace.useCallback(async () => {
106
106
  try {
107
107
  await fetch(`${cfg.authOrigin}${cfg.logoutPath}`, {
108
108
  method: "POST",
@@ -113,7 +113,7 @@ function OcSessionProvider({
113
113
  setAccount(null);
114
114
  setStatus("anonymous");
115
115
  }, [cfg.authOrigin, cfg.logoutPath]);
116
- const value = React4__namespace.useMemo(() => {
116
+ const value = React3__namespace.useMemo(() => {
117
117
  const returnTo = defaultReturnTo ?? (typeof window !== "undefined" ? window.location.href : void 0);
118
118
  return {
119
119
  status,
@@ -127,7 +127,7 @@ function OcSessionProvider({
127
127
  return /* @__PURE__ */ jsxRuntime.jsx(SessionContext.Provider, { value, children });
128
128
  }
129
129
  function useOcSession() {
130
- const ctx = React4__namespace.useContext(SessionContext);
130
+ const ctx = React3__namespace.useContext(SessionContext);
131
131
  if (!ctx) {
132
132
  throw new Error(
133
133
  "[@orangecheck/auth-client] useOcSession() must be called inside <OcSessionProvider>"
@@ -136,7 +136,7 @@ function useOcSession() {
136
136
  return ctx;
137
137
  }
138
138
  function useOptionalOcSession() {
139
- return React4__namespace.useContext(SessionContext);
139
+ return React3__namespace.useContext(SessionContext);
140
140
  }
141
141
  function shortenAddress(addr) {
142
142
  if (addr.length <= 12) return addr;
@@ -151,7 +151,7 @@ function isPrefixOf(value, target) {
151
151
  }
152
152
  var listboxIdCounter = 0;
153
153
  function useUniqueId(prefix) {
154
- const [id] = React4__namespace.useState(() => `${prefix}-${++listboxIdCounter}`);
154
+ const [id] = React3__namespace.useState(() => `${prefix}-${++listboxIdCounter}`);
155
155
  return id;
156
156
  }
157
157
  function OcAccountChip({
@@ -165,9 +165,9 @@ function OcAccountChip({
165
165
  }) {
166
166
  const { status, account, signInUrl: sessionSignInUrl, signOut } = useOcSession();
167
167
  const signInUrl = signInUrlOverride ?? sessionSignInUrl;
168
- const [open, setOpen] = React4__namespace.useState(false);
169
- const wrapRef = React4__namespace.useRef(null);
170
- React4__namespace.useEffect(() => {
168
+ const [open, setOpen] = React3__namespace.useState(false);
169
+ const wrapRef = React3__namespace.useRef(null);
170
+ React3__namespace.useEffect(() => {
171
171
  if (!open) return;
172
172
  function handleClickOutside(e) {
173
173
  if (wrapRef.current && !wrapRef.current.contains(e.target)) {
@@ -490,9 +490,9 @@ function useOcAddressSuggestion(options) {
490
490
  } = options;
491
491
  const { status, account } = useOcSession();
492
492
  const sessionAddress = status === "authenticated" ? account?.primaryBtc ?? null : null;
493
- const [open, setOpen] = React4__namespace.useState(false);
494
- const [highlighted, setHighlighted] = React4__namespace.useState(false);
495
- const blurTimer = React4__namespace.useRef(null);
493
+ const [open, setOpen] = React3__namespace.useState(false);
494
+ const [highlighted, setHighlighted] = React3__namespace.useState(false);
495
+ const blurTimer = React3__namespace.useRef(null);
496
496
  const listboxId = useUniqueId("oc-addr-listbox");
497
497
  const optionId = `${listboxId}-opt`;
498
498
  const valueMatchesSession = sessionAddress != null && value.toLowerCase() === sessionAddress.toLowerCase();
@@ -504,7 +504,7 @@ function useOcAddressSuggestion(options) {
504
504
  setOpen(false);
505
505
  setHighlighted(false);
506
506
  }
507
- React4__namespace.useEffect(() => {
507
+ React3__namespace.useEffect(() => {
508
508
  return () => {
509
509
  if (blurTimer.current != null) window.clearTimeout(blurTimer.current);
510
510
  };
@@ -607,7 +607,7 @@ function useOcAddressSuggestion(options) {
607
607
  ) : null;
608
608
  return { inputProps, popover };
609
609
  }
610
- var OcAddressInput = React4__namespace.forwardRef(
610
+ var OcAddressInput = React3__namespace.forwardRef(
611
611
  function OcAddressInput2({
612
612
  value,
613
613
  onValueChange,
@@ -622,15 +622,15 @@ var OcAddressInput = React4__namespace.forwardRef(
622
622
  }, forwardedRef) {
623
623
  const { status, account } = useOcSession();
624
624
  const sessionAddress = status === "authenticated" ? account?.primaryBtc ?? null : null;
625
- const [open, setOpen] = React4__namespace.useState(false);
626
- const [highlighted, setHighlighted] = React4__namespace.useState(false);
627
- const innerRef = React4__namespace.useRef(null);
625
+ const [open, setOpen] = React3__namespace.useState(false);
626
+ const [highlighted, setHighlighted] = React3__namespace.useState(false);
627
+ const innerRef = React3__namespace.useRef(null);
628
628
  const setRef = (node) => {
629
629
  innerRef.current = node;
630
630
  if (typeof forwardedRef === "function") forwardedRef(node);
631
631
  else if (forwardedRef) forwardedRef.current = node;
632
632
  };
633
- const blurTimer = React4__namespace.useRef(null);
633
+ const blurTimer = React3__namespace.useRef(null);
634
634
  const listboxId = useUniqueId("oc-addr-listbox");
635
635
  const optionId = `${listboxId}-opt`;
636
636
  const valueMatchesSession = sessionAddress != null && value.toLowerCase() === sessionAddress.toLowerCase();
@@ -677,7 +677,7 @@ var OcAddressInput = React4__namespace.forwardRef(
677
677
  }
678
678
  onKeyDown?.(e);
679
679
  }
680
- React4__namespace.useEffect(() => {
680
+ React3__namespace.useEffect(() => {
681
681
  return () => {
682
682
  if (blurTimer.current != null) window.clearTimeout(blurTimer.current);
683
683
  };
@@ -770,6 +770,797 @@ var OcAddressInput = React4__namespace.forwardRef(
770
770
  );
771
771
  }
772
772
  );
773
+ var DEFAULT_AUTH_ORIGIN = "https://ochk.io";
774
+ function OcLinkedIdentities({
775
+ authOrigin = DEFAULT_AUTH_ORIGIN,
776
+ className,
777
+ onChange
778
+ }) {
779
+ const [resp, setResp] = React3__namespace.useState(null);
780
+ const [error, setError] = React3__namespace.useState(null);
781
+ const [showEmailLink, setShowEmailLink] = React3__namespace.useState(false);
782
+ const [showBtcLink, setShowBtcLink] = React3__namespace.useState(false);
783
+ const refresh = React3__namespace.useCallback(async () => {
784
+ setError(null);
785
+ try {
786
+ const r = await fetch(`${authOrigin}/api/auth/identities`, {
787
+ credentials: "include"
788
+ });
789
+ if (!r.ok) {
790
+ if (r.status === 401) {
791
+ setError("not signed in");
792
+ return;
793
+ }
794
+ throw new Error(`http_${r.status}`);
795
+ }
796
+ setResp(await r.json());
797
+ } catch (e) {
798
+ setError(e instanceof Error ? e.message : "load failed");
799
+ }
800
+ }, [authOrigin]);
801
+ React3__namespace.useEffect(() => {
802
+ void refresh();
803
+ }, [refresh]);
804
+ const afterChange = React3__namespace.useCallback(() => {
805
+ setShowEmailLink(false);
806
+ setShowBtcLink(false);
807
+ void refresh();
808
+ onChange?.();
809
+ }, [refresh, onChange]);
810
+ if (error) {
811
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, "data-oc-li": "", style: panelStyle("warn"), role: "alert", children: [
812
+ /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { tone: "warn", children: "\xA7 identities \xB7 load deferred" }),
813
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: bodyStyle, children: [
814
+ "Couldn't reach the auth host (",
815
+ error,
816
+ "). Linking is temporarily unavailable; your signed-in identity still works as normal."
817
+ ] }),
818
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: () => void refresh(), style: ghostBtnStyle(false), children: "retry" })
819
+ ] });
820
+ }
821
+ if (!resp) {
822
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, "data-oc-li": "", style: panelStyle(), children: /* @__PURE__ */ jsxRuntime.jsxs("span", { style: hintStyle, children: [
823
+ "> ",
824
+ "loading\u2026"
825
+ ] }) });
826
+ }
827
+ const linked = resp.linked ?? [];
828
+ const emailRows = linked.filter((i) => i.kind === "email");
829
+ const btcRows = linked.filter((i) => i.kind === "btc");
830
+ const hasEmail = emailRows.some((i) => i.value);
831
+ const hasBtc = btcRows.some((i) => i.value);
832
+ const mergedFrom = resp.merged_from ?? [];
833
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className, "data-oc-li": "", style: { display: "grid", gap: 16 }, children: [
834
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panelStyle(), children: [
835
+ /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { children: "\xA7 what this is" }),
836
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: bodyStyle, children: [
837
+ "Your public identity is the opaque ",
838
+ /* @__PURE__ */ jsxRuntime.jsx("code", { style: codeStyle, children: "did:oc:" }),
839
+ " 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."
840
+ ] })
841
+ ] }),
842
+ mergedFrom.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(ConsolidationPanel, { entries: mergedFrom }),
843
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panelStyle("plain"), "data-oc-li-rows": "", children: [
844
+ /* @__PURE__ */ jsxRuntime.jsx(
845
+ IdentityGroup,
846
+ {
847
+ kind: "email",
848
+ title: "email",
849
+ rows: emailRows,
850
+ authOrigin,
851
+ onChange: afterChange,
852
+ addCta: hasEmail ? null : "link an email",
853
+ onAdd: () => {
854
+ setShowEmailLink((v) => !v);
855
+ setShowBtcLink(false);
856
+ }
857
+ }
858
+ ),
859
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { borderTop: `1px solid ${V.border}` } }),
860
+ /* @__PURE__ */ jsxRuntime.jsx(
861
+ IdentityGroup,
862
+ {
863
+ kind: "btc",
864
+ title: "bitcoin address",
865
+ rows: btcRows,
866
+ authOrigin,
867
+ onChange: afterChange,
868
+ addCta: hasBtc ? null : "link a Bitcoin address",
869
+ onAdd: () => {
870
+ setShowBtcLink((v) => !v);
871
+ setShowEmailLink(false);
872
+ }
873
+ }
874
+ )
875
+ ] }),
876
+ showEmailLink && /* @__PURE__ */ jsxRuntime.jsx(
877
+ EmailLinkForm,
878
+ {
879
+ authOrigin,
880
+ onDone: afterChange,
881
+ onCancel: () => setShowEmailLink(false)
882
+ }
883
+ ),
884
+ showBtcLink && /* @__PURE__ */ jsxRuntime.jsx(
885
+ BtcLinkForm,
886
+ {
887
+ authOrigin,
888
+ didOc: resp.did_oc ?? null,
889
+ onDone: afterChange,
890
+ onCancel: () => setShowBtcLink(false)
891
+ }
892
+ )
893
+ ] });
894
+ }
895
+ function IdentityGroup({
896
+ kind,
897
+ title,
898
+ rows,
899
+ authOrigin,
900
+ onChange,
901
+ addCta,
902
+ onAdd
903
+ }) {
904
+ if (rows.length === 0) {
905
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: rowStyle, children: [
906
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
907
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: rowLabelStyle, children: [
908
+ "\xA7 ",
909
+ title
910
+ ] }),
911
+ /* @__PURE__ */ jsxRuntime.jsx("div", { style: { ...bodyStyle, marginTop: 4 }, children: "none linked" })
912
+ ] }),
913
+ addCta && /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", onClick: onAdd, style: primaryChipStyle, children: [
914
+ "+ ",
915
+ addCta
916
+ ] })
917
+ ] });
918
+ }
919
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: rows.map((r) => /* @__PURE__ */ jsxRuntime.jsx(
920
+ IdentityRow,
921
+ {
922
+ row: r,
923
+ kind,
924
+ authOrigin,
925
+ onChange
926
+ },
927
+ r.id
928
+ )) });
929
+ }
930
+ function IdentityRow({
931
+ row,
932
+ kind,
933
+ authOrigin,
934
+ onChange
935
+ }) {
936
+ const [shown, setShown] = React3__namespace.useState(false);
937
+ const [confirming, setConfirming] = React3__namespace.useState(false);
938
+ const [busy, setBusy] = React3__namespace.useState(false);
939
+ const [err, setErr] = React3__namespace.useState(null);
940
+ const masked = row.value ? mask(row.value, kind) : "\u2014 pending recovery \u2014";
941
+ const display = shown && row.value ? row.value : masked;
942
+ async function unlink() {
943
+ if (row.is_primary) return;
944
+ setBusy(true);
945
+ setErr(null);
946
+ try {
947
+ const r = await fetch(
948
+ `${authOrigin}/api/auth/identities?id=${encodeURIComponent(row.id)}`,
949
+ { method: "DELETE", credentials: "include" }
950
+ );
951
+ if (!r.ok) {
952
+ const j = await r.json().catch(() => ({}));
953
+ throw new Error(j.reason ?? `http_${r.status}`);
954
+ }
955
+ onChange();
956
+ } catch (e) {
957
+ setErr(e instanceof Error ? e.message : "unlink failed");
958
+ setBusy(false);
959
+ setConfirming(false);
960
+ }
961
+ }
962
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: rowStyle, "data-oc-li-row": kind, children: [
963
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [
964
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexWrap: "wrap", gap: "4px 12px" }, children: [
965
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: rowLabelStyle, children: [
966
+ "\xA7 ",
967
+ kind === "email" ? "email" : "bitcoin address"
968
+ ] }),
969
+ row.is_primary && /* @__PURE__ */ jsxRuntime.jsx("span", { style: { ...rowLabelStyle, color: V.primary }, children: "primary \xB7 signup-time" })
970
+ ] }),
971
+ /* @__PURE__ */ jsxRuntime.jsx(
972
+ "div",
973
+ {
974
+ style: {
975
+ marginTop: 4,
976
+ color: V.foreground,
977
+ fontFamily: MONO,
978
+ fontSize: 12,
979
+ wordBreak: "break-all"
980
+ },
981
+ children: display
982
+ }
983
+ ),
984
+ row.verified_at && /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { ...hintStyle, marginTop: 4 }, children: [
985
+ "verified \xB7 ",
986
+ new Date(row.verified_at).toUTCString()
987
+ ] }),
988
+ err && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine, { children: err })
989
+ ] }),
990
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { style: { display: "flex", flexShrink: 0, gap: 8, flexWrap: "wrap" }, children: [
991
+ row.value && /* @__PURE__ */ jsxRuntime.jsx(
992
+ "button",
993
+ {
994
+ type: "button",
995
+ onClick: () => setShown((s) => !s),
996
+ style: ghostBtnStyle(false),
997
+ children: shown ? "hide" : "show"
998
+ }
999
+ ),
1000
+ !row.is_primary && !confirming && /* @__PURE__ */ jsxRuntime.jsx(
1001
+ "button",
1002
+ {
1003
+ type: "button",
1004
+ onClick: () => setConfirming(true),
1005
+ style: dangerBtnStyle(false),
1006
+ children: "unlink"
1007
+ }
1008
+ ),
1009
+ !row.is_primary && confirming && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1010
+ /* @__PURE__ */ jsxRuntime.jsx(
1011
+ "button",
1012
+ {
1013
+ type: "button",
1014
+ onClick: () => void unlink(),
1015
+ disabled: busy,
1016
+ style: dangerBtnStyle(busy),
1017
+ children: busy ? "unlinking\u2026" : "confirm unlink"
1018
+ }
1019
+ ),
1020
+ /* @__PURE__ */ jsxRuntime.jsx(
1021
+ "button",
1022
+ {
1023
+ type: "button",
1024
+ onClick: () => setConfirming(false),
1025
+ disabled: busy,
1026
+ style: ghostBtnStyle(busy),
1027
+ children: "cancel"
1028
+ }
1029
+ )
1030
+ ] })
1031
+ ] })
1032
+ ] });
1033
+ }
1034
+ function ConsolidationPanel({ entries }) {
1035
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panelStyle("success"), "data-oc-li-consolidated": "", children: [
1036
+ /* @__PURE__ */ jsxRuntime.jsxs(SectionLabel, { tone: "success", children: [
1037
+ "\xA7 consolidated accounts \xB7 ",
1038
+ entries.length
1039
+ ] }),
1040
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: bodyStyle, children: [
1041
+ entries.length === 1 ? "A previous OC account" : "Previous OC accounts",
1042
+ " you signed up with, folded into this one under dual proof of control. Historical activity is unioned into this account automatically;",
1043
+ " ",
1044
+ entries.length === 1 ? "it" : "they",
1045
+ " can no longer sign in."
1046
+ ] }),
1047
+ /* @__PURE__ */ jsxRuntime.jsx("ul", { style: { listStyle: "none", margin: "12px 0 0", padding: 0 }, children: entries.map((e) => /* @__PURE__ */ jsxRuntime.jsxs(
1048
+ "li",
1049
+ {
1050
+ style: {
1051
+ display: "flex",
1052
+ flexWrap: "wrap",
1053
+ gap: 8,
1054
+ padding: "8px 0",
1055
+ borderTop: `1px solid ${V.border}`,
1056
+ fontFamily: MONO,
1057
+ fontSize: 11
1058
+ },
1059
+ children: [
1060
+ /* @__PURE__ */ jsxRuntime.jsx("code", { style: { color: V.foreground, wordBreak: "break-all" }, children: e.did_oc }),
1061
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { style: { ...hintStyle, marginLeft: "auto" }, children: [
1062
+ "created ",
1063
+ new Date(e.created_at).toLocaleDateString()
1064
+ ] })
1065
+ ]
1066
+ },
1067
+ e.did_oc
1068
+ )) })
1069
+ ] });
1070
+ }
1071
+ function EmailLinkForm({
1072
+ authOrigin,
1073
+ onDone,
1074
+ onCancel
1075
+ }) {
1076
+ const [step, setStep] = React3__namespace.useState("email");
1077
+ const [email, setEmail] = React3__namespace.useState("");
1078
+ const [code, setCode] = React3__namespace.useState("");
1079
+ const [token, setToken] = React3__namespace.useState("");
1080
+ const [busy, setBusy] = React3__namespace.useState(false);
1081
+ const [err, setErr] = React3__namespace.useState(null);
1082
+ const [transfer, setTransfer] = React3__namespace.useState(false);
1083
+ async function start() {
1084
+ setBusy(true);
1085
+ setErr(null);
1086
+ try {
1087
+ const r = await fetch(`${authOrigin}/api/auth/email-otp/start`, {
1088
+ method: "POST",
1089
+ credentials: "include",
1090
+ headers: { "Content-Type": "application/json" },
1091
+ body: JSON.stringify({ email: email.trim().toLowerCase() })
1092
+ });
1093
+ const j = await r.json();
1094
+ if (!r.ok || !j.token) throw new Error(j.reason ?? `http_${r.status}`);
1095
+ setToken(j.token);
1096
+ setStep("code");
1097
+ } catch (e) {
1098
+ setErr(e instanceof Error ? e.message : "could not send code");
1099
+ } finally {
1100
+ setBusy(false);
1101
+ }
1102
+ }
1103
+ async function verify(confirmTransfer) {
1104
+ setBusy(true);
1105
+ setErr(null);
1106
+ try {
1107
+ const r = await fetch(`${authOrigin}/api/auth/link/email`, {
1108
+ method: "POST",
1109
+ credentials: "include",
1110
+ headers: { "Content-Type": "application/json" },
1111
+ body: JSON.stringify({
1112
+ email: email.trim().toLowerCase(),
1113
+ code: code.trim(),
1114
+ token,
1115
+ ...confirmTransfer ? { confirm_transfer: true } : {}
1116
+ })
1117
+ });
1118
+ const j = await r.json();
1119
+ if (r.status === 409 && j.reason === "email_linked_elsewhere") {
1120
+ setTransfer(true);
1121
+ setBusy(false);
1122
+ return;
1123
+ }
1124
+ if (!r.ok || !j.ok) throw new Error(j.reason ?? `http_${r.status}`);
1125
+ onDone();
1126
+ } catch (e) {
1127
+ setErr(e instanceof Error ? e.message : "link failed");
1128
+ setBusy(false);
1129
+ }
1130
+ }
1131
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panelStyle("accent"), "data-oc-li-form": "email", children: [
1132
+ /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { children: "\xA7 link an email" }),
1133
+ step === "email" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1134
+ /* @__PURE__ */ jsxRuntime.jsx(
1135
+ "input",
1136
+ {
1137
+ type: "email",
1138
+ autoComplete: "email",
1139
+ value: email,
1140
+ onChange: (e) => setEmail(e.target.value),
1141
+ placeholder: "you@example.com",
1142
+ style: inputStyle
1143
+ }
1144
+ ),
1145
+ err && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine, { children: err }),
1146
+ /* @__PURE__ */ jsxRuntime.jsxs(FormButtons, { children: [
1147
+ /* @__PURE__ */ jsxRuntime.jsx(
1148
+ "button",
1149
+ {
1150
+ type: "button",
1151
+ onClick: () => void start(),
1152
+ disabled: busy || email.trim().length < 5,
1153
+ style: primaryBtnStyle(busy || email.trim().length < 5),
1154
+ children: busy ? "sending\u2026" : "send code"
1155
+ }
1156
+ ),
1157
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: onCancel, style: ghostBtnStyle(busy), children: "cancel" })
1158
+ ] })
1159
+ ] }) : transfer ? /* @__PURE__ */ jsxRuntime.jsx(
1160
+ TransferConfirm,
1161
+ {
1162
+ what: "email",
1163
+ busy,
1164
+ onConfirm: () => void verify(true),
1165
+ onCancel,
1166
+ err
1167
+ }
1168
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1169
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: bodyStyle, children: [
1170
+ "Enter the 6-digit code sent to ",
1171
+ email,
1172
+ "."
1173
+ ] }),
1174
+ /* @__PURE__ */ jsxRuntime.jsx(
1175
+ "input",
1176
+ {
1177
+ type: "text",
1178
+ inputMode: "numeric",
1179
+ autoComplete: "one-time-code",
1180
+ maxLength: 6,
1181
+ value: code,
1182
+ onChange: (e) => setCode(e.target.value.replace(/\D/g, "").slice(0, 6)),
1183
+ placeholder: "123456",
1184
+ style: { ...inputStyle, letterSpacing: "0.3em" }
1185
+ }
1186
+ ),
1187
+ err && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine, { children: err }),
1188
+ /* @__PURE__ */ jsxRuntime.jsxs(FormButtons, { children: [
1189
+ /* @__PURE__ */ jsxRuntime.jsx(
1190
+ "button",
1191
+ {
1192
+ type: "button",
1193
+ onClick: () => void verify(false),
1194
+ disabled: busy || code.length !== 6,
1195
+ style: primaryBtnStyle(busy || code.length !== 6),
1196
+ children: busy ? "linking\u2026" : "verify + link"
1197
+ }
1198
+ ),
1199
+ /* @__PURE__ */ jsxRuntime.jsx(
1200
+ "button",
1201
+ {
1202
+ type: "button",
1203
+ onClick: () => setStep("email"),
1204
+ disabled: busy,
1205
+ style: ghostBtnStyle(busy),
1206
+ children: "back"
1207
+ }
1208
+ )
1209
+ ] })
1210
+ ] })
1211
+ ] });
1212
+ }
1213
+ function BtcLinkForm({
1214
+ authOrigin,
1215
+ didOc,
1216
+ onDone,
1217
+ onCancel
1218
+ }) {
1219
+ const [step, setStep] = React3__namespace.useState("request");
1220
+ const [addr, setAddr] = React3__namespace.useState("");
1221
+ const [challenge, setChallenge] = React3__namespace.useState("");
1222
+ const [busy, setBusy] = React3__namespace.useState(false);
1223
+ const [err, setErr] = React3__namespace.useState(null);
1224
+ const [pendingSig, setPendingSig] = React3__namespace.useState(null);
1225
+ async function getChallenge() {
1226
+ if (!didOc) {
1227
+ setErr("session not loaded yet \xB7 refresh and retry");
1228
+ return;
1229
+ }
1230
+ setBusy(true);
1231
+ setErr(null);
1232
+ try {
1233
+ const r = await fetch(
1234
+ `${authOrigin}/api/challenge?addr=${encodeURIComponent(addr)}&purpose=link&audience=${encodeURIComponent(didOc)}`
1235
+ );
1236
+ const j = await r.json();
1237
+ if (!r.ok || !j.message) throw new Error(j.reason ?? `http_${r.status}`);
1238
+ setChallenge(j.message);
1239
+ setStep("sign");
1240
+ } catch (e) {
1241
+ setErr(e instanceof Error ? e.message : "challenge failed");
1242
+ } finally {
1243
+ setBusy(false);
1244
+ }
1245
+ }
1246
+ async function postLink(sig, confirmTransfer) {
1247
+ setBusy(true);
1248
+ setErr(null);
1249
+ try {
1250
+ const r = await fetch(`${authOrigin}/api/auth/link/btc`, {
1251
+ method: "POST",
1252
+ credentials: "include",
1253
+ headers: { "Content-Type": "application/json" },
1254
+ body: JSON.stringify({
1255
+ message: challenge,
1256
+ signature: sig,
1257
+ ...confirmTransfer ? { confirm_transfer: true } : {}
1258
+ })
1259
+ });
1260
+ const j = await r.json();
1261
+ if (r.status === 409 && j.reason === "address_linked_elsewhere") {
1262
+ setPendingSig(sig);
1263
+ setBusy(false);
1264
+ return;
1265
+ }
1266
+ if (!r.ok || !j.ok) throw new Error(j.reason ?? `http_${r.status}`);
1267
+ onDone();
1268
+ } catch (e) {
1269
+ setErr(e instanceof Error ? e.message : "link failed");
1270
+ setBusy(false);
1271
+ }
1272
+ }
1273
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { style: panelStyle("accent"), "data-oc-li-form": "btc", children: [
1274
+ /* @__PURE__ */ jsxRuntime.jsx(SectionLabel, { children: "\xA7 link a Bitcoin address" }),
1275
+ step === "request" ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1276
+ /* @__PURE__ */ jsxRuntime.jsx("p", { style: bodyStyle, children: "Enter a Bitcoin address you control. We generate a challenge and your wallet signs it (BIP-322)." }),
1277
+ /* @__PURE__ */ jsxRuntime.jsx(
1278
+ "input",
1279
+ {
1280
+ type: "text",
1281
+ value: addr,
1282
+ onChange: (e) => setAddr(e.target.value.trim()),
1283
+ placeholder: "bc1q\u2026",
1284
+ autoCapitalize: "none",
1285
+ spellCheck: false,
1286
+ style: { ...inputStyle, fontFamily: MONO }
1287
+ }
1288
+ ),
1289
+ err && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine, { children: err }),
1290
+ /* @__PURE__ */ jsxRuntime.jsxs(FormButtons, { children: [
1291
+ /* @__PURE__ */ jsxRuntime.jsx(
1292
+ "button",
1293
+ {
1294
+ type: "button",
1295
+ onClick: () => void getChallenge(),
1296
+ disabled: busy || addr.length < 14,
1297
+ style: primaryBtnStyle(busy || addr.length < 14),
1298
+ children: busy ? "requesting\u2026" : "get challenge"
1299
+ }
1300
+ ),
1301
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: onCancel, style: ghostBtnStyle(busy), children: "cancel" })
1302
+ ] })
1303
+ ] }) : pendingSig ? /* @__PURE__ */ jsxRuntime.jsx(
1304
+ TransferConfirm,
1305
+ {
1306
+ what: "bitcoin address",
1307
+ busy,
1308
+ onConfirm: () => void postLink(pendingSig, true),
1309
+ onCancel,
1310
+ err
1311
+ }
1312
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1313
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: bodyStyle, children: [
1314
+ "Sign the challenge with the wallet that controls",
1315
+ " ",
1316
+ /* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontFamily: MONO, wordBreak: "break-all" }, children: addr }),
1317
+ "."
1318
+ ] }),
1319
+ /* @__PURE__ */ jsxRuntime.jsx(
1320
+ LazyWalletButton,
1321
+ {
1322
+ address: addr,
1323
+ message: challenge,
1324
+ showManual: true,
1325
+ layout: "list",
1326
+ heading: null,
1327
+ onSigned: (sig) => void postLink(sig, false),
1328
+ onError: (e) => setErr(e?.message ?? "wallet rejected")
1329
+ }
1330
+ ),
1331
+ err && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine, { children: err }),
1332
+ /* @__PURE__ */ jsxRuntime.jsx(FormButtons, { children: /* @__PURE__ */ jsxRuntime.jsx(
1333
+ "button",
1334
+ {
1335
+ type: "button",
1336
+ onClick: () => {
1337
+ setStep("request");
1338
+ setErr(null);
1339
+ },
1340
+ disabled: busy,
1341
+ style: ghostBtnStyle(busy),
1342
+ children: "back"
1343
+ }
1344
+ ) })
1345
+ ] })
1346
+ ] });
1347
+ }
1348
+ function TransferConfirm({
1349
+ what,
1350
+ busy,
1351
+ onConfirm,
1352
+ onCancel,
1353
+ err
1354
+ }) {
1355
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1356
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { style: bodyStyle, children: [
1357
+ "This ",
1358
+ what,
1359
+ " is already linked to another OrangeCheck account. You've just proven control of it \u2014 you can transfer the link to this account under dual proof of control. The other account loses this sign-in path; its history stays verifiable. Continue?"
1360
+ ] }),
1361
+ err && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine, { children: err }),
1362
+ /* @__PURE__ */ jsxRuntime.jsxs(FormButtons, { children: [
1363
+ /* @__PURE__ */ jsxRuntime.jsx(
1364
+ "button",
1365
+ {
1366
+ type: "button",
1367
+ onClick: onConfirm,
1368
+ disabled: busy,
1369
+ style: dangerBtnStyle(busy),
1370
+ children: busy ? "transferring\u2026" : "transfer link here"
1371
+ }
1372
+ ),
1373
+ /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", onClick: onCancel, disabled: busy, style: ghostBtnStyle(busy), children: "cancel" })
1374
+ ] })
1375
+ ] });
1376
+ }
1377
+ function LazyWalletButton(props) {
1378
+ const [Comp, setComp] = React3__namespace.useState(null);
1379
+ const [failed, setFailed] = React3__namespace.useState(false);
1380
+ React3__namespace.useEffect(() => {
1381
+ let alive = true;
1382
+ import('@orangecheck/wallet-adapter/react').then((m) => {
1383
+ if (alive) {
1384
+ setComp(
1385
+ () => m.OcWalletButton
1386
+ );
1387
+ }
1388
+ }).catch(() => {
1389
+ if (alive) setFailed(true);
1390
+ });
1391
+ return () => {
1392
+ alive = false;
1393
+ };
1394
+ }, []);
1395
+ if (failed) {
1396
+ return /* @__PURE__ */ jsxRuntime.jsx(ErrorLine, { children: "@orangecheck/wallet-adapter not installed \xB7 add it to your site to link a wallet" });
1397
+ }
1398
+ if (!Comp) return /* @__PURE__ */ jsxRuntime.jsxs("span", { style: hintStyle, children: [
1399
+ "> ",
1400
+ "loading wallet options\u2026"
1401
+ ] });
1402
+ return /* @__PURE__ */ jsxRuntime.jsx(Comp, { ...props });
1403
+ }
1404
+ function mask(value, kind) {
1405
+ if (kind === "email") {
1406
+ const [user, host] = value.split("@");
1407
+ if (!user || !host) return "\u2022\u2022\u2022\u2022";
1408
+ const m = user.length <= 2 ? "\u2022".repeat(user.length) : user[0] + "\u2022".repeat(user.length - 2) + user.slice(-1);
1409
+ return `${m}@${host}`;
1410
+ }
1411
+ if (value.length <= 12) return "\u2022".repeat(value.length);
1412
+ return `${value.slice(0, 6)}\u2026${value.slice(-4)}`;
1413
+ }
1414
+ function SectionLabel({
1415
+ children,
1416
+ tone = "primary"
1417
+ }) {
1418
+ const color = tone === "success" ? V.success : tone === "warn" ? V.warn : V.primary;
1419
+ return /* @__PURE__ */ jsxRuntime.jsx(
1420
+ "div",
1421
+ {
1422
+ style: {
1423
+ color,
1424
+ fontFamily: MONO,
1425
+ fontSize: 10,
1426
+ letterSpacing: "0.18em",
1427
+ textTransform: "uppercase",
1428
+ marginBottom: 8
1429
+ },
1430
+ children
1431
+ }
1432
+ );
1433
+ }
1434
+ function ErrorLine({ children }) {
1435
+ return /* @__PURE__ */ jsxRuntime.jsx(
1436
+ "p",
1437
+ {
1438
+ role: "alert",
1439
+ style: {
1440
+ margin: "8px 0 0",
1441
+ color: V.destructive,
1442
+ fontFamily: MONO,
1443
+ fontSize: 11
1444
+ },
1445
+ children
1446
+ }
1447
+ );
1448
+ }
1449
+ function FormButtons({ children }) {
1450
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 8, marginTop: 12 }, children });
1451
+ }
1452
+ var MONO = "ui-monospace, SFMono-Regular, monospace";
1453
+ var V = {
1454
+ primary: "var(--primary, #f97316)",
1455
+ foreground: "var(--foreground, #fafafa)",
1456
+ muted: "var(--muted-foreground, #a1a1aa)",
1457
+ background: "var(--background, #0a0a0a)",
1458
+ card: "var(--card, #111113)",
1459
+ border: "var(--border, #27272a)",
1460
+ destructive: "var(--destructive, #ef4444)",
1461
+ success: "var(--success, #22c55e)",
1462
+ warn: "var(--warning, #eab308)"
1463
+ };
1464
+ function panelStyle(tone = "default") {
1465
+ const base = {
1466
+ background: V.card,
1467
+ border: `1px solid ${V.border}`,
1468
+ padding: "1.25rem"
1469
+ };
1470
+ if (tone === "plain") return { ...base, padding: 0 };
1471
+ if (tone === "accent") return { ...base, borderColor: V.primary, background: "var(--card, #111113)" };
1472
+ if (tone === "success") return { ...base, borderColor: V.success };
1473
+ if (tone === "warn") return { ...base, borderColor: V.warn };
1474
+ return base;
1475
+ }
1476
+ var bodyStyle = {
1477
+ color: V.muted,
1478
+ fontSize: 13,
1479
+ lineHeight: 1.55,
1480
+ margin: "0 0 12px"
1481
+ };
1482
+ var hintStyle = {
1483
+ color: V.muted,
1484
+ fontFamily: MONO,
1485
+ fontSize: 10.5,
1486
+ opacity: 0.8
1487
+ };
1488
+ var codeStyle = {
1489
+ fontFamily: MONO,
1490
+ color: V.foreground
1491
+ };
1492
+ var rowStyle = {
1493
+ display: "flex",
1494
+ flexWrap: "wrap",
1495
+ alignItems: "center",
1496
+ justifyContent: "space-between",
1497
+ gap: 12,
1498
+ padding: "1rem"
1499
+ };
1500
+ var rowLabelStyle = {
1501
+ color: V.foreground,
1502
+ fontFamily: MONO,
1503
+ fontSize: 10.5,
1504
+ letterSpacing: "0.12em",
1505
+ textTransform: "uppercase",
1506
+ opacity: 0.8
1507
+ };
1508
+ var inputStyle = {
1509
+ width: "100%",
1510
+ padding: "0.55rem 0.75rem",
1511
+ background: V.background,
1512
+ color: V.foreground,
1513
+ border: `1px solid ${V.border}`,
1514
+ borderRadius: 6,
1515
+ fontFamily: MONO,
1516
+ // 16px — never lower · suppresses iOS Safari focus auto-zoom.
1517
+ fontSize: 16,
1518
+ outline: "none",
1519
+ boxSizing: "border-box"
1520
+ };
1521
+ function btnBase(disabled) {
1522
+ return {
1523
+ padding: "0.5rem 0.875rem",
1524
+ borderRadius: 6,
1525
+ fontFamily: MONO,
1526
+ fontSize: 10.5,
1527
+ letterSpacing: "0.12em",
1528
+ textTransform: "uppercase",
1529
+ cursor: disabled ? "not-allowed" : "pointer",
1530
+ opacity: disabled ? 0.5 : 1
1531
+ };
1532
+ }
1533
+ function primaryBtnStyle(disabled) {
1534
+ return {
1535
+ ...btnBase(disabled),
1536
+ background: V.primary,
1537
+ color: "var(--primary-foreground, #0a0a0a)",
1538
+ border: `1px solid ${V.primary}`,
1539
+ fontWeight: 600
1540
+ };
1541
+ }
1542
+ function ghostBtnStyle(disabled) {
1543
+ return {
1544
+ ...btnBase(disabled),
1545
+ background: V.background,
1546
+ color: V.foreground,
1547
+ border: `1px solid ${V.border}`
1548
+ };
1549
+ }
1550
+ function dangerBtnStyle(disabled) {
1551
+ return {
1552
+ ...btnBase(disabled),
1553
+ background: "transparent",
1554
+ color: V.destructive,
1555
+ border: `1px solid ${V.destructive}`
1556
+ };
1557
+ }
1558
+ var primaryChipStyle = {
1559
+ ...btnBase(false),
1560
+ background: "transparent",
1561
+ color: V.primary,
1562
+ border: `1px solid ${V.primary}`
1563
+ };
773
1564
  function safeReturnTo(input) {
774
1565
  const candidate = input ?? "/";
775
1566
  if (typeof candidate !== "string") return "/";
@@ -785,6 +1576,7 @@ function OcSignIn({
785
1576
  returnTo,
786
1577
  onSuccess,
787
1578
  resolveReturnTo,
1579
+ linkPrompt,
788
1580
  authOrigin = "https://ochk.io",
789
1581
  initialPath = "wallet",
790
1582
  paths,
@@ -793,24 +1585,34 @@ function OcSignIn({
793
1585
  const walletEnabled = paths?.wallet ?? true;
794
1586
  const emailEnabled = paths?.email ?? true;
795
1587
  const safeReturn = safeReturnTo(returnTo);
796
- const [path, setPath] = React4__namespace.useState(initialPath);
797
- const handleSuccess = React4__namespace.useCallback(
1588
+ const [path, setPath] = React3__namespace.useState(initialPath);
1589
+ const [signedIn, setSignedIn] = React3__namespace.useState(null);
1590
+ const navigate = React3__namespace.useCallback(
798
1591
  async (account) => {
799
- if (onSuccess) {
800
- onSuccess(account);
801
- return;
802
- }
803
1592
  if (resolveReturnTo) {
804
1593
  try {
805
- const target = await resolveReturnTo(account);
806
- hardNavigate(safeReturnTo(target));
1594
+ hardNavigate(safeReturnTo(await resolveReturnTo(account)));
807
1595
  return;
808
1596
  } catch {
809
1597
  }
810
1598
  }
811
1599
  hardNavigate(safeReturn);
812
1600
  },
813
- [onSuccess, resolveReturnTo, safeReturn]
1601
+ [resolveReturnTo, safeReturn]
1602
+ );
1603
+ const handleSuccess = React3__namespace.useCallback(
1604
+ async (account, token) => {
1605
+ if (onSuccess) {
1606
+ onSuccess(account, token);
1607
+ return;
1608
+ }
1609
+ if (linkPrompt) {
1610
+ setSignedIn(account);
1611
+ return;
1612
+ }
1613
+ await navigate(account);
1614
+ },
1615
+ [onSuccess, linkPrompt, navigate]
814
1616
  );
815
1617
  if (!walletEnabled && !emailEnabled) {
816
1618
  return /* @__PURE__ */ jsxRuntime.jsxs(
@@ -837,6 +1639,15 @@ function OcSignIn({
837
1639
  }
838
1640
  );
839
1641
  }
1642
+ if (signedIn) {
1643
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className, "data-oc-signin": "", children: /* @__PURE__ */ jsxRuntime.jsx(
1644
+ LinkPromptStep,
1645
+ {
1646
+ authOrigin,
1647
+ onContinue: () => void navigate(signedIn)
1648
+ }
1649
+ ) });
1650
+ }
840
1651
  const showWallet = walletEnabled && (path === "wallet" || !emailEnabled);
841
1652
  const showEmail = emailEnabled && (path === "email" || !walletEnabled);
842
1653
  const bothEnabled = walletEnabled && emailEnabled;
@@ -879,6 +1690,25 @@ function OcSignIn({
879
1690
  ] })
880
1691
  ] });
881
1692
  }
1693
+ function LinkPromptStep({
1694
+ authOrigin,
1695
+ onContinue
1696
+ }) {
1697
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { "data-oc-signin-linkprompt": "", children: [
1698
+ /* @__PURE__ */ jsxRuntime.jsx(FlowHeader, { label: "\xA7 signed in \xB7 add a backup", children: "You're in. Linking a second way to sign in \u2014 your email or your Bitcoin wallet \u2014 is also your recovery path: lose one, the other still gets you in. Optional; skip with continue." }),
1699
+ /* @__PURE__ */ jsxRuntime.jsx(OcLinkedIdentities, { authOrigin }),
1700
+ /* @__PURE__ */ jsxRuntime.jsx(
1701
+ "button",
1702
+ {
1703
+ type: "button",
1704
+ onClick: onContinue,
1705
+ style: submitStyle(false),
1706
+ "data-oc-signin-continue": "",
1707
+ children: "continue \u2192"
1708
+ }
1709
+ )
1710
+ ] });
1711
+ }
882
1712
  function SigninTab({
883
1713
  active,
884
1714
  onClick,
@@ -910,10 +1740,10 @@ function SigninTab({
910
1740
  );
911
1741
  }
912
1742
  function WalletFlow({ authOrigin, audience, onSuccess }) {
913
- const [address, setAddress] = React4__namespace.useState("");
914
- const [error, setError] = React4__namespace.useState(null);
915
- const [submitting, setSubmitting] = React4__namespace.useState(false);
916
- const [stage, setStage] = React4__namespace.useState("enter");
1743
+ const [address, setAddress] = React3__namespace.useState("");
1744
+ const [error, setError] = React3__namespace.useState(null);
1745
+ const [submitting, setSubmitting] = React3__namespace.useState(false);
1746
+ const [stage, setStage] = React3__namespace.useState("enter");
917
1747
  async function signIn(e) {
918
1748
  e.preventDefault();
919
1749
  const addr = address.trim();
@@ -966,7 +1796,7 @@ function WalletFlow({ authOrigin, audience, onSuccess }) {
966
1796
  "reason" in json && json.reason || "error" in json && json.error || `verify_failed_${res.status}`
967
1797
  );
968
1798
  }
969
- onSuccess(json.account);
1799
+ onSuccess(json.account, json.token);
970
1800
  } catch (err) {
971
1801
  const msg = err instanceof Error ? err.message : "sign-in failed";
972
1802
  setError(msg);
@@ -992,10 +1822,10 @@ function WalletFlow({ authOrigin, audience, onSuccess }) {
992
1822
  spellCheck: false,
993
1823
  required: true,
994
1824
  "aria-invalid": error ? true : void 0,
995
- style: inputStyle
1825
+ style: inputStyle2
996
1826
  }
997
1827
  ),
998
- error && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine, { children: error }),
1828
+ error && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine2, { children: error }),
999
1829
  /* @__PURE__ */ jsxRuntime.jsx(
1000
1830
  "button",
1001
1831
  {
@@ -1009,13 +1839,13 @@ function WalletFlow({ authOrigin, audience, onSuccess }) {
1009
1839
  ] });
1010
1840
  }
1011
1841
  function EmailFlow({ authOrigin, onSuccess }) {
1012
- const [stage, setStage] = React4__namespace.useState("enter");
1013
- const [email, setEmail] = React4__namespace.useState("");
1014
- const [emailError, setEmailError] = React4__namespace.useState(null);
1015
- const [code, setCode] = React4__namespace.useState("");
1016
- const [codeError, setCodeError] = React4__namespace.useState(null);
1017
- const [token, setToken] = React4__namespace.useState(null);
1018
- const [submitting, setSubmitting] = React4__namespace.useState(false);
1842
+ const [stage, setStage] = React3__namespace.useState("enter");
1843
+ const [email, setEmail] = React3__namespace.useState("");
1844
+ const [emailError, setEmailError] = React3__namespace.useState(null);
1845
+ const [code, setCode] = React3__namespace.useState("");
1846
+ const [codeError, setCodeError] = React3__namespace.useState(null);
1847
+ const [token, setToken] = React3__namespace.useState(null);
1848
+ const [submitting, setSubmitting] = React3__namespace.useState(false);
1019
1849
  async function start(e) {
1020
1850
  e.preventDefault();
1021
1851
  const trimmed = email.trim().toLowerCase();
@@ -1067,7 +1897,7 @@ function EmailFlow({ authOrigin, onSuccess }) {
1067
1897
  "reason" in json && json.reason || "error" in json && json.error || `verify failed (${res.status})`
1068
1898
  );
1069
1899
  }
1070
- onSuccess(json.account);
1900
+ onSuccess(json.account, json.token);
1071
1901
  } catch (err) {
1072
1902
  setCodeError(err instanceof Error ? err.message : "verify failed");
1073
1903
  } finally {
@@ -1091,10 +1921,10 @@ function EmailFlow({ authOrigin, onSuccess }) {
1091
1921
  placeholder: "you@example.com",
1092
1922
  required: true,
1093
1923
  "aria-invalid": emailError ? true : void 0,
1094
- style: inputStyle
1924
+ style: inputStyle2
1095
1925
  }
1096
1926
  ),
1097
- emailError && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine, { children: emailError }),
1927
+ emailError && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine2, { children: emailError }),
1098
1928
  /* @__PURE__ */ jsxRuntime.jsx("button", { type: "submit", disabled: submitting, style: submitStyle(submitting), children: submitting ? "sending\u2026" : "send one-time code \u2192" }),
1099
1929
  /* @__PURE__ */ jsxRuntime.jsx(Hint, { children: "OTP delivered by the auth host. Codes expire in 10 minutes and are single-use. Rate-limited 5 starts/hour/IP." })
1100
1930
  ] });
@@ -1123,10 +1953,10 @@ function EmailFlow({ authOrigin, onSuccess }) {
1123
1953
  required: true,
1124
1954
  autoFocus: true,
1125
1955
  "aria-invalid": codeError ? true : void 0,
1126
- style: { ...inputStyle, letterSpacing: "0.4em", fontSize: 16 }
1956
+ style: { ...inputStyle2, letterSpacing: "0.4em", fontSize: 16 }
1127
1957
  }
1128
1958
  ),
1129
- codeError && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine, { children: codeError }),
1959
+ codeError && /* @__PURE__ */ jsxRuntime.jsx(ErrorLine2, { children: codeError }),
1130
1960
  /* @__PURE__ */ jsxRuntime.jsx(
1131
1961
  "button",
1132
1962
  {
@@ -1213,7 +2043,7 @@ function Label({ children }) {
1213
2043
  }
1214
2044
  );
1215
2045
  }
1216
- var inputStyle = {
2046
+ var inputStyle2 = {
1217
2047
  width: "100%",
1218
2048
  padding: "0.55rem 0.75rem",
1219
2049
  background: "var(--background, #0a0a0a)",
@@ -1245,7 +2075,7 @@ function submitStyle(disabled) {
1245
2075
  width: "100%"
1246
2076
  };
1247
2077
  }
1248
- function ErrorLine({ children }) {
2078
+ function ErrorLine2({ children }) {
1249
2079
  return /* @__PURE__ */ jsxRuntime.jsx(
1250
2080
  "p",
1251
2081
  {
@@ -1292,13 +2122,13 @@ function isAbortError(err) {
1292
2122
  }
1293
2123
  function useWebAuthnRegister(opts) {
1294
2124
  const authOrigin = resolveAuthOrigin(opts);
1295
- const [status, setStatus] = React4__namespace.useState("idle");
1296
- const [error, setError] = React4__namespace.useState(null);
1297
- const reset = React4__namespace.useCallback(() => {
2125
+ const [status, setStatus] = React3__namespace.useState("idle");
2126
+ const [error, setError] = React3__namespace.useState(null);
2127
+ const reset = React3__namespace.useCallback(() => {
1298
2128
  setStatus("idle");
1299
2129
  setError(null);
1300
2130
  }, []);
1301
- const register = React4__namespace.useCallback(
2131
+ const register = React3__namespace.useCallback(
1302
2132
  async (args) => {
1303
2133
  setError(null);
1304
2134
  setStatus("requesting-options");
@@ -1360,10 +2190,10 @@ function useWebAuthnRegister(opts) {
1360
2190
  function useWebAuthnList(opts) {
1361
2191
  const authOrigin = resolveAuthOrigin(opts);
1362
2192
  const { status: sessionStatus } = useOcSession();
1363
- const [credentials, setCredentials] = React4__namespace.useState([]);
1364
- const [status, setStatus] = React4__namespace.useState("loading");
1365
- const [error, setError] = React4__namespace.useState(null);
1366
- const refetch = React4__namespace.useCallback(async () => {
2193
+ const [credentials, setCredentials] = React3__namespace.useState([]);
2194
+ const [status, setStatus] = React3__namespace.useState("loading");
2195
+ const [error, setError] = React3__namespace.useState(null);
2196
+ const refetch = React3__namespace.useCallback(async () => {
1367
2197
  setError(null);
1368
2198
  if (sessionStatus !== "authenticated") {
1369
2199
  setCredentials([]);
@@ -1391,10 +2221,10 @@ function useWebAuthnList(opts) {
1391
2221
  setStatus("error");
1392
2222
  }
1393
2223
  }, [authOrigin, sessionStatus]);
1394
- React4__namespace.useEffect(() => {
2224
+ React3__namespace.useEffect(() => {
1395
2225
  void refetch();
1396
2226
  }, [refetch]);
1397
- const rename = React4__namespace.useCallback(
2227
+ const rename = React3__namespace.useCallback(
1398
2228
  async (id, label) => {
1399
2229
  try {
1400
2230
  const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials/${id}`, {
@@ -1419,7 +2249,7 @@ function useWebAuthnList(opts) {
1419
2249
  },
1420
2250
  [authOrigin, refetch]
1421
2251
  );
1422
- const remove = React4__namespace.useCallback(
2252
+ const remove = React3__namespace.useCallback(
1423
2253
  async (id) => {
1424
2254
  try {
1425
2255
  const r = await fetch(`${authOrigin}/api/auth/webauthn/credentials/${id}`, {
@@ -1447,13 +2277,13 @@ function useWebAuthnList(opts) {
1447
2277
  function useStepUpAuth(opts) {
1448
2278
  const authOrigin = resolveAuthOrigin(opts);
1449
2279
  const { refresh } = useOcSession();
1450
- const [status, setStatus] = React4__namespace.useState("idle");
1451
- const [error, setError] = React4__namespace.useState(null);
1452
- const reset = React4__namespace.useCallback(() => {
2280
+ const [status, setStatus] = React3__namespace.useState("idle");
2281
+ const [error, setError] = React3__namespace.useState(null);
2282
+ const reset = React3__namespace.useCallback(() => {
1453
2283
  setStatus("idle");
1454
2284
  setError(null);
1455
2285
  }, []);
1456
- const stepUp = React4__namespace.useCallback(
2286
+ const stepUp = React3__namespace.useCallback(
1457
2287
  async (args) => {
1458
2288
  setError(null);
1459
2289
  setStatus("requesting-options");
@@ -1542,6 +2372,7 @@ exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
1542
2372
  exports.OcAccountChip = OcAccountChip;
1543
2373
  exports.OcAccountPill = OcAccountPill;
1544
2374
  exports.OcAddressInput = OcAddressInput;
2375
+ exports.OcLinkedIdentities = OcLinkedIdentities;
1545
2376
  exports.OcSessionProvider = OcSessionProvider;
1546
2377
  exports.OcSignIn = OcSignIn;
1547
2378
  exports.OcSignInButton = OcSignInButton;