@orangecheck/auth-client 2.35.0 → 2.35.1

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  useOcSession
3
- } from "./chunk-7ST4Q5KR.mjs";
3
+ } from "./chunk-E2XQ7YED.mjs";
4
4
  import {
5
5
  resolveConfig
6
6
  } from "./chunk-VRLFZAGK.mjs";
@@ -250,4 +250,4 @@ export {
250
250
  useWebAuthnList,
251
251
  useStepUpAuth
252
252
  };
253
- //# sourceMappingURL=chunk-BYAF2P2L.mjs.map
253
+ //# sourceMappingURL=chunk-ASL3DS3Q.mjs.map
@@ -43,12 +43,24 @@ function normalizeRosterEntry(raw) {
43
43
  lastSeenAt: raw.last_seen_at ?? raw.lastSeenAt ?? null
44
44
  };
45
45
  }
46
+ var ME_TIMEOUT_MS = 1e4;
47
+ function timeoutSignal(ms) {
48
+ if (typeof AbortSignal.timeout === "function") return AbortSignal.timeout(ms);
49
+ const controller = new AbortController();
50
+ setTimeout(() => controller.abort(), ms);
51
+ return controller.signal;
52
+ }
53
+ function isTimeout(err) {
54
+ const name = err?.name;
55
+ return name === "TimeoutError" || name === "AbortError";
56
+ }
46
57
  async function fetchHostRoster(cfg) {
47
58
  try {
48
59
  const res = await fetch(`${cfg.authOrigin}${cfg.mePath}`, {
49
60
  method: "GET",
50
61
  credentials: "include",
51
- headers: { Accept: "application/json", ...tabSessionHeader() }
62
+ headers: { Accept: "application/json", ...tabSessionHeader() },
63
+ signal: timeoutSignal(ME_TIMEOUT_MS)
52
64
  });
53
65
  if (!res.ok) return [];
54
66
  const body = await res.json();
@@ -124,7 +136,8 @@ function OcSessionProvider({
124
136
  let res = await fetch(cfg.mePath, {
125
137
  method: "GET",
126
138
  credentials: "include",
127
- headers: { Accept: "application/json", ...tabSessionHeader() }
139
+ headers: { Accept: "application/json", ...tabSessionHeader() },
140
+ signal: timeoutSignal(ME_TIMEOUT_MS)
128
141
  });
129
142
  if (res.status === 401 && pin) {
130
143
  clearTabSession();
@@ -133,7 +146,8 @@ function OcSessionProvider({
133
146
  res = await fetch(cfg.mePath, {
134
147
  method: "GET",
135
148
  credentials: "include",
136
- headers: { Accept: "application/json" }
149
+ headers: { Accept: "application/json" },
150
+ signal: timeoutSignal(ME_TIMEOUT_MS)
137
151
  });
138
152
  }
139
153
  if (res.status === 401) {
@@ -168,7 +182,9 @@ function OcSessionProvider({
168
182
  }
169
183
  } catch (err) {
170
184
  setStatus("error");
171
- setError(err instanceof Error ? err : new Error(String(err)));
185
+ setError(
186
+ isTimeout(err) ? new Error(`me endpoint did not answer within ${ME_TIMEOUT_MS / 1e3}s`) : err instanceof Error ? err : new Error(String(err))
187
+ );
172
188
  }
173
189
  }, [cfg, pinThisTab]);
174
190
  React.useEffect(() => {
@@ -339,8 +355,9 @@ function useOptionalOcSession() {
339
355
  }
340
356
 
341
357
  export {
358
+ ME_TIMEOUT_MS,
342
359
  OcSessionProvider,
343
360
  useOcSession,
344
361
  useOptionalOcSession
345
362
  };
346
- //# sourceMappingURL=chunk-7ST4Q5KR.mjs.map
363
+ //# sourceMappingURL=chunk-E2XQ7YED.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provider.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport {\n buildAddAccountUrl,\n buildSignInUrl,\n DEFAULT_CONFIG,\n DISPLAY_IDENTITY_KINDS,\n resolveConfig,\n type DisplayIdentity,\n type DisplayIdentityKind,\n type OcAccount,\n type OcAccountSummary,\n type OcAuthConfig,\n type OcSessionState,\n type OcSignOutScope,\n} from './types';\nimport { keepFragmentOnReturnLinks, restoreReturnFragment } from './return-to';\nimport {\n clearTabSession,\n consumeTabAccountHint,\n consumeTabAdoptMarker,\n installTabFetchInterceptor,\n installTabLinkDecorator,\n readTabSession,\n tabSessionHeader,\n writeTabSession,\n} from './tab-session';\n\nconst SessionContext = React.createContext<OcSessionState | null>(null);\n\ntype RawSigningMethod = 'fedimint_threshold' | 'fedimint_client' | 'bip322';\n\ninterface RawRosterEntry {\n did_oc?: string;\n didOc?: string;\n display_name?: string | null;\n displayName?: string | null;\n primary_btc?: string | null;\n primaryBtc?: string | null;\n display_identity?: { kind?: string; value?: string } | null;\n displayIdentity?: { kind?: string; value?: string } | null;\n last_seen_at?: string | null;\n lastSeenAt?: string | null;\n}\n\ninterface MeResponse {\n account?: {\n id?: string;\n account_id?: string;\n accountId?: string;\n did_oc?: string;\n didOc?: string;\n primary_btc?: string | null;\n primaryBtc?: string | null;\n has_email?: boolean;\n hasEmail?: boolean;\n display_name?: string | null;\n displayName?: string | null;\n nostr_npub?: string | null;\n nostrNpub?: string | null;\n home_federation_slug?: string | null;\n homeFederation?: string | null;\n signing_method?: RawSigningMethod | null;\n signingMethod?: RawSigningMethod | null;\n is_owner?: boolean;\n isOwner?: boolean;\n display_identity?: { kind?: string; value?: string } | null;\n displayIdentity?: { kind?: string; value?: string } | null;\n };\n /** Multi-account roster · other accounts in this browser. Absent\n * on hosts that haven't deployed the multi-account migration yet\n * — treated as `[]` (single-account fallback). */\n roster?: RawRosterEntry[];\n}\n\n/**\n * Resolve the badge identity from a `/api/auth/me` account payload.\n * Total: returns the carried `{kind,value}` when well-formed, else\n * `{kind:'did',value:didOc}` — so accounts that never promoted, and\n * sessions minted before the field shipped, degrade cleanly.\n */\nfunction normalizeDisplayIdentity(\n raw: NonNullable<MeResponse['account']>,\n didOc: string\n): DisplayIdentity {\n const di = raw.display_identity ?? raw.displayIdentity;\n if (\n di &&\n typeof di === 'object' &&\n typeof di.value === 'string' &&\n di.value.length > 0 &&\n typeof di.kind === 'string' &&\n (DISPLAY_IDENTITY_KINDS as readonly string[]).includes(di.kind)\n ) {\n return { kind: di.kind as DisplayIdentityKind, value: di.value };\n }\n return { kind: 'did', value: didOc };\n}\n\n/**\n * Multi-account · normalize one roster entry from /api/auth/me. Drops\n * entries with no did_oc (defensive — the host shouldn't emit those).\n */\nfunction normalizeRosterEntry(raw: RawRosterEntry): OcAccountSummary | null {\n const didOc = raw.did_oc ?? raw.didOc;\n if (!didOc) return null;\n const di = raw.display_identity ?? raw.displayIdentity;\n const displayIdentity: DisplayIdentity =\n di &&\n typeof di === 'object' &&\n typeof di.value === 'string' &&\n di.value.length > 0 &&\n typeof di.kind === 'string' &&\n (DISPLAY_IDENTITY_KINDS as readonly string[]).includes(di.kind)\n ? { kind: di.kind as DisplayIdentityKind, value: di.value }\n : { kind: 'did', value: didOc };\n return {\n didOc,\n displayName: raw.display_name ?? raw.displayName ?? null,\n primaryBtc: raw.primary_btc ?? raw.primaryBtc ?? null,\n displayIdentity,\n lastSeenAt: raw.last_seen_at ?? raw.lastSeenAt ?? null,\n };\n}\n\n/**\n * Multi-account · fetch the roster from the AUTH HOST cross-origin.\n *\n * Consumer subdomains expose a LOCAL `/api/auth/me` that verifies the JWT\n * but has no database, so it never carries the `roster` (which is host DB\n * state). When the local /me comes back authenticated with an empty roster,\n * we source it from the host directly — the same credentialed cross-origin\n * pattern this provider already uses for logout + account PATCH, and the\n * host already serves `.ochk.io` consumers via CORS.\n *\n * Best-effort by construction: any failure resolves to `[]`, so a roster\n * fetch can never change the authenticated state derived from the local /me.\n */\n/** How long a session check may take before the page stops waiting on it. */\nexport const ME_TIMEOUT_MS = 10_000;\n\n/** `AbortSignal.timeout` where the browser has it, a timer where it does not. */\nfunction timeoutSignal(ms: number): AbortSignal {\n if (typeof AbortSignal.timeout === 'function') return AbortSignal.timeout(ms);\n const controller = new AbortController();\n setTimeout(() => controller.abort(), ms);\n return controller.signal;\n}\n\nfunction isTimeout(err: unknown): boolean {\n const name = (err as { name?: unknown } | null)?.name;\n return name === 'TimeoutError' || name === 'AbortError';\n}\n\nasync function fetchHostRoster(cfg: Required<OcAuthConfig>): Promise<OcAccountSummary[]> {\n try {\n // Per-tab · carry the pin so the host computes \"peers\" relative\n // to THIS tab's effective account, not the cookie default.\n const res = await fetch(`${cfg.authOrigin}${cfg.mePath}`, {\n method: 'GET',\n credentials: 'include',\n headers: { Accept: 'application/json', ...tabSessionHeader() },\n signal: timeoutSignal(ME_TIMEOUT_MS),\n });\n if (!res.ok) return [];\n const body = (await res.json()) as MeResponse;\n return Array.isArray(body.roster)\n ? body.roster\n .map(normalizeRosterEntry)\n .filter((r): r is OcAccountSummary => r !== null)\n : [];\n } catch {\n return [];\n }\n}\n\nfunction normalizeAccount(raw: MeResponse['account']): OcAccount | null {\n if (!raw) return null;\n const didOc = raw.did_oc ?? raw.didOc;\n const accountId = raw.id ?? raw.account_id ?? raw.accountId;\n if (!didOc || !accountId) return null;\n return {\n accountId,\n didOc,\n primaryBtc: raw.primary_btc ?? raw.primaryBtc ?? null,\n hasEmail: raw.has_email ?? raw.hasEmail ?? false,\n displayName: raw.display_name ?? raw.displayName ?? null,\n nostrNpub: raw.nostr_npub ?? raw.nostrNpub ?? null,\n homeFederation: raw.home_federation_slug ?? raw.homeFederation ?? null,\n signingMethod: raw.signing_method ?? raw.signingMethod ?? null,\n isOwner: Boolean(raw.is_owner ?? raw.isOwner ?? false),\n displayIdentity: normalizeDisplayIdentity(raw, didOc),\n };\n}\n\nexport interface OcSessionProviderProps {\n children: React.ReactNode;\n config?: OcAuthConfig;\n /**\n * Optional return URL passed to the sign-in page. Defaults to the\n * current page. Its fragment is never sent: it is kept in this tab and\n * restored on return (./return-to).\n */\n defaultReturnTo?: string;\n}\n\n/**\n * Top-level provider that exposes the cross-subdomain oc_session to every\n * component below it. Mount once, near the root of your tree.\n */\nexport function OcSessionProvider({\n children,\n config,\n defaultReturnTo,\n}: OcSessionProviderProps): React.ReactElement {\n const cfg = React.useMemo(() => resolveConfig(config), [config]);\n // Back from a sign-in or a check: put back the fragment its return\n // address left in this tab, before any page below reads the address.\n // The adoption marker the auth host appends goes first, so the fragment\n // takes its place. Children's effects run before this provider's, so\n // this happens during the first render, once; both calls are idempotent.\n React.useState(() => {\n if (typeof window !== 'undefined') {\n consumeTabAdoptMarker();\n restoreReturnFragment();\n }\n return null;\n });\n const [account, setAccount] = React.useState<OcAccount | null>(null);\n const [roster, setRoster] = React.useState<OcAccountSummary[]>([]);\n const [status, setStatus] = React.useState<OcSessionState['status']>('loading');\n const [error, setError] = React.useState<Error | null>(null);\n const [tabPinned, setTabPinned] = React.useState(false);\n\n // Per-tab · mint a pin for this tab via the host. Called once per\n // tab on first authenticated resolve, so a later switch in ANOTHER\n // tab can't yank this one — it keeps presenting its own token.\n // Best-effort: a host that predates `/api/auth/tab` (or a network\n // failure) leaves the tab unpinned, which is exactly the legacy\n // cookie-following behavior.\n const pinInFlightRef = React.useRef(false);\n const pinThisTab = React.useCallback(\n async (didOc: string) => {\n if (!cfg.multiAccount || pinInFlightRef.current || readTabSession()) return;\n pinInFlightRef.current = true;\n try {\n const res = await fetch(`${cfg.authOrigin}/api/auth/tab`, {\n method: 'POST',\n credentials: 'include',\n headers: { Accept: 'application/json' },\n });\n if (!res.ok) return;\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 account this tab\n // just resolved — a cookie flip racing between our /me and\n // /tab calls must not pin the WRONG account.\n if (\n body.ok &&\n typeof body.token === 'string' &&\n body.account?.did_oc === didOc\n ) {\n writeTabSession({ token: body.token, didOc });\n setTabPinned(true);\n }\n } catch {\n // best-effort by construction\n } finally {\n pinInFlightRef.current = false;\n }\n },\n [cfg.authOrigin, cfg.multiAccount]\n );\n\n const refresh = React.useCallback(async () => {\n if (typeof window === 'undefined') return;\n try {\n let pin = readTabSession();\n // A check that never answers would leave the page loading for good;\n // past the timeout it is an error, which every site already shows.\n let res = await fetch(cfg.mePath, {\n method: 'GET',\n credentials: 'include',\n headers: { Accept: 'application/json', ...tabSessionHeader() },\n signal: timeoutSignal(ME_TIMEOUT_MS),\n });\n // Per-tab · 401 with a pin present means the pinned token is\n // dead (expired, or revoked and rejected by the host). Drop\n // the pin and retry once as the browser-default account.\n if (res.status === 401 && pin) {\n clearTabSession();\n pin = null;\n setTabPinned(false);\n res = await fetch(cfg.mePath, {\n method: 'GET',\n credentials: 'include',\n headers: { Accept: 'application/json' },\n signal: timeoutSignal(ME_TIMEOUT_MS),\n });\n }\n if (res.status === 401) {\n setAccount(null);\n setRoster([]);\n setStatus('anonymous');\n setError(null);\n return;\n }\n if (!res.ok) {\n setStatus('error');\n setError(new Error(`me endpoint returned ${res.status}`));\n return;\n }\n const body = (await res.json()) as MeResponse;\n const acct = normalizeAccount(body.account);\n // Per-tab reconciliation · the server answered as a different\n // account than the pin → it doesn't honor `x-oc-tab-session`\n // yet (pre-migration deploy). Drop the pin: the UI must never\n // display an account the server isn't acting as.\n if (acct && pin && acct.didOc !== pin.didOc) {\n clearTabSession();\n pin = null;\n }\n setTabPinned(pin !== null);\n const rosterEntries = Array.isArray(body.roster)\n ? body.roster\n .map(normalizeRosterEntry)\n .filter((r): r is OcAccountSummary => r !== null)\n : [];\n setAccount(acct);\n setRoster(rosterEntries);\n setStatus(acct ? 'authenticated' : 'anonymous');\n setError(null);\n\n // Per-tab · authenticated but unpinned → pin this tab to the\n // account it just resolved (fire-and-forget).\n if (acct && !pin) void pinThisTab(acct.didOc);\n\n // Multi-account · the local /me carried no roster (the standard\n // case on consumer subdomains, whose local /me has no DB). When\n // we're authenticated and NOT already on the auth host, source\n // the roster from the host so the §accounts switcher lights up.\n // Best-effort: never touches account/status on failure.\n const currentOrigin =\n typeof window !== 'undefined' ? window.location.origin : null;\n if (\n cfg.multiAccount &&\n acct &&\n rosterEntries.length === 0 &&\n currentOrigin !== null &&\n !cfg.authOrigin.startsWith(currentOrigin)\n ) {\n const peers = await fetchHostRoster(cfg);\n if (peers.length > 0) setRoster(peers);\n }\n } catch (err) {\n setStatus('error');\n setError(\n isTimeout(err)\n ? new Error(`me endpoint did not answer within ${ME_TIMEOUT_MS / 1000}s`)\n : err instanceof Error\n ? err\n : new Error(String(err))\n );\n }\n }, [cfg, pinThisTab]);\n\n React.useEffect(() => {\n let cancelled = false;\n // Post-ceremony adoption · the auth host appends `#oc-adopt` when\n // returning from a sign-in/add ceremony: the user expects THIS tab\n // to become the account they just authenticated, so a stale pin\n // must not survive the round trip.\n consumeTabAdoptMarker();\n void (async () => {\n // New-tab inheritance · if the opener stamped `#oc-as=<did>`,\n // adopt that account BEFORE the first /me fetch so this tab\n // resolves as the opener's account, not the cookie default.\n if (cfg.multiAccount) await consumeTabAccountHint(cfg.authOrigin);\n if (!cancelled) void refresh();\n })();\n return () => {\n cancelled = true;\n };\n }, [refresh, cfg.authOrigin, cfg.multiAccount]);\n\n // A link that brings the browser back here keeps this page's fragment\n // in the tab, since its return_to cannot carry it.\n React.useEffect(() => keepFragmentOnReturnLinks(), []);\n\n // Per-tab · attach the pin to every same-site fetch so app-level data\n // calls execute as the account this tab displays. Scoped + reversible;\n // see installTabFetchInterceptor for the conservative rules.\n React.useEffect(\n () => (cfg.multiAccount ? installTabFetchInterceptor(cfg.authOrigin) : undefined),\n [cfg.authOrigin, cfg.multiAccount]\n );\n\n // Per-tab · stamp this tab's effective account onto outgoing\n // family-origin links so a new/cross-subdomain tab inherits it\n // instead of the shared cookie default. See installTabLinkDecorator.\n React.useEffect(\n () => (cfg.multiAccount ? installTabLinkDecorator(cfg.authOrigin) : undefined),\n [cfg.authOrigin, cfg.multiAccount]\n );\n\n const signOut = React.useCallback(\n async (opts?: { scope?: OcSignOutScope }) => {\n const scope: OcSignOutScope = opts?.scope ?? 'all';\n try {\n const url = new URL(`${cfg.authOrigin}${cfg.logoutPath}`);\n if (scope === 'current') url.searchParams.set('scope', 'current');\n const res = await fetch(url.toString(), {\n method: 'POST',\n credentials: 'include',\n // Per-tab · carry the pin so scope='current' signs out\n // the account THIS tab is operating as, not whichever\n // account the shared cookie happens to point at.\n headers: { ...tabSessionHeader() },\n // `keepalive` lets the logout round-trip complete even if\n // the caller hard-navigates away in the same tick (e.g.\n // `<OcAccountMenu>` redirects home immediately on sign-out).\n // Without it the in-flight request is cancelled on unload\n // and the `.ochk.io` cookie may never get cleared.\n keepalive: true,\n });\n // The signed-out account's token is dead either way — the\n // pin must not outlive it.\n clearTabSession();\n setTabPinned(false);\n // Multi-account: with scope='current', the server may have\n // switched the active session to a roster peer instead of\n // clearing the cookie. Re-fetch /api/auth/me so this\n // provider reflects the new active account; if the body\n // says `switched_to !== null`, refresh resolves to the\n // peer, otherwise it resolves to anonymous and the\n // setState below is redundant-but-harmless.\n if (scope === 'current' && res.ok) {\n await refresh();\n return;\n }\n } catch {\n // fall through — we still clear local state so the UI reflects\n // the user's intent even if the server round-trip fails.\n clearTabSession();\n setTabPinned(false);\n }\n setAccount(null);\n setRoster([]);\n setStatus('anonymous');\n },\n [cfg.authOrigin, cfg.logoutPath, refresh]\n );\n\n const switchAccount = React.useCallback(\n async (didOc: string) => {\n if (typeof window === 'undefined') return;\n const res = await fetch(`${cfg.authOrigin}/api/auth/switch`, {\n method: 'POST',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json', ...tabSessionHeader() },\n body: JSON.stringify({ did_oc: didOc }),\n });\n if (!res.ok) {\n let reason = `http_${res.status}`;\n try {\n const body = (await res.json()) as { reason?: string };\n if (body.reason) reason = body.reason;\n } catch {\n // keep http_ fallback\n }\n throw new Error(`[@orangecheck/auth-client] switchAccount failed: ${reason}`);\n }\n // Per-tab · pin THIS tab to the switch target using the fresh\n // JWT the host echoes in the body. The host has also flipped\n // the shared cookie — the default for FUTURE tabs — but other\n // open tabs keep their own pins and are not yanked. Pin to the\n // did the SERVER minted for (merge-graph collapse may differ\n // from the requested did).\n try {\n const body = (await res.json()) as {\n token?: string;\n account?: { did_oc?: string };\n };\n if (typeof body.token === 'string' && body.account?.did_oc) {\n writeTabSession({ token: body.token, didOc: body.account.did_oc });\n setTabPinned(true);\n }\n } catch {\n // body unreadable — cookie still flipped; refresh resolves it\n }\n // Re-fetch /me to surface the new active account + freshly\n // computed roster (the previous active account is now a peer,\n // swap is symmetric).\n await refresh();\n },\n [cfg.authOrigin, refresh]\n );\n\n const addAccountUrl = React.useCallback(\n (returnTo?: string) => {\n const rt =\n returnTo ?? (typeof window !== 'undefined' ? window.location.href : undefined);\n return buildAddAccountUrl(cfg, rt);\n },\n [cfg]\n );\n\n const setDisplayIdentity = React.useCallback(\n async (kind: DisplayIdentityKind) => {\n if (typeof window === 'undefined') return;\n // PATCH the auth host directly (family-CORS). It writes the\n // pref, re-mints the `.ochk.io` session cookie with the new\n // `display_identity` claim, and Set-Cookies it back — so the\n // choice propagates to every subdomain. Then refresh so this\n // session reflects it immediately.\n const res = await fetch(`${cfg.authOrigin}/api/auth/account`, {\n method: 'PATCH',\n credentials: 'include',\n headers: { 'Content-Type': 'application/json', ...tabSessionHeader() },\n body: JSON.stringify({ display_identity: kind }),\n });\n if (!res.ok) {\n let reason = `http_${res.status}`;\n try {\n const body = (await res.json()) as { reason?: string };\n if (body.reason) reason = body.reason;\n } catch {\n // keep the http_ fallback\n }\n throw new Error(`[@orangecheck/auth-client] setDisplayIdentity failed: ${reason}`);\n }\n // Per-tab · the PATCH re-mints; when this tab is pinned the\n // host echoes the fresh token in the body (it only rewrites\n // the shared cookie when the cookie account was the actor).\n // Re-pin so the tab carries the updated claims.\n try {\n const body = (await res.json()) as {\n token?: string;\n account?: { did_oc?: string };\n };\n const pin = readTabSession();\n if (\n pin &&\n typeof body.token === 'string' &&\n body.account?.did_oc === pin.didOc\n ) {\n writeTabSession({ token: body.token, didOc: pin.didOc });\n }\n } catch {\n // body unreadable — refresh below still reconciles state\n }\n await refresh();\n },\n [cfg.authOrigin, refresh]\n );\n\n const value = React.useMemo<OcSessionState>(() => {\n const returnTo =\n defaultReturnTo ?? (typeof window !== 'undefined' ? window.location.href : undefined);\n return {\n status,\n account,\n roster,\n tabPinned,\n error,\n refresh,\n signOut,\n switchAccount,\n addAccountUrl,\n setDisplayIdentity,\n signInUrl: buildSignInUrl(cfg, returnTo),\n };\n }, [\n status,\n account,\n roster,\n tabPinned,\n error,\n refresh,\n signOut,\n switchAccount,\n addAccountUrl,\n setDisplayIdentity,\n cfg,\n defaultReturnTo,\n ]);\n\n return <SessionContext.Provider value={value}>{children}</SessionContext.Provider>;\n}\n\n/**\n * Access the current cross-subdomain oc_session. Must be called inside\n * an `<OcSessionProvider>`.\n */\nexport function useOcSession(): OcSessionState {\n const ctx = React.useContext(SessionContext);\n if (!ctx) {\n throw new Error(\n '[@orangecheck/auth-client] useOcSession() must be called inside <OcSessionProvider>'\n );\n }\n return ctx;\n}\n\n/**\n * Non-throwing variant — returns `null` if called outside a provider.\n * Useful for libraries that want to read the session *if it exists* but\n * shouldn't crash on apps that haven't opted in.\n */\nexport function useOptionalOcSession(): OcSessionState | null {\n return React.useContext(SessionContext);\n}\n\nexport { DEFAULT_CONFIG };\n"],"mappings":"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","names":[]}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  useOcSession
3
- } from "./chunk-7ST4Q5KR.mjs";
3
+ } from "./chunk-E2XQ7YED.mjs";
4
4
 
5
5
  // src/components.tsx
6
6
  import * as React from "react";
@@ -649,4 +649,4 @@ export {
649
649
  useOcAddressSuggestion,
650
650
  OcAddressInput
651
651
  };
652
- //# sourceMappingURL=chunk-7UYPMTAV.mjs.map
652
+ //# sourceMappingURL=chunk-EZKAMTG7.mjs.map
@@ -4,8 +4,8 @@ import {
4
4
  OcAddressInput,
5
5
  OcSignInButton,
6
6
  useOcAddressSuggestion
7
- } from "./chunk-7UYPMTAV.mjs";
8
- import "./chunk-7ST4Q5KR.mjs";
7
+ } from "./chunk-EZKAMTG7.mjs";
8
+ import "./chunk-E2XQ7YED.mjs";
9
9
  import "./chunk-VRLFZAGK.mjs";
10
10
  import "./chunk-CYR7J4UY.mjs";
11
11
  import "./chunk-TJJNTITX.mjs";
package/dist/index.js CHANGED
@@ -348,12 +348,24 @@ function normalizeRosterEntry(raw) {
348
348
  lastSeenAt: raw.last_seen_at ?? raw.lastSeenAt ?? null
349
349
  };
350
350
  }
351
+ var ME_TIMEOUT_MS = 1e4;
352
+ function timeoutSignal(ms) {
353
+ if (typeof AbortSignal.timeout === "function") return AbortSignal.timeout(ms);
354
+ const controller = new AbortController();
355
+ setTimeout(() => controller.abort(), ms);
356
+ return controller.signal;
357
+ }
358
+ function isTimeout(err) {
359
+ const name = err?.name;
360
+ return name === "TimeoutError" || name === "AbortError";
361
+ }
351
362
  async function fetchHostRoster(cfg) {
352
363
  try {
353
364
  const res = await fetch(`${cfg.authOrigin}${cfg.mePath}`, {
354
365
  method: "GET",
355
366
  credentials: "include",
356
- headers: { Accept: "application/json", ...tabSessionHeader() }
367
+ headers: { Accept: "application/json", ...tabSessionHeader() },
368
+ signal: timeoutSignal(ME_TIMEOUT_MS)
357
369
  });
358
370
  if (!res.ok) return [];
359
371
  const body = await res.json();
@@ -429,7 +441,8 @@ function OcSessionProvider({
429
441
  let res = await fetch(cfg.mePath, {
430
442
  method: "GET",
431
443
  credentials: "include",
432
- headers: { Accept: "application/json", ...tabSessionHeader() }
444
+ headers: { Accept: "application/json", ...tabSessionHeader() },
445
+ signal: timeoutSignal(ME_TIMEOUT_MS)
433
446
  });
434
447
  if (res.status === 401 && pin) {
435
448
  clearTabSession();
@@ -438,7 +451,8 @@ function OcSessionProvider({
438
451
  res = await fetch(cfg.mePath, {
439
452
  method: "GET",
440
453
  credentials: "include",
441
- headers: { Accept: "application/json" }
454
+ headers: { Accept: "application/json" },
455
+ signal: timeoutSignal(ME_TIMEOUT_MS)
442
456
  });
443
457
  }
444
458
  if (res.status === 401) {
@@ -473,7 +487,9 @@ function OcSessionProvider({
473
487
  }
474
488
  } catch (err) {
475
489
  setStatus("error");
476
- setError(err instanceof Error ? err : new Error(String(err)));
490
+ setError(
491
+ isTimeout(err) ? new Error(`me endpoint did not answer within ${ME_TIMEOUT_MS / 1e3}s`) : err instanceof Error ? err : new Error(String(err))
492
+ );
477
493
  }
478
494
  }, [cfg, pinThisTab]);
479
495
  React4__namespace.useEffect(() => {