@burdenoff/microfe-auth 2026.624.2 → 2026.624.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.
@@ -9,6 +9,7 @@ import "./useApiKeyOperations.js";
9
9
  import "./useAppOperations.js";
10
10
  import "./useWorkspaceOperations.js";
11
11
  import "./useContextSwitcherData.js";
12
+ import "./useActiveContextBootstrapMirror.js";
12
13
  import "./useDelegationOperations.js";
13
14
  import "./useUserOperations.js";
14
15
  import "./useSsoOperations.js";
@@ -0,0 +1,27 @@
1
+ import { useEffect as e } from "react";
2
+ //#region src/auth/hooks/useActiveContextBootstrapMirror.ts
3
+ function t({ storeOrganizationId: t, storeWorkspaceId: n, storeProjectId: r, activeOrganizationId: i, activeWorkspaceId: a, activeProjectId: o, storeWorkspaces: s, storeProjects: c, setOrganization: l, setWorkspace: u, setProject: d }) {
4
+ e(() => {
5
+ t && !i && l(t);
6
+ let e = i || t, f = !!n && !!e && !!s?.some((t) => t.id === n && t.organizationId === e);
7
+ n && !a && f && u(n);
8
+ let p = a || (f ? n : null), m = !!r && !!p && !!c?.some((e) => e.id === r && e.workspaceId === p);
9
+ r && !o && m && d(r);
10
+ }, [
11
+ t,
12
+ n,
13
+ r,
14
+ i,
15
+ a,
16
+ o,
17
+ s,
18
+ c,
19
+ l,
20
+ u,
21
+ d
22
+ ]);
23
+ }
24
+ //#endregion
25
+ export { t as useActiveContextBootstrapMirror };
26
+
27
+ //# sourceMappingURL=useActiveContextBootstrapMirror.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useActiveContextBootstrapMirror.js","names":[],"sources":["../../../src/auth/hooks/useActiveContextBootstrapMirror.ts"],"sourcesContent":["import { useEffect } from \"react\";\n\ntype ContextId = string | null | undefined;\n\ninterface MirrorWorkspace {\n id: string;\n organizationId?: string | null;\n}\n\ninterface MirrorProject {\n id: string;\n workspaceId?: string | null;\n}\n\nexport interface ActiveContextBootstrapMirrorArgs {\n /** Auth-store `currentOrganizationId` */\n storeOrganizationId: ContextId;\n /** Auth-store `currentWorkspaceId` */\n storeWorkspaceId: ContextId;\n /** Auth-store `currentProjectId` */\n storeProjectId: ContextId;\n /** ActiveContext `organizationId` */\n activeOrganizationId: ContextId;\n /** ActiveContext `workspaceId` */\n activeWorkspaceId: ContextId;\n /** ActiveContext `projectId` */\n activeProjectId: ContextId;\n /**\n * The auth store's loaded `workspaces`. Used to verify that\n * `storeWorkspaceId` actually belongs to the organization being mirrored\n * before it is filled. If omitted, the workspace axis is never mirrored\n * (safe default — better no workspace than one from the wrong org).\n */\n storeWorkspaces?: ReadonlyArray<MirrorWorkspace>;\n /**\n * The auth store's loaded `projects`. Used to verify that `storeProjectId`\n * actually belongs to the workspace being mirrored before it is filled. If\n * omitted, the project axis is never mirrored.\n */\n storeProjects?: ReadonlyArray<MirrorProject>;\n /**\n * ActiveContext setters. Must be **stable references** (they are, coming\n * from `ActiveContextProvider`'s memoized value) — passing inline arrows\n * makes this effect re-run every render. Likewise prefer stable references\n * for `storeWorkspaces` / `storeProjects` (e.g. a shallow-equal Zustand\n * selector or `useMemo`); a new array reference each render re-runs the\n * effect. Re-runs are harmless (the fills are idempotent), just wasted work.\n */\n setOrganization: (organizationId: string | null) => void;\n setWorkspace: (workspaceId: string | null) => void;\n setProject: (projectId: string | null) => void;\n}\n\n/**\n * One-way, **fill-only** mirror of the auth store's org / workspace / project\n * ids into ActiveContext, for app-shell headers / context switchers.\n *\n * Some context ids are derived programmatically in the Zustand auth store\n * without a paired ActiveContext write:\n * - the bootstrap in `useContextSwitcherData` auto-selects an organization\n * (`setCurrentOrganizationId`) and, on the persisted-workspace path, a\n * workspace (`setCurrentWorkspaceId`) without calling `setActiveContext`;\n * - `useProjectSyncEffect` / `selectWorkspace` auto-select a project.\n *\n * ActiveContext is the source of truth for the URL + storage + the props passed\n * to workspace MFEs and the workspace-token context, so those store-only ids\n * must reach it — otherwise e.g. a fresh `defaultWorkspaceId` login leaves\n * `organizationId` undefined and the token / MFE context break until the user\n * reselects.\n *\n * The rule is **fill, never override**: copy a store id into ActiveContext only\n * when ActiveContext has no value for that axis yet. We must NOT overwrite a\n * non-null ActiveContext value, because:\n * 1. it would clobber an explicit selection — a switch handler's write, or a\n * deep-link / browser-back `?workspace=`/`?project=` that the router already\n * applied; and\n * 2. overriding is exactly what turns this into a two-store ping-pong — the\n * store→AC and AC→store effects each read the other's pre-commit value and\n * swap the ids every commit until React throws \"Maximum update depth\n * exceeded\" (error #185). Filling only nulls is self-terminating: once the\n * axis is set the guard is false forever, so it can never alternate.\n *\n * Cross-context safety: a value is filled only when it provably belongs to the\n * parent context being mirrored, validated against the loaded store metadata —\n * the **workspace** must belong to the target organization (`storeWorkspaces`),\n * and the **project** must belong to the target workspace (`storeProjects`).\n * The store can transiently hold a stale child id after its parent advanced\n * (e.g. `useContextSwitcherData` copies a new org into the store before the new\n * org's — unpersisted — workspace list loads, leaving `currentWorkspaceId`\n * pointing at the previous org; or the workspace advances to B while the\n * project still points at A). Gating on loaded metadata means such a stale id\n * isn't in the list yet, so it is not mirrored until it is genuinely valid.\n *\n * See BOFF-2805.\n */\nexport function useActiveContextBootstrapMirror({\n storeOrganizationId,\n storeWorkspaceId,\n storeProjectId,\n activeOrganizationId,\n activeWorkspaceId,\n activeProjectId,\n storeWorkspaces,\n storeProjects,\n setOrganization,\n setWorkspace,\n setProject,\n}: ActiveContextBootstrapMirrorArgs): void {\n useEffect(() => {\n if (storeOrganizationId && !activeOrganizationId) {\n setOrganization(storeOrganizationId);\n }\n\n // Fill the workspace only when it provably belongs to the organization\n // ActiveContext is on (or is being filled this pass). Guards against\n // publishing a (org, workspace) pair from two different orgs while the\n // store's org has advanced but its (unpersisted) workspace list / current\n // workspace id has not caught up.\n const targetOrganizationId = activeOrganizationId || storeOrganizationId;\n const workspaceBelongsToTargetOrganization =\n !!storeWorkspaceId &&\n !!targetOrganizationId &&\n !!storeWorkspaces?.some(\n (workspace) =>\n workspace.id === storeWorkspaceId &&\n workspace.organizationId === targetOrganizationId,\n );\n\n if (\n storeWorkspaceId &&\n !activeWorkspaceId &&\n workspaceBelongsToTargetOrganization\n ) {\n setWorkspace(storeWorkspaceId);\n }\n\n // Fill the project only when it provably belongs to the workspace\n // ActiveContext is on (or is being filled this pass). Prevents a stale\n // project from a previously-selected workspace leaking across a switch.\n // Fall back to the store workspace only when it is being filled this pass;\n // otherwise (e.g. workspace fill skipped because its metadata hasn't\n // loaded) we'd let a project fill without its parent workspace present,\n // an inconsistent (org, null, project) state.\n const targetWorkspaceId =\n activeWorkspaceId ||\n (workspaceBelongsToTargetOrganization ? storeWorkspaceId : null);\n const projectBelongsToTargetWorkspace =\n !!storeProjectId &&\n !!targetWorkspaceId &&\n !!storeProjects?.some(\n (project) =>\n project.id === storeProjectId &&\n project.workspaceId === targetWorkspaceId,\n );\n\n if (storeProjectId && !activeProjectId && projectBelongsToTargetWorkspace) {\n setProject(storeProjectId);\n }\n }, [\n storeOrganizationId,\n storeWorkspaceId,\n storeProjectId,\n activeOrganizationId,\n activeWorkspaceId,\n activeProjectId,\n storeWorkspaces,\n storeProjects,\n setOrganization,\n setWorkspace,\n setProject,\n ]);\n}\n"],"mappings":";;AA+FA,SAAgB,EAAgC,EAC9C,wBACA,qBACA,mBACA,yBACA,sBACA,oBACA,oBACA,kBACA,oBACA,iBACA,iBACyC;AACzC,SAAgB;AACd,EAAI,KAAuB,CAAC,KAC1B,EAAgB,EAAoB;EAQtC,IAAM,IAAuB,KAAwB,GAC/C,IACJ,CAAC,CAAC,KACF,CAAC,CAAC,KACF,CAAC,CAAC,GAAiB,MAChB,MACC,EAAU,OAAO,KACjB,EAAU,mBAAmB,EAChC;AAEH,EACE,KACA,CAAC,KACD,KAEA,EAAa,EAAiB;EAUhC,IAAM,IACJ,MACC,IAAuC,IAAmB,OACvD,IACJ,CAAC,CAAC,KACF,CAAC,CAAC,KACF,CAAC,CAAC,GAAe,MACd,MACC,EAAQ,OAAO,KACf,EAAQ,gBAAgB,EAC3B;AAEH,EAAI,KAAkB,CAAC,KAAmB,KACxC,EAAW,EAAe;IAE3B;EACD;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACD,CAAC"}
@@ -10,6 +10,7 @@ import "./hooks/usePasswordOperations.js";
10
10
  import "./hooks/useSessionOperations.js";
