@burdenoff/microfe-auth 2026.718.1 → 2026.718.3

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,45 +1,42 @@
1
- import { useEffect as e } from "react";
2
- import { useSearchParams as t } from "react-router-dom";
3
- import { jsx as n } from "react/jsx-runtime";
1
+ import { isAllowedSsoOrigin as e } from "../utils/ssoOrigins.js";
2
+ import { useEffect as t } from "react";
3
+ import { useSearchParams as n } from "react-router-dom";
4
+ import { jsx as r } from "react/jsx-runtime";
5
+ import { mintSsoSessionTokens as i } from "@burdenoff/fe-libs/shared/utils";
6
+ import { resolveGatewayUrls as a } from "@burdenoff/fe-libs/shared/config/gatewayUrls";
4
7
  //#region src/auth/pages/SSORelayPage.tsx
5
- var r = new Set(/* @__PURE__ */ "burdenoff.com,vibecontrols.com,fluidgrids.com,botlit.ai,bigconsole.com,gethealthybowl.com,headshotmarketing.com,timecampus.com,ggnomad.com,movethewheels.com,crewfoundry.com,planmagnet.com,eventfullymanaged.com,kadaikodi.com,assethandler.com,technospam.com,semanticfed.com,manufacturedops.com,theglobalfuel.com,housingvista.com,proserviceworld.com,mybodyshield.com,artistrybase.com,collabkin.com,brainyrich.com,theculturepost.com,comradecircle.com,cosmicintersection.com,govcitizenhub.com,intelwatchtower.com,ecoimpacthub.com,buildmyiq.com,fluidgrids.ai,atheletebridge.com,adaptercloud.com,coolandlovely.com,subscriberbot.com,labsofscience.com,hookmovies.com".split(",")), i = "relayChain", a = "sso_failed", o = new Set(["localhost", "127.0.0.1"]), s = {
8
+ var o = "relayChain", s = "sso_failed", c = new Set(["localhost", "127.0.0.1"]), l = {
6
9
  "bf-sso": /(?:^|;\s*)bf-sso=([^;]*)/,
7
10
  "bf-sso-profile": /(?:^|;\s*)bf-sso-profile=([^;]*)/
8
11
  };
9
- function c(e) {
10
- if (e.hostname === "localhost" || e.hostname === "127.0.0.1" || e.hostname.endsWith(".local.burdenoff.com")) return !0;
11
- if (e.protocol !== "https:") return !1;
12
- for (let t of r) if (e.hostname === t || e.hostname === `app.${t}` || e.hostname === `alphaapp.${t}` || e.hostname === `admin.${t}` || e.hostname === `alphaadmin.${t}` || e.hostname === `www.${t}`) return !0;
13
- return !1;
14
- }
15
- function l() {
12
+ function u() {
16
13
  try {
17
14
  let e = window.location.hostname, t = e.endsWith(".local.burdenoff.com") ? ".local.burdenoff.com" : void 0;
18
- for (let n of ["bf-sso", "bf-sso-profile"]) document.cookie = `${n}=; path=/; max-age=0`, t && (document.cookie = `${n}=; path=/; domain=${t}; max-age=0`), e && !o.has(e) && (document.cookie = `${n}=; path=/; domain=${e}; max-age=0`);
15
+ for (let n of ["bf-sso", "bf-sso-profile"]) document.cookie = `${n}=; path=/; max-age=0`, t && (document.cookie = `${n}=; path=/; domain=${t}; max-age=0`), e && !c.has(e) && (document.cookie = `${n}=; path=/; domain=${e}; max-age=0`);
19
16
  } catch {}
20
17
  }
21
- function u() {
18
+ function d() {
22
19
  try {
23
- return (new URLSearchParams(window.location.search).get(i) || "").split("|").filter((e) => e.length > 0);
20
+ return (new URLSearchParams(window.location.search).get(o) || "").split("|").filter((e) => e.length > 0);
24
21
  } catch {
25
22
  return [];
26
23
  }
27
24
  }
28
- function d(e) {
25
+ function f(t) {
29
26
  try {
30
- let t = new URL(e, window.location.origin);
31
- return t.origin !== window.location.origin && !c(t) ? "/auth/login" : (t.searchParams.set(a, "1"), t.href);
27
+ let n = new URL(t, window.location.origin);
28
+ return n.origin !== window.location.origin && !e(n) ? "/auth/login" : (n.searchParams.set(s, "1"), n.href);
32
29
  } catch {
33
30
  return "/auth/login?sso_failed=1";
34
31
  }
35
32
  }
36
- function f() {
37
- return p()?.id ?? null;
38
- }
39
33
  function p() {
34
+ return m()?.id ?? null;
35
+ }
36
+ function m() {
40
37
  try {
41
38
  for (let e of ["bf-sso", "bf-sso-profile"]) {
42
- let t = document.cookie.match(s[e]);
39
+ let t = document.cookie.match(l[e]);
43
40
  if (!t) continue;
44
41
  let n = JSON.parse(decodeURIComponent(t[1]));
45
42
  if (typeof n == "string") return {
@@ -56,36 +53,36 @@ function p() {
56
53
  }
57
54
  return null;
58
55
  }
59
- function m(e) {
56
+ function h(t) {
60
57
  try {
61
- let t = p();
62
- if (!t?.origin) return !1;
63
- let n = new URL(t.origin);
64
- if (!c(n) || n.origin === window.location.origin) return !1;
65
- let r = u();
66
- if (r.includes(n.origin) || r.length >= 3) return !1;
67
- let a = new URL("/auth/sso-relay", n.origin);
68
- return a.searchParams.set("returnTo", e), a.searchParams.set(i, Array.from(new Set([...r, window.location.origin])).join("|")), window.location.replace(a.href), !0;
58
+ let n = m();
59
+ if (!n?.origin) return !1;
60
+ let r = new URL(n.origin);
61
+ if (!e(r) || r.origin === window.location.origin) return !1;
62
+ let i = d();
63
+ if (i.includes(r.origin) || i.length >= 3) return !1;
64
+ let a = new URL("/auth/sso-relay", r.origin);
65
+ return a.searchParams.set("returnTo", t), a.searchParams.set(o, Array.from(new Set([...i, window.location.origin])).join("|")), window.location.replace(a.href), !0;
69
66
  } catch {
70
67
  return !1;
71
68
  }
72
69
  }
73
- function h() {
70
+ function g() {
74
71
  return localStorage.getItem("bf-profiles");
75
72
  }
76
- function g() {
73
+ function _() {
77
74
  try {
78
- let e = h();
75
+ let e = g();
79
76
  return (e ? JSON.parse(e) : []).map((e) => e.id).filter((e) => typeof e == "string" && e.length > 0);
80
77
  } catch {
81
78
  return [];
82
79
  }
83
80
  }
84
- function _() {
81
+ function v() {
85
82
  let e = [
86
- f(),
83
+ p(),
87
84
  sessionStorage.getItem("bf-active-profile"),
88
- ...g()
85
+ ..._()
89
86
  ].filter((e) => typeof e == "string" && e.length > 0);
90
87
  for (let t of Array.from(new Set(e))) {
91
88
  let e = localStorage.getItem(`bf-p-${t}-tokens`), n = localStorage.getItem(`bf-p-${t}-user`);
@@ -93,51 +90,84 @@ function _() {
93
90
  }
94
91
  return null;
95
92
  }
96
- function v() {
97
- let [r] = t();
98
- return e(() => {
99
- let e = r.get("returnTo");
100
- if (!e) {
101
- window.location.replace("/");
102
- return;
103
- }
104
- let t = _();
105
- if (!t) {
106
- if (m(e)) return;
107
- l(), window.location.replace(d(e));
108
- return;
109
- }
110
- let n = localStorage.getItem(`bf-p-${t}-tokens`), i = localStorage.getItem(`bf-p-${t}-user`), a = h();
111
- if (!n || !i) {
112
- if (m(e)) return;
113
- l(), window.location.replace(d(e));
114
- return;
115
- }
116
- let o = btoa(JSON.stringify({
117
- profileId: t,
118
- tokens: n,
119
- user: i,
120
- profiles: a
121
- })), s;
122
- try {
123
- let t = new URL(e);
124
- if (!c(t)) {
93
+ function y(e) {
94
+ try {
95
+ localStorage.removeItem(`bf-p-${e}-tokens`), localStorage.removeItem(`bf-p-${e}-user`), localStorage.removeItem(`bf-p-${e}-workspace-token`), localStorage.removeItem(`bf-p-${e}-auth-storage`), sessionStorage.removeItem("bf-active-profile");
96
+ } catch {}
97
+ }
98
+ function b(e) {
99
+ try {
100
+ return JSON.parse(e);
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+ function x() {
106
+ let [o] = n();
107
+ return t(() => {
108
+ let t = !1;
109
+ return (async () => {
110
+ let n = o.get("returnTo");
111
+ if (!n) {
125
112
  window.location.replace("/");
126
113
  return;
127
114
  }
128
- let n = t.pathname + t.search;
129
- s = `${t.origin}/auth/sso-callback?returnTo=${encodeURIComponent(n)}#sso=${o}`;
130
- } catch {
131
- window.location.replace(e);
132
- return;
133
- }
134
- window.location.replace(s);
135
- }, [r]), /* @__PURE__ */ n("div", {
115
+ let r = v();
116
+ if (!r) {
117
+ if (h(n)) return;
118
+ u(), window.location.replace(f(n));
119
+ return;
120
+ }
121
+ let s = localStorage.getItem(`bf-p-${r}-tokens`), c = localStorage.getItem(`bf-p-${r}-user`), l = g();
122
+ if (!s || !c) {
123
+ if (h(n)) return;
124
+ u(), window.location.replace(f(n));
125
+ return;
126
+ }
127
+ let d;
128
+ try {
129
+ d = new URL(n);
130
+ } catch {
131
+ window.location.replace(n);
132
+ return;
133
+ }
134
+ if (!e(d)) {
135
+ window.location.replace("/");
136
+ return;
137
+ }
138
+ let p = b(s), m = null;
139
+ if (p?.refreshToken) {
140
+ let { globalBaseUrl: e } = a(), { tokens: o, denied: s } = await i(`${e}/global/graphql`, p.refreshToken);
141
+ if (t) return;
142
+ if (o) m = JSON.stringify(o);
143
+ else if (s) {
144
+ if (y(r), h(n)) return;
145
+ u(), window.location.replace(f(n));
146
+ return;
147
+ }
148
+ }
149
+ if (!m) if (p?.accessToken && typeof p.expiresAt == "number" && p.expiresAt - Date.now() > 6e4) m = s;
150
+ else {
151
+ if (y(r), h(n)) return;
152
+ u(), window.location.replace(f(n));
153
+ return;
154
+ }
155
+ let _ = btoa(JSON.stringify({
156
+ profileId: r,
157
+ tokens: m,
158
+ user: c,
159
+ profiles: l
160
+ })), x = d.pathname + d.search, S = `${d.origin}/auth/sso-callback?returnTo=${encodeURIComponent(x)}#sso=${_}`;
161
+ window.location.replace(S);
162
+ })(), () => {
163
+ t = !0;
164
+ };
165
+ }, [o]), /* @__PURE__ */ r("div", {
136
166
  className: "flex items-center justify-center min-h-screen bg-bg-canvas",
137
- children: /* @__PURE__ */ n("div", { className: "animate-spin rounded-full size-8 border-2 border-action-primary-bg border-t-transparent" })
167
+ children: /* @__PURE__ */ r("div", { className: "animate-spin rounded-full size-8 border-2 border-action-primary-bg border-t-transparent" })
138
168
  });
139
169
  }
140
170
  //#endregion
141
- export { v as SSORelayPage };
171
+ export { x as SSORelayPage };
142
172
 
143
173
  //# sourceMappingURL=SSORelayPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SSORelayPage.js","names":[],"sources":["../../../src/auth/pages/SSORelayPage.tsx"],"sourcesContent":["/**\n * SSORelayPage — Cross-domain SSO token relay.\n *\n * This page is loaded on the ORIGIN where the user is logged in.\n * It reads tokens from localStorage and redirects to the TARGET domain's\n * /auth/sso-callback with the tokens in a URL fragment (not query param\n * for security — fragments are not sent to servers).\n *\n * Flow:\n * 1. User visits productA.com → not logged in → detects SSO cookie\n * 2. Redirect to productB.com/auth/sso-relay?returnTo=productA.com/dashboard\n * (productB is where the user IS logged in — identified by cookie)\n * 3. This page reads tokens from productB's localStorage\n * 4. Redirects to productA.com/auth/sso-callback#token=<base64-encoded-tokens>\n * 5. SSORelayCallbackPage on productA decodes and stores tokens, redirects to /dashboard\n */\n\nimport { useEffect } from \"react\";\nimport { useSearchParams } from \"react-router-dom\";\n\nconst ALLOWED_RELAY_DOMAINS = new Set([\n \"burdenoff.com\",\n \"vibecontrols.com\",\n \"fluidgrids.com\",\n \"botlit.ai\",\n \"bigconsole.com\",\n \"gethealthybowl.com\",\n \"headshotmarketing.com\",\n \"timecampus.com\",\n \"ggnomad.com\",\n \"movethewheels.com\",\n \"crewfoundry.com\",\n \"planmagnet.com\",\n \"eventfullymanaged.com\",\n \"kadaikodi.com\",\n \"assethandler.com\",\n \"technospam.com\",\n \"semanticfed.com\",\n \"manufacturedops.com\",\n \"theglobalfuel.com\",\n \"housingvista.com\",\n \"proserviceworld.com\",\n \"mybodyshield.com\",\n \"artistrybase.com\",\n \"collabkin.com\",\n \"brainyrich.com\",\n \"theculturepost.com\",\n \"comradecircle.com\",\n \"cosmicintersection.com\",\n \"govcitizenhub.com\",\n \"intelwatchtower.com\",\n \"ecoimpacthub.com\",\n \"buildmyiq.com\",\n \"fluidgrids.ai\",\n \"atheletebridge.com\",\n \"adaptercloud.com\",\n \"coolandlovely.com\",\n \"subscriberbot.com\",\n \"labsofscience.com\",\n \"hookmovies.com\",\n]);\n\nconst RELAY_CHAIN_PARAM = \"relayChain\";\nconst RELAY_FAILED_PARAM = \"sso_failed\";\nconst LOCAL_HOSTNAMES = new Set([\"localhost\", \"127.0.0.1\"]);\n\nconst SSO_COOKIE_PATTERNS = {\n \"bf-sso\": /(?:^|;\\s*)bf-sso=([^;]*)/,\n \"bf-sso-profile\": /(?:^|;\\s*)bf-sso-profile=([^;]*)/,\n} as const;\n\nfunction isAllowedRelayTarget(url: URL): boolean {\n if (url.hostname === \"localhost\" || url.hostname === \"127.0.0.1\") {\n return true;\n }\n if (url.hostname.endsWith(\".local.burdenoff.com\")) {\n return true;\n }\n if (url.protocol !== \"https:\") {\n return false;\n }\n for (const domain of ALLOWED_RELAY_DOMAINS) {\n if (\n url.hostname === domain ||\n url.hostname === `app.${domain}` ||\n url.hostname === `alphaapp.${domain}` ||\n // Admin app shells (admin.burdenoff.com / alphaadmin.burdenoff.com) are\n // first-party SSO relay targets. Without these the relay-back to the\n // admin shell fails isAllowedRelayTarget() and SSORelayPage falls through\n // to window.location.replace(\"/\"), stranding the user on app.burdenoff.com.\n url.hostname === `admin.${domain}` ||\n url.hostname === `alphaadmin.${domain}` ||\n url.hostname === `www.${domain}`\n ) {\n return true;\n }\n }\n return false;\n}\n\nfunction clearRelayCookies(): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n for (const cookieName of [\"bf-sso\", \"bf-sso-profile\"] as const) {\n document.cookie = `${cookieName}=; path=/; max-age=0`;\n if (domain) {\n document.cookie = `${cookieName}=; path=/; domain=${domain}; max-age=0`;\n }\n if (hostname && !LOCAL_HOSTNAMES.has(hostname)) {\n document.cookie = `${cookieName}=; path=/; domain=${hostname}; max-age=0`;\n }\n }\n } catch {\n /* best effort */\n }\n}\n\nfunction getRelayChain(): string[] {\n try {\n return (\n new URLSearchParams(window.location.search).get(RELAY_CHAIN_PARAM) || \"\"\n )\n .split(\"|\")\n .filter((origin) => origin.length > 0);\n } catch {\n return [];\n }\n}\n\nfunction getFailedReturnTo(returnTo: string): string {\n try {\n const targetUrl = new URL(returnTo, window.location.origin);\n if (\n targetUrl.origin !== window.location.origin &&\n !isAllowedRelayTarget(targetUrl)\n ) {\n return \"/auth/login\";\n }\n targetUrl.searchParams.set(RELAY_FAILED_PARAM, \"1\");\n return targetUrl.href;\n } catch {\n return \"/auth/login?sso_failed=1\";\n }\n}\n\nfunction readSsoCookieProfileId(): string | null {\n return readSsoCookieData()?.id ?? null;\n}\n\nfunction readSsoCookieData(): {\n id: string | null;\n origin: string | null;\n} | null {\n try {\n for (const cookieName of [\"bf-sso\", \"bf-sso-profile\"] as const) {\n const match = document.cookie.match(SSO_COOKIE_PATTERNS[cookieName]);\n if (!match) continue;\n\n const parsed = JSON.parse(decodeURIComponent(match[1])) as\n | string\n | { id?: unknown; origin?: unknown };\n if (typeof parsed === \"string\") return { id: parsed, origin: null };\n if (typeof parsed.id === \"string\" && parsed.id.length > 0) {\n return {\n id: parsed.id,\n origin: typeof parsed.origin === \"string\" ? parsed.origin : null,\n };\n }\n }\n } catch {\n return null;\n }\n return null;\n}\n\nfunction redirectToCookieRelayOrigin(returnTo: string): boolean {\n try {\n const ssoCookie = readSsoCookieData();\n if (!ssoCookie?.origin) return false;\n\n const relayUrl = new URL(ssoCookie.origin);\n if (!isAllowedRelayTarget(relayUrl)) return false;\n if (relayUrl.origin === window.location.origin) return false;\n\n const relayChain = getRelayChain();\n if (relayChain.includes(relayUrl.origin) || relayChain.length >= 3) {\n return false;\n }\n\n const nextRelayUrl = new URL(\"/auth/sso-relay\", relayUrl.origin);\n nextRelayUrl.searchParams.set(\"returnTo\", returnTo);\n nextRelayUrl.searchParams.set(\n RELAY_CHAIN_PARAM,\n Array.from(new Set([...relayChain, window.location.origin])).join(\"|\"),\n );\n\n window.location.replace(nextRelayUrl.href);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction readProfilesRaw(): string | null {\n return localStorage.getItem(\"bf-profiles\");\n}\n\nfunction readProfileIds(): string[] {\n try {\n const profilesRaw = readProfilesRaw();\n const profiles = profilesRaw\n ? (JSON.parse(profilesRaw) as Array<{ id?: unknown }>)\n : [];\n return profiles\n .map((profile) => profile.id)\n .filter((id): id is string => typeof id === \"string\" && id.length > 0);\n } catch {\n return [];\n }\n}\n\nfunction resolveRelayProfileId(): string | null {\n const candidates = [\n readSsoCookieProfileId(),\n sessionStorage.getItem(\"bf-active-profile\"),\n ...readProfileIds(),\n ].filter((id): id is string => typeof id === \"string\" && id.length > 0);\n\n for (const profileId of Array.from(new Set(candidates))) {\n const tokensRaw = localStorage.getItem(`bf-p-${profileId}-tokens`);\n const userRaw = localStorage.getItem(`bf-p-${profileId}-user`);\n if (tokensRaw && userRaw) {\n sessionStorage.setItem(\"bf-active-profile\", profileId);\n return profileId;\n }\n }\n\n return null;\n}\n\nexport function SSORelayPage() {\n const [searchParams] = useSearchParams();\n\n useEffect(() => {\n const returnTo = searchParams.get(\"returnTo\");\n if (!returnTo) {\n // No relay target — there is nothing to relay to. Do NOT strand the user\n // on a perpetual spinner (HM-016): send them to the app root, which routes\n // to the dashboard when authenticated or the landing/login when not.\n window.location.replace(\"/\");\n return;\n }\n\n // Read tokens from localStorage (this origin has them). Prefer the shared\n // SSO cookie profile because a relay often runs in a new tab with empty\n // sessionStorage and stale historical profiles in localStorage.\n const profileId = resolveRelayProfileId();\n if (!profileId) {\n if (redirectToCookieRelayOrigin(returnTo)) return;\n clearRelayCookies();\n window.location.replace(getFailedReturnTo(returnTo));\n return;\n }\n\n const tokensRaw = localStorage.getItem(`bf-p-${profileId}-tokens`);\n const userRaw = localStorage.getItem(`bf-p-${profileId}-user`);\n const profilesRaw = readProfilesRaw();\n\n if (!tokensRaw || !userRaw) {\n // No tokens — redirect back without them\n if (redirectToCookieRelayOrigin(returnTo)) return;\n clearRelayCookies();\n window.location.replace(getFailedReturnTo(returnTo));\n return;\n }\n\n // Encode the auth state as a base64 URL fragment\n const payload = {\n profileId,\n tokens: tokensRaw,\n user: userRaw,\n profiles: profilesRaw,\n };\n const encoded = btoa(JSON.stringify(payload));\n\n // Build the callback URL on the target domain\n // Parse returnTo to get the target origin\n let callbackUrl: string;\n try {\n const targetUrl = new URL(returnTo);\n if (!isAllowedRelayTarget(targetUrl)) {\n window.location.replace(\"/\");\n return;\n }\n const returnPath = targetUrl.pathname + targetUrl.search;\n callbackUrl = `${targetUrl.origin}/auth/sso-callback?returnTo=${encodeURIComponent(returnPath)}#sso=${encoded}`;\n } catch {\n // returnTo is a relative path — can't do cross-domain relay\n window.location.replace(returnTo);\n return;\n }\n\n // Redirect to the callback\n window.location.replace(callbackUrl);\n }, [searchParams]);\n\n return (\n <div className=\"flex items-center justify-center min-h-screen bg-bg-canvas\">\n <div className=\"animate-spin rounded-full size-8 border-2 border-action-primary-bg border-t-transparent\" />\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SSORelayPage.js","names":[],"sources":["../../../src/auth/pages/SSORelayPage.tsx"],"sourcesContent":["/**\n * SSORelayPage — Cross-domain SSO token relay.\n *\n * This page is loaded on the ORIGIN where the user is logged in.\n * It reads tokens from localStorage and redirects to the TARGET domain's\n * /auth/sso-callback with the tokens in a URL fragment (not query param\n * for security — fragments are not sent to servers).\n *\n * Flow:\n * 1. User visits productA.com → not logged in → detects SSO cookie\n * 2. Redirect to productB.com/auth/sso-relay?returnTo=productA.com/dashboard\n * (productB is where the user IS logged in — identified by cookie)\n * 3. This page reads tokens from productB's localStorage\n * 4. Redirects to productA.com/auth/sso-callback#token=<base64-encoded-tokens>\n * 5. SSORelayCallbackPage on productA decodes and stores tokens, redirects to /dashboard\n */\n\nimport { useEffect } from \"react\";\nimport { useSearchParams } from \"react-router-dom\";\nimport { resolveGatewayUrls } from \"@burdenoff/fe-libs/shared/config/gatewayUrls\";\nimport { mintSsoSessionTokens } from \"@burdenoff/fe-libs/shared/utils\";\nimport { isAllowedSsoOrigin } from \"../utils/ssoOrigins\";\n\nconst RELAY_CHAIN_PARAM = \"relayChain\";\nconst RELAY_FAILED_PARAM = \"sso_failed\";\nconst LOCAL_HOSTNAMES = new Set([\"localhost\", \"127.0.0.1\"]);\n\nconst SSO_COOKIE_PATTERNS = {\n \"bf-sso\": /(?:^|;\\s*)bf-sso=([^;]*)/,\n \"bf-sso-profile\": /(?:^|;\\s*)bf-sso-profile=([^;]*)/,\n} as const;\n\nfunction clearRelayCookies(): void {\n try {\n const hostname = window.location.hostname;\n const domain = hostname.endsWith(\".local.burdenoff.com\")\n ? \".local.burdenoff.com\"\n : undefined;\n for (const cookieName of [\"bf-sso\", \"bf-sso-profile\"] as const) {\n document.cookie = `${cookieName}=; path=/; max-age=0`;\n if (domain) {\n document.cookie = `${cookieName}=; path=/; domain=${domain}; max-age=0`;\n }\n if (hostname && !LOCAL_HOSTNAMES.has(hostname)) {\n document.cookie = `${cookieName}=; path=/; domain=${hostname}; max-age=0`;\n }\n }\n } catch {\n /* best effort */\n }\n}\n\nfunction getRelayChain(): string[] {\n try {\n return (\n new URLSearchParams(window.location.search).get(RELAY_CHAIN_PARAM) || \"\"\n )\n .split(\"|\")\n .filter((origin) => origin.length > 0);\n } catch {\n return [];\n }\n}\n\nfunction getFailedReturnTo(returnTo: string): string {\n try {\n const targetUrl = new URL(returnTo, window.location.origin);\n if (\n targetUrl.origin !== window.location.origin &&\n !isAllowedSsoOrigin(targetUrl)\n ) {\n return \"/auth/login\";\n }\n targetUrl.searchParams.set(RELAY_FAILED_PARAM, \"1\");\n return targetUrl.href;\n } catch {\n return \"/auth/login?sso_failed=1\";\n }\n}\n\nfunction readSsoCookieProfileId(): string | null {\n return readSsoCookieData()?.id ?? null;\n}\n\nfunction readSsoCookieData(): {\n id: string | null;\n origin: string | null;\n} | null {\n try {\n for (const cookieName of [\"bf-sso\", \"bf-sso-profile\"] as const) {\n const match = document.cookie.match(SSO_COOKIE_PATTERNS[cookieName]);\n if (!match) continue;\n\n const parsed = JSON.parse(decodeURIComponent(match[1])) as\n string | { id?: unknown; origin?: unknown };\n if (typeof parsed === \"string\") return { id: parsed, origin: null };\n if (typeof parsed.id === \"string\" && parsed.id.length > 0) {\n return {\n id: parsed.id,\n origin: typeof parsed.origin === \"string\" ? parsed.origin : null,\n };\n }\n }\n } catch {\n return null;\n }\n return null;\n}\n\nfunction redirectToCookieRelayOrigin(returnTo: string): boolean {\n try {\n const ssoCookie = readSsoCookieData();\n if (!ssoCookie?.origin) return false;\n\n const relayUrl = new URL(ssoCookie.origin);\n if (!isAllowedSsoOrigin(relayUrl)) return false;\n if (relayUrl.origin === window.location.origin) return false;\n\n const relayChain = getRelayChain();\n if (relayChain.includes(relayUrl.origin) || relayChain.length >= 3) {\n return false;\n }\n\n const nextRelayUrl = new URL(\"/auth/sso-relay\", relayUrl.origin);\n nextRelayUrl.searchParams.set(\"returnTo\", returnTo);\n nextRelayUrl.searchParams.set(\n RELAY_CHAIN_PARAM,\n Array.from(new Set([...relayChain, window.location.origin])).join(\"|\"),\n );\n\n window.location.replace(nextRelayUrl.href);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction readProfilesRaw(): string | null {\n return localStorage.getItem(\"bf-profiles\");\n}\n\nfunction readProfileIds(): string[] {\n try {\n const profilesRaw = readProfilesRaw();\n const profiles = profilesRaw\n ? (JSON.parse(profilesRaw) as Array<{ id?: unknown }>)\n : [];\n return profiles\n .map((profile) => profile.id)\n .filter((id): id is string => typeof id === \"string\" && id.length > 0);\n } catch {\n return [];\n }\n}\n\nfunction resolveRelayProfileId(): string | null {\n const candidates = [\n readSsoCookieProfileId(),\n sessionStorage.getItem(\"bf-active-profile\"),\n ...readProfileIds(),\n ].filter((id): id is string => typeof id === \"string\" && id.length > 0);\n\n for (const profileId of Array.from(new Set(candidates))) {\n const tokensRaw = localStorage.getItem(`bf-p-${profileId}-tokens`);\n const userRaw = localStorage.getItem(`bf-p-${profileId}-user`);\n if (tokensRaw && userRaw) {\n sessionStorage.setItem(\"bf-active-profile\", profileId);\n return profileId;\n }\n }\n\n return null;\n}\n\n/** Wipe a dead/stale profile session from THIS origin's storage so future\n * relays stop offering it (BOFF-4463). */\nfunction clearStaleRelayProfile(profileId: string): void {\n try {\n localStorage.removeItem(`bf-p-${profileId}-tokens`);\n localStorage.removeItem(`bf-p-${profileId}-user`);\n localStorage.removeItem(`bf-p-${profileId}-workspace-token`);\n localStorage.removeItem(`bf-p-${profileId}-auth-storage`);\n sessionStorage.removeItem(\"bf-active-profile\");\n } catch {\n /* best effort */\n }\n}\n\nfunction parseStoredTokens(tokensRaw: string): {\n accessToken?: string;\n refreshToken?: string;\n expiresAt?: number;\n} | null {\n try {\n return JSON.parse(tokensRaw) as {\n accessToken?: string;\n refreshToken?: string;\n expiresAt?: number;\n };\n } catch {\n return null;\n }\n}\n\nexport function SSORelayPage() {\n const [searchParams] = useSearchParams();\n\n useEffect(() => {\n let cancelled = false;\n\n const run = async () => {\n const returnTo = searchParams.get(\"returnTo\");\n if (!returnTo) {\n // No relay target — there is nothing to relay to. Do NOT strand the user\n // on a perpetual spinner (HM-016): send them to the app root, which routes\n // to the dashboard when authenticated or the landing/login when not.\n window.location.replace(\"/\");\n return;\n }\n\n // Read tokens from localStorage (this origin has them). Prefer the shared\n // SSO cookie profile because a relay often runs in a new tab with empty\n // sessionStorage and stale historical profiles in localStorage.\n const profileId = resolveRelayProfileId();\n if (!profileId) {\n if (redirectToCookieRelayOrigin(returnTo)) return;\n clearRelayCookies();\n window.location.replace(getFailedReturnTo(returnTo));\n return;\n }\n\n const tokensRaw = localStorage.getItem(`bf-p-${profileId}-tokens`);\n const userRaw = localStorage.getItem(`bf-p-${profileId}-user`);\n const profilesRaw = readProfilesRaw();\n\n if (!tokensRaw || !userRaw) {\n // No tokens — redirect back without them\n if (redirectToCookieRelayOrigin(returnTo)) return;\n clearRelayCookies();\n window.location.replace(getFailedReturnTo(returnTo));\n return;\n }\n\n // Validate the target BEFORE any server round-trip.\n let targetUrl: URL;\n try {\n targetUrl = new URL(returnTo);\n } catch {\n // returnTo is a relative path — can't do cross-domain relay\n window.location.replace(returnTo);\n return;\n }\n if (!isAllowedSsoOrigin(targetUrl)) {\n window.location.replace(\"/\");\n return;\n }\n\n // BOFF-4463: relay a FRESH, dedicated token lineage to the target\n // instead of copying this origin's pair. Copied pairs share one\n // rotating refresh token — the first origin to refresh revoked it for\n // every other origin, and this page previously relayed even EXPIRED\n // pairs, dropping the target into a session that force-logged-out\n // minutes later.\n const stored = parseStoredTokens(tokensRaw);\n let relayTokensJson: string | null = null;\n\n if (stored?.refreshToken) {\n const { globalBaseUrl } = resolveGatewayUrls();\n const { tokens: minted, denied } = await mintSsoSessionTokens(\n `${globalBaseUrl}/global/graphql`,\n stored.refreshToken,\n );\n if (cancelled) return;\n if (minted) {\n relayTokensJson = JSON.stringify(minted);\n } else if (denied) {\n // The session behind this stored pair is dead (revoked/rotated\n // away). Never hand it to the target — clear it and fail over.\n clearStaleRelayProfile(profileId);\n if (redirectToCookieRelayOrigin(returnTo)) return;\n clearRelayCookies();\n window.location.replace(getFailedReturnTo(returnTo));\n return;\n }\n }\n\n if (!relayTokensJson) {\n // Mint unavailable (rollout gap / transient). Legacy fallback is only\n // safe while the stored access token is actually still valid.\n const accessStillValid =\n !!stored?.accessToken &&\n typeof stored.expiresAt === \"number\" &&\n stored.expiresAt - Date.now() > 60_000;\n if (accessStillValid) {\n relayTokensJson = tokensRaw;\n } else {\n clearStaleRelayProfile(profileId);\n if (redirectToCookieRelayOrigin(returnTo)) return;\n clearRelayCookies();\n window.location.replace(getFailedReturnTo(returnTo));\n return;\n }\n }\n\n // Encode the auth state as a base64 URL fragment\n const payload = {\n profileId,\n tokens: relayTokensJson,\n user: userRaw,\n profiles: profilesRaw,\n };\n const encoded = btoa(JSON.stringify(payload));\n\n const returnPath = targetUrl.pathname + targetUrl.search;\n const callbackUrl = `${targetUrl.origin}/auth/sso-callback?returnTo=${encodeURIComponent(returnPath)}#sso=${encoded}`;\n\n // Redirect to the callback\n window.location.replace(callbackUrl);\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [searchParams]);\n\n return (\n <div className=\"flex items-center justify-center min-h-screen bg-bg-canvas\">\n <div className=\"animate-spin rounded-full size-8 border-2 border-action-primary-bg border-t-transparent\" />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1,30 +1,20 @@
1
- import { useEffect as e, useRef as t } from "react";
2
- import { jsx as n } from "react/jsx-runtime";
1
+ import { isAllowedSsoOriginString as e } from "../utils/ssoOrigins.js";
2
+ import { useEffect as t, useRef as n } from "react";
3
+ import { jsx as r } from "react/jsx-runtime";
3
4
  //#region src/auth/pages/SessionBridgePage.tsx
4
- var r = "burdenoff-auth-bridge", i = "burdenoff-auth-client", a = "bf-p", o = "bf-profiles", s = "bf-active-profile";
5
- function c(e) {
5
+ var i = "burdenoff-auth-bridge", a = "burdenoff-auth-client", o = "bf-p", s = "bf-profiles", c = "bf-active-profile";
6
+ function l(e) {
6
7
  return e !== null && typeof e.postMessage == "function";
7
8
  }
8
- function l(e) {
9
+ function u(e) {
9
10
  return typeof e == "object" && !!e && typeof e.type == "string";
10
11
  }
11
- var u = new Set(/* @__PURE__ */ "burdenoff.com,vibecontrols.com,fluidgrids.com,botlit.ai,bigconsole.com,gethealthybowl.com,headshotmarketing.com,timecampus.com,ggnomad.com,movethewheels.com,crewfoundry.com,planmagnet.com,eventfullymanaged.com,kadaikodi.com,assethandler.com,technospam.com,semanticfed.com,manufacturedops.com,theglobalfuel.com,housingvista.com,proserviceworld.com,mybodyshield.com,artistrybase.com,collabkin.com,brainyrich.com,theculturepost.com,comradecircle.com,cosmicintersection.com,govcitizenhub.com,intelwatchtower.com,ecoimpacthub.com,buildmyiq.com,fluidgrids.ai,atheletebridge.com,adaptercloud.com,coolandlovely.com,subscriberbot.com,labsofscience.com,hookmovies.com".split(","));
12
- function d(e) {
12
+ function d() {
13
13
  try {
14
- let t = new URL(e);
15
- if (t.hostname === "localhost" || t.hostname === "127.0.0.1" || t.hostname.endsWith(".local.burdenoff.com")) return !0;
16
- for (let e of u) if (t.hostname === e || t.hostname === `app.${e}` || t.hostname === `alphaapp.${e}` || t.hostname === `admin.${e}` || t.hostname === `alphaadmin.${e}` || t.hostname === `www.${e}`) return !0;
17
- return !1;
18
- } catch {
19
- return !1;
20
- }
21
- }
22
- function f() {
23
- try {
24
- let e = localStorage.getItem(o), t = sessionStorage.getItem(s);
14
+ let e = localStorage.getItem(s), t = sessionStorage.getItem(c);
25
15
  if (!t && e) try {
26
16
  let n = JSON.parse(e);
27
- n.length > 0 && (t = n[0].id, sessionStorage.setItem(s, t));
17
+ n.length > 0 && (t = n[0].id, sessionStorage.setItem(c, t));
28
18
  } catch {}
29
19
  if (!t) return {
30
20
  user: null,
@@ -32,7 +22,7 @@ function f() {
32
22
  profiles: e ? JSON.parse(e) : [],
33
23
  activeProfileId: null
34
24
  };
35
- let n = localStorage.getItem(`${a}-${t}-user`), r = localStorage.getItem(`${a}-${t}-tokens`), i = localStorage.getItem(`${a}-${t}-workspace-token`);
25
+ let n = localStorage.getItem(`${o}-${t}-user`), r = localStorage.getItem(`${o}-${t}-tokens`), i = localStorage.getItem(`${o}-${t}-workspace-token`);
36
26
  return {
37
27
  user: n ? JSON.parse(n) : null,
38
28
  tokens: r ? {
@@ -51,64 +41,64 @@ function f() {
51
41
  };
52
42
  }
53
43
  }
54
- function p(e) {
55
- let t = e.activeProfileId ?? e.user?.id ?? sessionStorage.getItem(s);
44
+ function f(e) {
45
+ let t = e.activeProfileId ?? e.user?.id ?? sessionStorage.getItem(c);
56
46
  if (t) {
57
- if (e.user && localStorage.setItem(`${a}-${t}-user`, JSON.stringify(e.user)), e.tokens && typeof e.tokens == "object") {
47
+ if (e.user && localStorage.setItem(`${o}-${t}-user`, JSON.stringify(e.user)), e.tokens && typeof e.tokens == "object") {
58
48
  let { workspaceToken: n, ...r } = e.tokens;
59
- localStorage.setItem(`${a}-${t}-tokens`, JSON.stringify(r)), n && localStorage.setItem(`${a}-${t}-workspace-token`, String(n));
49
+ localStorage.setItem(`${o}-${t}-tokens`, JSON.stringify(r)), n && localStorage.setItem(`${o}-${t}-workspace-token`, String(n));
60
50
  }
61
- e.profiles && localStorage.setItem(o, JSON.stringify(e.profiles)), sessionStorage.setItem(s, t);
51
+ e.profiles && localStorage.setItem(s, JSON.stringify(e.profiles)), sessionStorage.setItem(c, t);
62
52
  }
63
53
  }
64
- function m() {
65
- let e = sessionStorage.getItem(s);
54
+ function p() {
55
+ let e = sessionStorage.getItem(c);
66
56
  if (e) {
67
- localStorage.removeItem(`${a}-${e}-user`), localStorage.removeItem(`${a}-${e}-tokens`), localStorage.removeItem(`${a}-${e}-workspace-token`), localStorage.removeItem(`${a}-${e}-auth-storage`);
57
+ localStorage.removeItem(`${o}-${e}-user`), localStorage.removeItem(`${o}-${e}-tokens`), localStorage.removeItem(`${o}-${e}-workspace-token`), localStorage.removeItem(`${o}-${e}-auth-storage`);
68
58
  try {
69
- let t = localStorage.getItem(o), n = t ? JSON.parse(t) : [];
70
- localStorage.setItem(o, JSON.stringify(n.filter((t) => t.id !== e)));
59
+ let t = localStorage.getItem(s), n = t ? JSON.parse(t) : [];
60
+ localStorage.setItem(s, JSON.stringify(n.filter((t) => t.id !== e)));
71
61
  } catch {}
72
- sessionStorage.removeItem(s);
62
+ sessionStorage.removeItem(c);
73
63
  }
74
64
  localStorage.removeItem("auth-storage");
75
65
  }
76
- function h() {
66
+ function m() {
77
67
  try {
78
- let e = localStorage.getItem(o), t = e ? JSON.parse(e) : [];
79
- for (let e of t) localStorage.removeItem(`${a}-${e.id}-user`), localStorage.removeItem(`${a}-${e.id}-tokens`), localStorage.removeItem(`${a}-${e.id}-workspace-token`), localStorage.removeItem(`${a}-${e.id}-auth-storage`), localStorage.removeItem(`${a}-${e.id}-context`);
68
+ let e = localStorage.getItem(s), t = e ? JSON.parse(e) : [];
69
+ for (let e of t) localStorage.removeItem(`${o}-${e.id}-user`), localStorage.removeItem(`${o}-${e.id}-tokens`), localStorage.removeItem(`${o}-${e.id}-workspace-token`), localStorage.removeItem(`${o}-${e.id}-auth-storage`), localStorage.removeItem(`${o}-${e.id}-context`);
80
70
  } catch {}
81
- for (let e of Object.keys(localStorage)) e.startsWith(`${a}-`) && e.endsWith("-auth-storage") && localStorage.removeItem(e);
82
- localStorage.removeItem(o), localStorage.removeItem("auth-storage"), sessionStorage.removeItem(s);
71
+ for (let e of Object.keys(localStorage)) e.startsWith(`${o}-`) && e.endsWith("-auth-storage") && localStorage.removeItem(e);
72
+ localStorage.removeItem(s), localStorage.removeItem("auth-storage"), sessionStorage.removeItem(c);
83
73
  }
84
- var g = "bf-sso-profile", _ = "bf-sso";
85
- function v(e, t) {
74
+ var h = "bf-sso-profile", g = "bf-sso";
75
+ function _(e, t) {
86
76
  try {
87
77
  let n = window.location.hostname.endsWith(".local.burdenoff.com") ? ".local.burdenoff.com" : void 0, r = n ? `; domain=${n}` : "", i = window.location.protocol === "https:" ? "; secure" : "", a = JSON.stringify({
88
78
  id: e,
89
79
  origin: t
90
80
  });
91
- document.cookie = `${g}=${encodeURIComponent(a)}${r}; path=/; samesite=lax${i}; max-age=86400`;
81
+ document.cookie = `${h}=${encodeURIComponent(a)}${r}; path=/; samesite=lax${i}; max-age=86400`;
92
82
  } catch {}
93
83
  }
94
- function y(e, t) {
84
+ function v(e, t) {
95
85
  try {
96
86
  let n = window.location.hostname.endsWith(".local.burdenoff.com") ? ".local.burdenoff.com" : void 0, r = n ? `; domain=${n}` : "", i = window.location.protocol === "https:" ? "; secure" : "", a = JSON.stringify({
97
87
  id: e,
98
88
  origin: t
99
89
  });
100
- document.cookie = `${_}=${encodeURIComponent(a)}${r}; path=/; samesite=lax${i}; max-age=86400`;
90
+ document.cookie = `${g}=${encodeURIComponent(a)}${r}; path=/; samesite=lax${i}; max-age=86400`;
101
91
  } catch {}
102
92
  }
103
- function b() {
93
+ function y() {
104
94
  try {
105
95
  let e = window.location.hostname, t = e.endsWith(".local.burdenoff.com") ? ".local.burdenoff.com" : void 0;
106
- document.cookie = `${g}=; path=/; max-age=0`, document.cookie = `${_}=; path=/; max-age=0`, t && (document.cookie = `${g}=; path=/; domain=${t}; max-age=0`, document.cookie = `${_}=; path=/; domain=${t}; max-age=0`), e && !["localhost", "127.0.0.1"].includes(e) && (document.cookie = `${g}=; path=/; domain=${e}; max-age=0`, document.cookie = `${_}=; path=/; domain=${e}; max-age=0`);
96
+ document.cookie = `${h}=; path=/; max-age=0`, document.cookie = `${g}=; path=/; max-age=0`, t && (document.cookie = `${h}=; path=/; domain=${t}; max-age=0`, document.cookie = `${g}=; path=/; domain=${t}; max-age=0`), e && !["localhost", "127.0.0.1"].includes(e) && (document.cookie = `${h}=; path=/; domain=${e}; max-age=0`, document.cookie = `${g}=; path=/; domain=${e}; max-age=0`);
107
97
  } catch {}
108
98
  }
109
- function x() {
110
- let s = t(/* @__PURE__ */ new Map()), u = t(/* @__PURE__ */ new Map()), g = t(!1);
111
- return e(() => {
99
+ function b() {
100
+ let c = n(/* @__PURE__ */ new Map()), h = n(/* @__PURE__ */ new Map()), g = n(!1);
101
+ return t(() => {
112
102
  async function e() {
113
103
  try {
114
104
  if (!document.requestStorageAccess) {
@@ -125,95 +115,95 @@ function x() {
125
115
  }
126
116
  }
127
117
  e();
128
- }, []), e(() => {
129
- function e(e) {
130
- return d(e);
118
+ }, []), t(() => {
119
+ function t(t) {
120
+ return e(t);
131
121
  }
132
- function t(e, t) {
133
- s.current.forEach((n, i) => {
134
- i !== t && n.source.postMessage({
122
+ function n(e, t) {
123
+ c.current.forEach((n, r) => {
124
+ r !== t && n.source.postMessage({
135
125
  ...e,
136
- source: r,
126
+ source: i,
137
127
  nonce: n.nonce
138
128
  }, n.origin);
139
129
  });
140
130
  }
141
- function n(n) {
142
- if (!e(n.origin) || !l(n.data) || n.data.source !== i) return;
143
- let a = n.data, o = c(n.source) ? n.source : null;
131
+ function r(e) {
132
+ if (!t(e.origin) || !u(e.data) || e.data.source !== a) return;
133
+ let r = e.data, o = l(e.source) ? e.source : null;
144
134
  if (!o) return;
145
- let d = typeof a.nonce == "string" && a.nonce.length > 0 ? a.nonce : null, g = s.current.get(o);
146
- if (a.type !== "AUTH_INIT") {
147
- if (!d || g?.nonce !== d) return;
148
- } else if (!d) return;
149
- switch (a.type) {
135
+ let s = typeof r.nonce == "string" && r.nonce.length > 0 ? r.nonce : null, g = c.current.get(o);
136
+ if (r.type !== "AUTH_INIT") {
137
+ if (!s || g?.nonce !== s) return;
138
+ } else if (!s) return;
139
+ switch (r.type) {
150
140
  case "AUTH_INIT": {
151
- if (s.current.set(o, {
141
+ if (c.current.set(o, {
152
142
  source: o,
153
- origin: n.origin,
154
- nonce: d
155
- }), a.payload && typeof a.payload == "object") {
156
- let e = a.payload;
157
- e.user && e.tokens && p(e);
143
+ origin: e.origin,
144
+ nonce: s
145
+ }), r.payload && typeof r.payload == "object") {
146
+ let e = r.payload;
147
+ e.user && e.tokens && f(e);
158
148
  }
159
- let e = f(), t = s.current.get(o);
160
- t && (t.source.postMessage({
149
+ let t = d(), n = c.current.get(o);
150
+ n && (n.source.postMessage({
161
151
  type: "AUTH_STATE",
162
- payload: e,
163
- source: r,
164
- nonce: t.nonce
165
- }, n.origin), u.current.get(o) !== t.nonce && (u.current.set(o, t.nonce), t.source.postMessage({
152
+ payload: t,
153
+ source: i,
154
+ nonce: n.nonce
155
+ }, e.origin), h.current.get(o) !== n.nonce && (h.current.set(o, n.nonce), n.source.postMessage({
166
156
  type: "AUTH_BRIDGE_READY",
167
- source: r,
168
- nonce: t.nonce
169
- }, n.origin)));
157
+ source: i,
158
+ nonce: n.nonce
159
+ }, e.origin)));
170
160
  break;
171
161
  }
172
162
  case "AUTH_LOGIN":
173
- if (a.payload) {
174
- p(a.payload);
175
- let e = a.payload, r = e.activeProfileId ?? e.user?.id;
176
- r && (b(), v(r, n.origin), y(r, n.origin)), t({
163
+ if (r.payload) {
164
+ f(r.payload);
165
+ let t = r.payload, i = t.activeProfileId ?? t.user?.id;
166
+ i && (y(), _(i, e.origin), v(i, e.origin)), n({
177
167
  type: "AUTH_STATE_CHANGED",
178
168
  payload: {
179
- ...f(),
169
+ ...d(),
180
170
  trigger: "login"
181
171
  }
182
172
  }, o);
183
173
  }
184
174
  break;
185
175
  case "AUTH_LOGOUT":
186
- m(), b(), t({
176
+ p(), y(), n({
187
177
  type: "AUTH_STATE_CHANGED",
188
178
  payload: {
189
- ...f(),
179
+ ...d(),
190
180
  trigger: "logout"
191
181
  }
192
182
  }, o);
193
183
  break;
194
184
  case "AUTH_LOGOUT_ALL":
195
- h(), b(), t({
185
+ m(), y(), n({
196
186
  type: "AUTH_STATE_CHANGED",
197
187
  payload: {
198
- ...f(),
188
+ ...d(),
199
189
  trigger: "logout_all"
200
190
  }
201
191
  }, o);
202
192
  break;
203
193
  case "AUTH_TOKEN_REFRESH":
204
- a.payload?.tokens && (p({ tokens: a.payload.tokens }), t({
194
+ r.payload?.tokens && (f({ tokens: r.payload.tokens }), n({
205
195
  type: "AUTH_STATE_CHANGED",
206
196
  payload: {
207
- ...f(),
197
+ ...d(),
208
198
  trigger: "refresh"
209
199
  }
210
200
  }, o));
211
201
  break;
212
202
  case "AUTH_ACCOUNT_SWITCH":
213
- a.payload && (p(a.payload), t({
203
+ r.payload && (f(r.payload), n({
214
204
  type: "AUTH_STATE_CHANGED",
215
205
  payload: {
216
- ...f(),
206
+ ...d(),
217
207
  trigger: "account_switch"
218
208
  }
219
209
  }, o));
@@ -221,24 +211,24 @@ function x() {
221
211
  }
222
212
  }
223
213
  function g(e) {
224
- (e.key?.startsWith(a) || e.key === o) && t({
214
+ (e.key?.startsWith(o) || e.key === s) && n({
225
215
  type: "AUTH_STATE_CHANGED",
226
216
  payload: {
227
- ...f(),
217
+ ...d(),
228
218
  trigger: "storage_sync"
229
219
  }
230
220
  });
231
221
  }
232
- return window.addEventListener("message", n), window.addEventListener("storage", g), () => {
233
- window.removeEventListener("message", n), window.removeEventListener("storage", g);
222
+ return window.addEventListener("message", r), window.addEventListener("storage", g), () => {
223
+ window.removeEventListener("message", r), window.removeEventListener("storage", g);
234
224
  };
235
- }, []), /* @__PURE__ */ n("div", {
225
+ }, []), /* @__PURE__ */ r("div", {
236
226
  id: "auth-session-bridge-root",
237
227
  "data-bridge": "active",
238
228
  style: { display: "none" }
239
229
  });
240
230
  }
241
231
  //#endregion
242
- export { x as SessionBridgePage };
232
+ export { b as SessionBridgePage };
243
233
 
244
234
  //# sourceMappingURL=SessionBridgePage.js.map