@burdenoff/microfe-auth 2026.1014.2 → 2026.1023.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.
@@ -0,0 +1,46 @@
1
+ import { Organization, Workspace } from '../types';
2
+ /** A deep-linked organization / workspace the caller cannot open. */
3
+ export interface ContextLinkDenial {
4
+ kind: "workspace" | "organization";
5
+ /** The id the link named. */
6
+ id: string;
7
+ }
8
+ export type WorkspaceDeepLinkDecision = {
9
+ kind: "none";
10
+ } | {
11
+ kind: "switch";
12
+ workspace: Workspace;
13
+ } | {
14
+ kind: "denied";
15
+ denial: ContextLinkDenial;
16
+ };
17
+ export interface WorkspaceDeepLinkInput {
18
+ /** The active workspace id when the bootstrap started (URL first). */
19
+ requestedWorkspaceId: string | null;
20
+ /** True when that id is the `?workspace=` param of the current URL. */
21
+ fromUrl: boolean;
22
+ /** The organization the bootstrap is about to enter. */
23
+ targetOrgId: string | null;
24
+ /** The caller's organizations, as `myOrganizations` returned them. */
25
+ organizations: ReadonlyArray<Pick<Organization, "id">>;
26
+ /** The caller's workspaces across organizations, or null when not read. */
27
+ workspaces: ReadonlyArray<Workspace> | null;
28
+ /** False when `workspaces` is only a page of a longer list. */
29
+ workspacesComplete: boolean;
30
+ }
31
+ export declare function decideWorkspaceDeepLink(input: WorkspaceDeepLinkInput): WorkspaceDeepLinkDecision;
32
+ /**
33
+ * The denial for a deep-linked organization the caller is not a member of.
34
+ * Null when the organization did not come from the URL (a stale saved id) or
35
+ * the caller's organization list is empty (nothing was read).
36
+ */
37
+ export declare function decideOrganizationDeepLink(input: {
38
+ requestedOrgId: string | null;
39
+ fromUrl: boolean;
40
+ organizations: ReadonlyArray<Pick<Organization, "id">>;
41
+ }): ContextLinkDenial | null;
42
+ /** True when a `{ total, items }` page holds the whole list. */
43
+ export declare function isCompleteListing(listing: {
44
+ total?: number | null;
45
+ items: ReadonlyArray<unknown>;
46
+ } | null): boolean;
@@ -0,0 +1,30 @@
1
+ //#region src/auth/hooks/contextDeepLink.ts
2
+ function e(e) {
3
+ let { requestedWorkspaceId: t, workspaces: n } = e;
4
+ if (!t || !n) return { kind: "none" };
5
+ let r = n.find((e) => e.id === t);
6
+ return r ? !e.organizations.some((e) => e.id === r.organizationId) || r.organizationId === e.targetOrgId ? { kind: "none" } : {
7
+ kind: "switch",
8
+ workspace: r
9
+ } : e.fromUrl && e.workspacesComplete ? {
10
+ kind: "denied",
11
+ denial: {
12
+ kind: "workspace",
13
+ id: t
14
+ }
15
+ } : { kind: "none" };
16
+ }
17
+ function t(e) {
18
+ let { requestedOrgId: t, fromUrl: n, organizations: r } = e;
19
+ return !t || !n || r.length === 0 || r.some((e) => e.id === t) ? null : {
20
+ kind: "organization",
21
+ id: t
22
+ };
23
+ }
24
+ function n(e) {
25
+ return e ? typeof e.total != "number" || e.total <= e.items.length : !1;
26
+ }
27
+ //#endregion
28
+ export { t as decideOrganizationDeepLink, e as decideWorkspaceDeepLink, n as isCompleteListing };
29
+
30
+ //# sourceMappingURL=contextDeepLink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contextDeepLink.js","names":[],"sources":["../../../src/auth/hooks/contextDeepLink.ts"],"sourcesContent":["/**\n * Deep links to a workspace (`?workspace=<id>`, with or without `?org=`).\n *\n * ★★★ The bug this fixes (vc-fg-parity round 3): a link such as\n * `/workflows/runs?workspace=<id>` without an `org` param, to a workspace in a\n * DIFFERENT organization than the one saved in the session, was rewritten\n * ~2.6s after load to the saved organization's first workspace, with no\n * message. The bootstrap kept the saved organization, filtered `myWorkspaces`\n * by it, did not find the linked workspace, and fell back silently.\n *\n * A workspace names its organization, so the workspace in the link wins:\n *\n * - linked workspace is one of the caller's (`myWorkspaces`) and its\n * organization is accessible → switch organization AND workspace to it;\n * - linked workspace is not one of the caller's (and the list was read in\n * full) → record a denial the shell shows as a clear \"not available\" state\n * instead of a silent redirect;\n * - otherwise (no link, or an incomplete read) → nothing changes here.\n *\n * Only a workspace that came from the URL is a deep link. A stale id restored\n * from session storage (an earlier account, a deleted workspace) still falls\n * back quietly, as it always has.\n *\n * Pure so it can be unit-tested; `useContextSwitcherData` applies it.\n */\nimport type { Organization, Workspace } from \"../types\";\n\n/** A deep-linked organization / workspace the caller cannot open. */\nexport interface ContextLinkDenial {\n kind: \"workspace\" | \"organization\";\n /** The id the link named. */\n id: string;\n}\n\nexport type WorkspaceDeepLinkDecision =\n | { kind: \"none\" }\n | { kind: \"switch\"; workspace: Workspace }\n | { kind: \"denied\"; denial: ContextLinkDenial };\n\nexport interface WorkspaceDeepLinkInput {\n /** The active workspace id when the bootstrap started (URL first). */\n requestedWorkspaceId: string | null;\n /** True when that id is the `?workspace=` param of the current URL. */\n fromUrl: boolean;\n /** The organization the bootstrap is about to enter. */\n targetOrgId: string | null;\n /** The caller's organizations, as `myOrganizations` returned them. */\n organizations: ReadonlyArray<Pick<Organization, \"id\">>;\n /** The caller's workspaces across organizations, or null when not read. */\n workspaces: ReadonlyArray<Workspace> | null;\n /** False when `workspaces` is only a page of a longer list. */\n workspacesComplete: boolean;\n}\n\nexport function decideWorkspaceDeepLink(\n input: WorkspaceDeepLinkInput,\n): WorkspaceDeepLinkDecision {\n const { requestedWorkspaceId, workspaces } = input;\n if (!requestedWorkspaceId || !workspaces) return { kind: \"none\" };\n\n const linked = workspaces.find((w) => w.id === requestedWorkspaceId);\n if (linked) {\n const orgAccessible = input.organizations.some(\n (org) => org.id === linked.organizationId,\n );\n if (!orgAccessible || linked.organizationId === input.targetOrgId) {\n return { kind: \"none\" };\n }\n return { kind: \"switch\", workspace: linked };\n }\n\n if (input.fromUrl && input.workspacesComplete) {\n return {\n kind: \"denied\",\n denial: { kind: \"workspace\", id: requestedWorkspaceId },\n };\n }\n return { kind: \"none\" };\n}\n\n/**\n * The denial for a deep-linked organization the caller is not a member of.\n * Null when the organization did not come from the URL (a stale saved id) or\n * the caller's organization list is empty (nothing was read).\n */\nexport function decideOrganizationDeepLink(input: {\n requestedOrgId: string | null;\n fromUrl: boolean;\n organizations: ReadonlyArray<Pick<Organization, \"id\">>;\n}): ContextLinkDenial | null {\n const { requestedOrgId, fromUrl, organizations } = input;\n if (!requestedOrgId || !fromUrl || organizations.length === 0) return null;\n if (organizations.some((org) => org.id === requestedOrgId)) return null;\n return { kind: \"organization\", id: requestedOrgId };\n}\n\n/** True when a `{ total, items }` page holds the whole list. */\nexport function isCompleteListing(\n listing: { total?: number | null; items: ReadonlyArray<unknown> } | null,\n): boolean {\n if (!listing) return false;\n return (\n typeof listing.total !== \"number\" || listing.total <= listing.items.length\n );\n}\n"],"mappings":";AAsDA,SAAgB,EACd,GAC2B;CAC3B,IAAM,EAAE,yBAAsB,kBAAe;CAC7C,IAAI,CAAC,KAAwB,CAAC,GAAY,OAAO,EAAE,MAAM,OAAO;CAEhE,IAAM,IAAS,EAAW,MAAM,MAAM,EAAE,OAAO,CAAoB;CAiBnE,OAhBI,IAIE,CAHkB,EAAM,cAAc,MACvC,MAAQ,EAAI,OAAO,EAAO,cAExB,KAAiB,EAAO,mBAAmB,EAAM,cAC7C,EAAE,MAAM,OAAO,IAEjB;EAAE,MAAM;EAAU,WAAW;CAAO,IAGzC,EAAM,WAAW,EAAM,qBAClB;EACL,MAAM;EACN,QAAQ;GAAE,MAAM;GAAa,IAAI;EAAqB;CACxD,IAEK,EAAE,MAAM,OAAO;AACxB;AAOA,SAAgB,EAA2B,GAId;CAC3B,IAAM,EAAE,mBAAgB,YAAS,qBAAkB;CAGnD,OAFI,CAAC,KAAkB,CAAC,KAAW,EAAc,WAAW,KACxD,EAAc,MAAM,MAAQ,EAAI,OAAO,CAAc,IAAU,OAC5D;EAAE,MAAM;EAAgB,IAAI;CAAe;AACpD;AAGA,SAAgB,EACd,GACS;CAET,OADK,IAEH,OAAO,EAAQ,SAAU,YAAY,EAAQ,SAAS,EAAQ,MAAM,SAFjD;AAIvB"}