11
11
  import "./hooks/useWorkspaceOperations.js";
12
12
  import "./hooks/useContextSwitcherData.js";
13
+ import "./hooks/useActiveContextBootstrapMirror.js";
13
14
  import "./hooks/useUserOperations.js";
14
15
  import "./hooks/index.js";
15
16
  import "./hooks/useWorkspaceToken.js";
package/dist/index.js CHANGED
@@ -11,10 +11,11 @@ import { usePasswordOperations as c } from "./auth/hooks/usePasswordOperations.j
11
11
  import { useSessionOperations as l } from "./auth/hooks/useSessionOperations.js";
12
12
  import { useWorkspaceOperations as u } from "./auth/hooks/useWorkspaceOperations.js";
13
13
  import { useContextSwitcherData as d, useProjectSyncEffect as f, useWorkspaceSyncEffect as p } from "./auth/hooks/useContextSwitcherData.js";
14
- import { useUserOperations as m } from "./auth/hooks/useUserOperations.js";
15
- import { useWorkspaceToken as h } from "./auth/hooks/useWorkspaceToken.js";
16
- import { useTokenRefresh as g } from "./auth/hooks/useTokenRefresh.js";
17
- import { WorkspaceSelector as _ } from "./auth/components/workspace/WorkspaceSelector.js";
18
- import { UserProfileMenu as v } from "./auth/components/profile/UserProfileMenu.js";
14
+ import { useActiveContextBootstrapMirror as m } from "./auth/hooks/useActiveContextBootstrapMirror.js";
15
+ import { useUserOperations as h } from "./auth/hooks/useUserOperations.js";
16
+ import { useWorkspaceToken as g } from "./auth/hooks/useWorkspaceToken.js";
17
+ import { useTokenRefresh as _ } from "./auth/hooks/useTokenRefresh.js";
18
+ import { WorkspaceSelector as v } from "./auth/components/workspace/WorkspaceSelector.js";
19
+ import { UserProfileMenu as y } from "./auth/components/profile/UserProfileMenu.js";
19
20
  import "./auth/index.js";
20
- export { o as AuthRoot, a as SSORelayCallbackPage, i as SSORelayPage, v as UserProfileMenu, _ as WorkspaceSelector, e as useAuth, n as useAuthOperations, t as useAuthStore, d as useContextSwitcherData, s as useMfaOperations, r as useMyWorkspaces, c as usePasswordOperations, f as useProjectSyncEffect, l as useSessionOperations, g as useTokenRefresh, m as useUserOperations, u as useWorkspaceOperations, p as useWorkspaceSyncEffect, h as useWorkspaceToken };
21
+ export { o as AuthRoot, a as SSORelayCallbackPage, i as SSORelayPage, y as UserProfileMenu, v as WorkspaceSelector, m as useActiveContextBootstrapMirror, e as useAuth, n as useAuthOperations, t as useAuthStore, d as useContextSwitcherData, s as useMfaOperations, r as useMyWorkspaces, c as usePasswordOperations, f as useProjectSyncEffect, l as useSessionOperations, _ as useTokenRefresh, h as useUserOperations, u as useWorkspaceOperations, p as useWorkspaceSyncEffect, g as useWorkspaceToken };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-auth",
3
- "version": "2026.624.2",
3
+ "version": "2026.624.3",
4
4
  "description": "Auth microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "imports": {