@orangecheck/auth-client 2.33.4 → 2.35.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/appearance.mjs +23 -0
- package/dist/appearance.mjs.map +1 -0
- package/dist/bond.mjs +20 -0
- package/dist/bond.mjs.map +1 -0
- package/dist/chunk-4E7GJ5IB.mjs +1184 -0
- package/dist/chunk-4E7GJ5IB.mjs.map +1 -0
- package/dist/chunk-7ST4Q5KR.mjs +346 -0
- package/dist/chunk-7ST4Q5KR.mjs.map +1 -0
- package/dist/chunk-7UYPMTAV.mjs +652 -0
- package/dist/chunk-7UYPMTAV.mjs.map +1 -0
- package/dist/chunk-BYAF2P2L.mjs +253 -0
- package/dist/chunk-BYAF2P2L.mjs.map +1 -0
- package/dist/chunk-CYR7J4UY.mjs +118 -0
- package/dist/chunk-CYR7J4UY.mjs.map +1 -0
- package/dist/chunk-H2TXOCXH.mjs +23 -0
- package/dist/chunk-H2TXOCXH.mjs.map +1 -0
- package/dist/chunk-HZ4FJMF3.mjs +490 -0
- package/dist/chunk-HZ4FJMF3.mjs.map +1 -0
- package/dist/chunk-TJJNTITX.mjs +187 -0
- package/dist/chunk-TJJNTITX.mjs.map +1 -0
- package/dist/chunk-UFRU3I3D.mjs +913 -0
- package/dist/chunk-UFRU3I3D.mjs.map +1 -0
- package/dist/chunk-VRLFZAGK.mjs +37 -0
- package/dist/chunk-VRLFZAGK.mjs.map +1 -0
- package/dist/chunk-XCZVSE7M.mjs +40 -0
- package/dist/chunk-XCZVSE7M.mjs.map +1 -0
- package/dist/components.mjs +19 -0
- package/dist/components.mjs.map +1 -0
- package/dist/index.js +77 -67
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +112 -4001
- package/dist/index.mjs.map +1 -1
- package/dist/linked-identities.mjs +41 -0
- package/dist/linked-identities.mjs.map +1 -0
- package/dist/provider.mjs +17 -0
- package/dist/provider.mjs.map +1 -0
- package/dist/return-to.mjs +18 -0
- package/dist/return-to.mjs.map +1 -0
- package/dist/signin.mjs +23 -0
- package/dist/signin.mjs.map +1 -0
- package/dist/sudo.mjs +16 -0
- package/dist/sudo.mjs.map +1 -0
- package/dist/tab-session.mjs +29 -0
- package/dist/tab-session.mjs.map +1 -0
- package/dist/types.mjs +17 -0
- package/dist/types.mjs.map +1 -0
- package/dist/webauthn.mjs +15 -0
- package/dist/webauthn.mjs.map +1 -0
- package/package.json +2 -1
- package/dist/index.d.mts +0 -303
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
// src/tab-session.ts
|
|
2
|
+
var TAB_SESSION_HEADER = "x-oc-tab-session";
|
|
3
|
+
var TAB_SESSION_STORAGE_KEY = "oc_tab_session";
|
|
4
|
+
var TAB_ACCOUNT_HINT_KEY = "oc-as";
|
|
5
|
+
var TAB_ADOPT_HASH = "#oc-adopt";
|
|
6
|
+
function readTabSession() {
|
|
7
|
+
if (typeof window === "undefined") return null;
|
|
8
|
+
try {
|
|
9
|
+
const raw = window.sessionStorage.getItem(TAB_SESSION_STORAGE_KEY);
|
|
10
|
+
if (!raw) return null;
|
|
11
|
+
const parsed = JSON.parse(raw);
|
|
12
|
+
if (typeof parsed.token !== "string" || parsed.token.length === 0) return null;
|
|
13
|
+
if (typeof parsed.didOc !== "string" || parsed.didOc.length === 0) return null;
|
|
14
|
+
return { token: parsed.token, didOc: parsed.didOc };
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
function writeTabSession(session) {
|
|
20
|
+
if (typeof window === "undefined") return;
|
|
21
|
+
try {
|
|
22
|
+
window.sessionStorage.setItem(TAB_SESSION_STORAGE_KEY, JSON.stringify(session));
|
|
23
|
+
} catch {
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function clearTabSession() {
|
|
27
|
+
if (typeof window === "undefined") return;
|
|
28
|
+
try {
|
|
29
|
+
window.sessionStorage.removeItem(TAB_SESSION_STORAGE_KEY);
|
|
30
|
+
} catch {
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function tabSessionHeader() {
|
|
34
|
+
const pin = readTabSession();
|
|
35
|
+
return pin ? { [TAB_SESSION_HEADER]: pin.token } : {};
|
|
36
|
+
}
|
|
37
|
+
function isPinnableUrl(url, authOrigin) {
|
|
38
|
+
if (typeof window === "undefined") return false;
|
|
39
|
+
try {
|
|
40
|
+
const u = new URL(url, window.location.href);
|
|
41
|
+
return u.origin === window.location.origin || u.origin === authOrigin;
|
|
42
|
+
} catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function installTabFetchInterceptor(authOrigin) {
|
|
47
|
+
if (typeof window === "undefined") return () => {
|
|
48
|
+
};
|
|
49
|
+
const original = window.fetch;
|
|
50
|
+
const wrapped = (input, init) => {
|
|
51
|
+
try {
|
|
52
|
+
const pin = readTabSession();
|
|
53
|
+
if (pin) {
|
|
54
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url;
|
|
55
|
+
if (isPinnableUrl(url, authOrigin)) {
|
|
56
|
+
const headers = new Headers(
|
|
57
|
+
init?.headers ?? (input instanceof Request ? input.headers : void 0)
|
|
58
|
+
);
|
|
59
|
+
if (!headers.has("authorization") && !headers.has(TAB_SESSION_HEADER)) {
|
|
60
|
+
headers.set(TAB_SESSION_HEADER, pin.token);
|
|
61
|
+
init = { ...init, headers };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
}
|
|
67
|
+
return original.call(window, input, init);
|
|
68
|
+
};
|
|
69
|
+
window.fetch = wrapped;
|
|
70
|
+
return () => {
|
|
71
|
+
if (window.fetch === wrapped) window.fetch = original;
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function consumeTabAdoptMarker() {
|
|
75
|
+
if (typeof window === "undefined") return false;
|
|
76
|
+
if (!window.location.hash.includes(TAB_ADOPT_HASH.slice(1))) return false;
|
|
77
|
+
clearTabSession();
|
|
78
|
+
try {
|
|
79
|
+
const url = new URL(window.location.href);
|
|
80
|
+
url.hash = "";
|
|
81
|
+
window.history.replaceState(window.history.state, "", url.toString());
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
var OC_AS_HINT_RE = /^oc-as=(did:oc:[0-9a-f]{32})$/;
|
|
87
|
+
function familyApex(authOrigin) {
|
|
88
|
+
try {
|
|
89
|
+
return new URL(authOrigin).hostname.toLowerCase();
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
function isFamilyUrl(u, authOrigin) {
|
|
95
|
+
if (u.protocol !== "http:" && u.protocol !== "https:") return false;
|
|
96
|
+
if (typeof window !== "undefined" && u.origin === window.location.origin) return true;
|
|
97
|
+
const apex = familyApex(authOrigin);
|
|
98
|
+
if (!apex) return false;
|
|
99
|
+
const host = u.hostname.toLowerCase();
|
|
100
|
+
return host === apex || host.endsWith(`.${apex}`);
|
|
101
|
+
}
|
|
102
|
+
function opensWithoutTabPin(e, anchor, dest) {
|
|
103
|
+
if (typeof window !== "undefined" && dest.origin !== window.location.origin) return true;
|
|
104
|
+
const target = (anchor.getAttribute("target") ?? "").toLowerCase();
|
|
105
|
+
return e.button === 1 || // middle-click
|
|
106
|
+
e.metaKey || e.ctrlKey || e.shiftKey || target === "_blank";
|
|
107
|
+
}
|
|
108
|
+
function installTabLinkDecorator(authOrigin) {
|
|
109
|
+
if (typeof window === "undefined" || typeof document === "undefined") return () => {
|
|
110
|
+
};
|
|
111
|
+
const onActivate = (e) => {
|
|
112
|
+
try {
|
|
113
|
+
const pin = readTabSession();
|
|
114
|
+
if (!pin) return;
|
|
115
|
+
const target = e.target;
|
|
116
|
+
if (!(target instanceof Element)) return;
|
|
117
|
+
const anchor = target.closest("a[href]");
|
|
118
|
+
if (!anchor || anchor.hasAttribute("download")) return;
|
|
119
|
+
const raw = anchor.getAttribute("href");
|
|
120
|
+
if (!raw || raw.includes(`${TAB_ACCOUNT_HINT_KEY}=`)) return;
|
|
121
|
+
let dest;
|
|
122
|
+
try {
|
|
123
|
+
dest = new URL(anchor.href);
|
|
124
|
+
} catch {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (dest.hash) return;
|
|
128
|
+
if (!isFamilyUrl(dest, authOrigin)) return;
|
|
129
|
+
if (!opensWithoutTabPin(e, anchor, dest)) return;
|
|
130
|
+
anchor.setAttribute("href", `${raw}#${TAB_ACCOUNT_HINT_KEY}=${pin.didOc}`);
|
|
131
|
+
} catch {
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
window.addEventListener("click", onActivate, true);
|
|
135
|
+
window.addEventListener("auxclick", onActivate, true);
|
|
136
|
+
return () => {
|
|
137
|
+
window.removeEventListener("click", onActivate, true);
|
|
138
|
+
window.removeEventListener("auxclick", onActivate, true);
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async function consumeTabAccountHint(authOrigin) {
|
|
142
|
+
if (typeof window === "undefined") return null;
|
|
143
|
+
const frag = window.location.hash.replace(/^#/, "");
|
|
144
|
+
const m = OC_AS_HINT_RE.exec(frag);
|
|
145
|
+
if (!m) return null;
|
|
146
|
+
const did = m[1];
|
|
147
|
+
try {
|
|
148
|
+
const url = new URL(window.location.href);
|
|
149
|
+
url.hash = "";
|
|
150
|
+
window.history.replaceState(window.history.state, "", url.toString());
|
|
151
|
+
} catch {
|
|
152
|
+
}
|
|
153
|
+
const existing = readTabSession();
|
|
154
|
+
if (existing && existing.didOc === did) return did;
|
|
155
|
+
try {
|
|
156
|
+
const res = await fetch(`${authOrigin}/api/auth/tab`, {
|
|
157
|
+
method: "POST",
|
|
158
|
+
credentials: "include",
|
|
159
|
+
headers: { Accept: "application/json", "Content-Type": "application/json" },
|
|
160
|
+
body: JSON.stringify({ did_oc: did })
|
|
161
|
+
});
|
|
162
|
+
if (!res.ok) return null;
|
|
163
|
+
const body = await res.json();
|
|
164
|
+
if (body.ok && typeof body.token === "string" && body.account?.did_oc === did) {
|
|
165
|
+
writeTabSession({ token: body.token, didOc: did });
|
|
166
|
+
return did;
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export {
|
|
174
|
+
TAB_SESSION_HEADER,
|
|
175
|
+
TAB_SESSION_STORAGE_KEY,
|
|
176
|
+
TAB_ACCOUNT_HINT_KEY,
|
|
177
|
+
TAB_ADOPT_HASH,
|
|
178
|
+
readTabSession,
|
|
179
|
+
writeTabSession,
|
|
180
|
+
clearTabSession,
|
|
181
|
+
tabSessionHeader,
|
|
182
|
+
installTabFetchInterceptor,
|
|
183
|
+
consumeTabAdoptMarker,
|
|
184
|
+
installTabLinkDecorator,
|
|
185
|
+
consumeTabAccountHint
|
|
186
|
+
};
|
|
187
|
+
//# sourceMappingURL=chunk-TJJNTITX.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/tab-session.ts"],"sourcesContent":["/**\n * Per-tab account pinning · sessionStorage-backed.\n *\n * One browser holds ONE shared `oc_session` cookie (the browser-wide\n * default account) but may be signed into N roster accounts at once. A\n * tab pins itself to one account by holding that account's session JWT\n * here — sessionStorage is per-tab by nature — and sending it on every\n * same-site API call as the `x-oc-tab-session` header. Servers verify\n * the header exactly like the cookie token (it IS a session JWT, a\n * credential, never a bare account selector), so switching accounts in\n * one tab no longer yanks the identity out from under every other tab.\n *\n * Trade-off, named: a pinned token is readable by same-origin script,\n * unlike the HttpOnly cookie. An XSS on a family site can already act\n * as the user via `credentials: 'include'`; the marginal exposure is\n * token exfiltration for offline use, bounded by the token's TTL and\n * the host's per-row revocation. The host has echoed session JWTs in\n * `/api/auth/signin` / `/api/auth/switch` response bodies since the\n * multi-account migration, so this channel is not new.\n */\n\n/** Header carrying the tab-pinned session JWT. Mirrors `@orangecheck/auth-core`'s `TAB_SESSION_HEADER`. */\nexport const TAB_SESSION_HEADER = 'x-oc-tab-session';\n\nexport const TAB_SESSION_STORAGE_KEY = 'oc_tab_session';\n\n/**\n * URL-fragment key the link decorator stamps onto family-origin\n * navigations so a NEW tab (or a cross-subdomain document GET) inherits\n * the OPENER tab's effective account instead of falling back to the\n * shared cookie's default account.\n *\n * The value is the account's `did:oc` — a PUBLIC identifier, never the\n * session JWT. It rides a fragment (`#oc-as=<did>`), which is never sent\n * to any server, and the host re-validates roster membership before\n * minting a tab token for it, so a hand-crafted link grants nothing the\n * visitor's own cookie/roster doesn't already authorize.\n */\nexport const TAB_ACCOUNT_HINT_KEY = 'oc-as';\n\n/**\n * Hash marker the auth host appends to a post-ceremony redirect so the\n * landing tab adopts the cookie account instead of keeping a stale pin\n * (the user just completed a sign-in/add ceremony and expects to BE the\n * new account in that tab). Never sent to any server (it's a fragment).\n */\nexport const TAB_ADOPT_HASH = '#oc-adopt';\n\nexport interface OcTabSession {\n token: string;\n didOc: string;\n}\n\nexport function readTabSession(): OcTabSession | null {\n if (typeof window === 'undefined') return null;\n try {\n const raw = window.sessionStorage.getItem(TAB_SESSION_STORAGE_KEY);\n if (!raw) return null;\n const parsed = JSON.parse(raw) as { token?: unknown; didOc?: unknown };\n if (typeof parsed.token !== 'string' || parsed.token.length === 0) return null;\n if (typeof parsed.didOc !== 'string' || parsed.didOc.length === 0) return null;\n return { token: parsed.token, didOc: parsed.didOc };\n } catch {\n // sessionStorage can throw (privacy modes) · malformed JSON → unpinned\n return null;\n }\n}\n\nexport function writeTabSession(session: OcTabSession): void {\n if (typeof window === 'undefined') return;\n try {\n window.sessionStorage.setItem(TAB_SESSION_STORAGE_KEY, JSON.stringify(session));\n } catch {\n // quota / privacy mode — degrade to unpinned (cookie-default) behavior\n }\n}\n\nexport function clearTabSession(): void {\n if (typeof window === 'undefined') return;\n try {\n window.sessionStorage.removeItem(TAB_SESSION_STORAGE_KEY);\n } catch {\n // nothing to clear\n }\n}\n\n/** `{ 'x-oc-tab-session': <jwt> }` when this tab is pinned, else `{}`. */\nexport function tabSessionHeader(): Record<string, string> {\n const pin = readTabSession();\n return pin ? { [TAB_SESSION_HEADER]: pin.token } : {};\n}\n\n/**\n * Should this request carry the tab pin? Same-origin requests (relative\n * URLs or the page's own origin) and requests to the auth host — never\n * third parties.\n */\nfunction isPinnableUrl(url: string, authOrigin: string): boolean {\n if (typeof window === 'undefined') return false;\n try {\n const u = new URL(url, window.location.href);\n return u.origin === window.location.origin || u.origin === authOrigin;\n } catch {\n return false;\n }\n}\n\n/**\n * Install a scoped `window.fetch` wrapper that attaches the tab pin to\n * every same-site request, so app-level data fetches (vault shares, me\n * wallet, projects, …) execute as the account this tab displays —\n * without touching any call site. Returns an uninstaller.\n *\n * Conservative by design:\n * - no pin → pass-through (zero behavior change)\n * - only same-origin + auth-host URLs (see {@link isPinnableUrl})\n * - never overrides an existing `Authorization` or `x-oc-tab-session`\n * header (programmatic Bearer / integrator-token paths keep their\n * own credential semantics)\n * - any internal error → original fetch, untouched\n */\nexport function installTabFetchInterceptor(authOrigin: string): () => void {\n if (typeof window === 'undefined') return () => {};\n const original = window.fetch;\n const wrapped: typeof window.fetch = (input, init) => {\n try {\n const pin = readTabSession();\n if (pin) {\n const url =\n typeof input === 'string'\n ? input\n : input instanceof URL\n ? input.href\n : input.url;\n if (isPinnableUrl(url, authOrigin)) {\n const headers = new Headers(\n init?.headers ?? (input instanceof Request ? input.headers : undefined)\n );\n if (!headers.has('authorization') && !headers.has(TAB_SESSION_HEADER)) {\n headers.set(TAB_SESSION_HEADER, pin.token);\n init = { ...init, headers };\n }\n }\n }\n } catch {\n // never break a fetch over pinning\n }\n return original.call(window, input as RequestInfo | URL, init);\n };\n window.fetch = wrapped;\n return () => {\n // only restore if nobody else wrapped on top of us in the meantime\n if (window.fetch === wrapped) window.fetch = original;\n };\n}\n\n/**\n * If the URL carries the {@link TAB_ADOPT_HASH} marker, clear the pin\n * (so the tab adopts the cookie account) and strip the marker from the\n * address bar. Returns `true` when adoption happened.\n */\nexport function consumeTabAdoptMarker(): boolean {\n if (typeof window === 'undefined') return false;\n if (!window.location.hash.includes(TAB_ADOPT_HASH.slice(1))) return false;\n clearTabSession();\n try {\n const url = new URL(window.location.href);\n url.hash = '';\n window.history.replaceState(window.history.state, '', url.toString());\n } catch {\n // cosmetic only — a lingering #oc-adopt re-adopts on reload, which\n // is idempotent (the pin is already cleared).\n }\n return true;\n}\n\n// ─── Cross-tab account inheritance (the new-tab pin handoff) ────────────\n//\n// The pin lives in sessionStorage, which a NEW tab does not inherit\n// cross-origin (and not at all on Safari). So a CTRL/⌘/middle-click on a\n// family link — or a same-tab navigation to a sibling subdomain — would\n// land unpinned and resolve to the shared cookie's DEFAULT account, i.e.\n// \"the other account.\" We bridge the gap WITHOUT putting a credential in\n// a URL: the opener tab stamps the destination link with the public\n// `did:oc` of its effective account (`#oc-as=<did>`), and the landing\n// tab mints its own pin for that account via the host (which re-checks\n// roster membership). The did is a selector, not a credential.\n\nconst OC_AS_HINT_RE = /^oc-as=(did:oc:[0-9a-f]{32})$/;\n\n/** Apex hostname of the auth host, e.g. `ochk.io`. Null if unparseable. */\nfunction familyApex(authOrigin: string): string | null {\n try {\n return new URL(authOrigin).hostname.toLowerCase();\n } catch {\n return null;\n }\n}\n\n/**\n * Is `u` a family destination worth stamping — current origin or any\n * host at/under the auth host's apex (so `stamp.ochk.io`, `ochk.io`,\n * etc. match, but `example.com` never does)? http(s) only — `mailto:`,\n * `tel:`, `javascript:`, `blob:` are excluded by construction.\n */\nfunction isFamilyUrl(u: URL, authOrigin: string): boolean {\n if (u.protocol !== 'http:' && u.protocol !== 'https:') return false;\n if (typeof window !== 'undefined' && u.origin === window.location.origin) return true;\n const apex = familyApex(authOrigin);\n if (!apex) return false;\n const host = u.hostname.toLowerCase();\n return host === apex || host.endsWith(`.${apex}`);\n}\n\n/** Would following this link land in a context that can't see this tab's pin? */\nfunction opensWithoutTabPin(e: MouseEvent, anchor: HTMLAnchorElement, dest: URL): boolean {\n // Cross-origin always loses the pin (sessionStorage is per-origin).\n if (typeof window !== 'undefined' && dest.origin !== window.location.origin) return true;\n // Same-origin: only a NEW tab/window risks losing it (Safari doesn't\n // clone sessionStorage); a same-tab navigation keeps it, so leave\n // those links untouched to avoid needless URL noise.\n const target = (anchor.getAttribute('target') ?? '').toLowerCase();\n return (\n e.button === 1 || // middle-click\n e.metaKey ||\n e.ctrlKey ||\n e.shiftKey ||\n target === '_blank'\n );\n}\n\n/**\n * Install a capture-phase listener that stamps the tab's effective\n * `did:oc` onto outgoing family-origin links the instant they're\n * activated, so a new/cross-subdomain tab adopts THIS tab's account.\n * Returns an uninstaller. Mirrors {@link installTabFetchInterceptor}'s\n * conservatism:\n * - no pin → never stamps (zero behavior change)\n * - family origins only → never leaks the did to third parties\n * - reads the pin FRESH per event (no install-time capture)\n * - never clobbers an existing `#fragment`, a download, or a\n * non-http(s) scheme\n * - any internal error → leaves the link alone\n *\n * It mutates `a.href` in place rather than calling `window.open`, so it\n * never trips a popup blocker and the browser's own ctrl/⌘/middle-click\n * handling (and the context menu's \"open in new tab\") all carry the\n * stamp. The stamp is idempotent and self-healing — React resets the\n * href on the next render, and the destination strips the fragment via\n * {@link consumeTabAccountHint}.\n */\nexport function installTabLinkDecorator(authOrigin: string): () => void {\n if (typeof window === 'undefined' || typeof document === 'undefined') return () => {};\n const onActivate = (e: MouseEvent) => {\n try {\n const pin = readTabSession();\n if (!pin) return;\n const target = e.target;\n if (!(target instanceof Element)) return;\n const anchor = target.closest('a[href]') as HTMLAnchorElement | null;\n if (!anchor || anchor.hasAttribute('download')) return;\n const raw = anchor.getAttribute('href');\n if (!raw || raw.includes(`${TAB_ACCOUNT_HINT_KEY}=`)) return;\n let dest: URL;\n try {\n dest = new URL(anchor.href);\n } catch {\n return;\n }\n if (dest.hash) return; // don't clobber a real #anchor deep-link\n if (!isFamilyUrl(dest, authOrigin)) return;\n if (!opensWithoutTabPin(e, anchor, dest)) return;\n anchor.setAttribute('href', `${raw}#${TAB_ACCOUNT_HINT_KEY}=${pin.didOc}`);\n } catch {\n // never break a navigation over decoration\n }\n };\n window.addEventListener('click', onActivate, true);\n window.addEventListener('auxclick', onActivate, true);\n return () => {\n window.removeEventListener('click', onActivate, true);\n window.removeEventListener('auxclick', onActivate, true);\n };\n}\n\n/**\n * On load, if the URL carries `#oc-as=<did>`, adopt that account for\n * THIS tab: mint a pin for it via the host's `/api/auth/tab` (which\n * re-validates the did is in this browser's roster) and stash it in\n * sessionStorage, then strip the fragment from the address bar. Must run\n * BEFORE the provider's first `/api/auth/me` fetch so that fetch carries\n * the right pin.\n *\n * Returns the adopted `did:oc` on success, else null. Best-effort and\n * fail-safe: a stale host (no targeted minting), a roster miss (403), or\n * a network error leaves the tab unpinned — exactly the legacy\n * cookie-following behavior. The fragment is always stripped so it never\n * lingers in history/bookmarks or re-fires on reload.\n */\nexport async function consumeTabAccountHint(authOrigin: string): Promise<string | null> {\n if (typeof window === 'undefined') return null;\n const frag = window.location.hash.replace(/^#/, '');\n const m = OC_AS_HINT_RE.exec(frag);\n if (!m) return null;\n const did = m[1]!;\n // Strip the fragment regardless of what happens next.\n try {\n const url = new URL(window.location.href);\n url.hash = '';\n window.history.replaceState(window.history.state, '', url.toString());\n } catch {\n // cosmetic only\n }\n // Already pinned to the hinted account (same-tab nav, or a same-origin\n // new tab that cloned sessionStorage) → nothing to mint.\n const existing = readTabSession();\n if (existing && existing.didOc === did) return did;\n try {\n const res = await fetch(`${authOrigin}/api/auth/tab`, {\n method: 'POST',\n credentials: 'include',\n headers: { Accept: 'application/json', 'Content-Type': 'application/json' },\n body: JSON.stringify({ did_oc: did }),\n });\n if (!res.ok) return null;\n const body = (await res.json()) as {\n ok?: boolean;\n token?: string;\n account?: { did_oc?: string };\n };\n // Only pin when the host minted for the hinted account — a stale\n // host that ignores the body mints for the cookie account, which\n // we must NOT adopt (it's the wrong-account bug we're fixing).\n if (body.ok && typeof body.token === 'string' && body.account?.did_oc === did) {\n writeTabSession({ token: body.token, didOc: did });\n return did;\n }\n } catch {\n // best-effort by construction\n }\n return null;\n}\n"],"mappings":"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","names":[]}
|