@burdenoff/microfe-export 2026.530.2 → 2026.623.2
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExportLandingPage.js","names":[],"sources":["../../../src/export/pages/ExportLandingPage.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useSearchParams } from \"react-router-dom\";\nimport { Tabs, type TabItem } from \"@burdenoff/fe-libs/chrome\";\n\nimport { ExportDashboardPage } from \"./ExportDashboardPage\";\nimport { CreateExportPage } from \"./CreateExportPage\";\nimport { ExportHistoryPage } from \"./ExportHistoryPage\";\nimport { ExportTemplatesPage } from \"./ExportTemplatesPage\";\n\n/**\n * ExportLandingPage\n *\n * Tabbed landing page for the Export microfrontend. Renders the existing\n * sub-pages (dashboard, new, history, templates) inside a Tabs container.\n * The active tab is reflected in the `?tab=` URL search param.\n */\nexport function ExportLandingPage() {\n const [params, setParams] = useSearchParams();\n\n const tabs: TabItem[] = useMemo(\n () => [\n { id: \"dashboard\", label: \"Dashboard\" },\n { id: \"
|
|
1
|
+
{"version":3,"file":"ExportLandingPage.js","names":[],"sources":["../../../src/export/pages/ExportLandingPage.tsx"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useSearchParams } from \"react-router-dom\";\nimport { Tabs, type TabItem } from \"@burdenoff/fe-libs/chrome\";\n\nimport { ExportDashboardPage } from \"./ExportDashboardPage\";\nimport { CreateExportPage } from \"./CreateExportPage\";\nimport { ExportHistoryPage } from \"./ExportHistoryPage\";\nimport { ExportTemplatesPage } from \"./ExportTemplatesPage\";\n\n/**\n * ExportLandingPage\n *\n * Tabbed landing page for the Export microfrontend. Renders the existing\n * sub-pages (dashboard, new, history, templates) inside a Tabs container.\n * The active tab is reflected in the `?tab=` URL search param.\n */\nexport function ExportLandingPage() {\n const [params, setParams] = useSearchParams();\n\n // No dedicated \"New Export\" tab — creating an export is a primary action\n // (the Create button / wizard), not a peer view. The wizard is still\n // reachable at ?tab=new (rendered below) but is not shown in the tab strip.\n const tabs: TabItem[] = useMemo(\n () => [\n { id: \"dashboard\", label: \"Dashboard\" },\n { id: \"history\", label: \"History\" },\n { id: \"templates\", label: \"Templates\" },\n ],\n [],\n );\n\n const activeId = params.get(\"tab\") ?? tabs[0].id;\n\n const handleTabChange = (id: string) => {\n const next = new URLSearchParams(params);\n if (id === tabs[0].id) {\n next.delete(\"tab\");\n } else {\n next.set(\"tab\", id);\n }\n setParams(next, { replace: true });\n };\n\n return (\n <div className=\"flex flex-col h-full\">\n <div className=\"border-b border-border-subtle px-4 pt-3\">\n <Tabs\n items={tabs}\n activeTabId={activeId}\n onTabChange={handleTabChange}\n variant=\"underline\"\n />\n </div>\n <div className=\"flex-1 min-h-0 overflow-auto\">\n {activeId === \"dashboard\" && <ExportDashboardPage />}\n {activeId === \"new\" && <CreateExportPage />}\n {activeId === \"history\" && <ExportHistoryPage />}\n {activeId === \"templates\" && <ExportTemplatesPage />}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAgBA,SAAgB,IAAoB;CAClC,IAAM,CAAC,GAAQ,KAAa,GAAiB,EAKvC,IAAkB,QAChB;EACJ;GAAE,IAAI;GAAa,OAAO;GAAa;EACvC;GAAE,IAAI;GAAW,OAAO;GAAW;EACnC;GAAE,IAAI;GAAa,OAAO;GAAa;EACxC,EACD,EAAE,CACH,EAEK,IAAW,EAAO,IAAI,MAAM,IAAI,EAAK,GAAG;AAY9C,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,GAAD;IACE,OAAO;IACP,aAAa;IACb,cAhBiB,MAAe;KACtC,IAAM,IAAO,IAAI,gBAAgB,EAAO;AAMxC,KALI,MAAO,EAAK,GAAG,KACjB,EAAK,OAAO,MAAM,GAElB,EAAK,IAAI,OAAO,EAAG,EAErB,EAAU,GAAM,EAAE,SAAS,IAAM,CAAC;;IAU5B,SAAQ;IACR,CAAA;GACE,CAAA,EACN,kBAAC,OAAD;GAAK,WAAU;aAAf;IACG,MAAa,eAAe,kBAAC,GAAD,EAAuB,CAAA;IACnD,MAAa,SAAS,kBAAC,GAAD,EAAoB,CAAA;IAC1C,MAAa,aAAa,kBAAC,GAAD,EAAqB,CAAA;IAC/C,MAAa,eAAe,kBAAC,GAAD,EAAuB,CAAA;IAChD;KACF"}
|
|
@@ -3,59 +3,67 @@ import { GraphQLWsLink as t } from "../../node_modules/@apollo/client/link/subsc
|
|
|
3
3
|
import { createClient as n } from "../../node_modules/graphql-ws/dist/client.js";
|
|
4
4
|
import { createContext as r, useContext as i, useMemo as a, useRef as o } from "react";
|
|
5
5
|
import { MemoryRouter as s } from "react-router-dom";
|
|
6
|
-
import {
|
|
7
|
-
import { ApolloClient as
|
|
8
|
-
import { onError as
|
|
9
|
-
import { ApolloProvider as
|
|
10
|
-
import { isAuthNetworkError as
|
|
11
|
-
import { jsx as
|
|
6
|
+
import { MutationCache as c, QueryClient as l, QueryClientProvider as u } from "@tanstack/react-query";
|
|
7
|
+
import { ApolloClient as d, HttpLink as f, InMemoryCache as p, Observable as m, from as h, split as g } from "@apollo/client";
|
|
8
|
+
import { onError as _ } from "@apollo/client/link/error";
|
|
9
|
+
import { ApolloProvider as v } from "@apollo/client/react";
|
|
10
|
+
import { isAuthNetworkError as y, redirectToLogin as b } from "@burdenoff/fe-libs/shared/utils";
|
|
11
|
+
import { jsx as x } from "react/jsx-runtime";
|
|
12
12
|
//#region src/export/providers/ExportProvider.tsx
|
|
13
|
-
function
|
|
13
|
+
function S(e = []) {
|
|
14
14
|
return e.some((e) => {
|
|
15
15
|
let t = typeof e.extensions?.code == "string" ? e.extensions.code : "", n = typeof e.extensions?.errorCode == "string" ? e.extensions.errorCode : "", r = (e.message ?? "").toLowerCase();
|
|
16
16
|
return t === "UNAUTHENTICATED" || n === "TOKEN_EXPIRED" || r.includes("token_expired") || r.includes("token expired") || r.includes("session expired") || r.includes("unauthorized") || r.includes("unauthenticated") || r.includes("authentication required") || r.includes("workspace context missing");
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
-
var
|
|
20
|
-
let e = i(
|
|
19
|
+
var C = r(null), w = () => {
|
|
20
|
+
let e = i(C);
|
|
21
21
|
if (!e) throw Error("useExport must be used within ExportProvider");
|
|
22
22
|
return e;
|
|
23
|
-
},
|
|
24
|
-
let
|
|
23
|
+
}, T = ({ children: r, basePath: i, navigate: w, currentUser: T, workspaceId: E, tenantId: D, organizationId: O, apiGatewayUrl: k, authToken: A, workspaceToken: j, eventBus: M, getWorkspaceToken: N }) => {
|
|
24
|
+
let P = {
|
|
25
25
|
basePath: i,
|
|
26
|
-
navigate:
|
|
27
|
-
currentUser:
|
|
28
|
-
workspaceId:
|
|
29
|
-
tenantId:
|
|
30
|
-
organizationId:
|
|
31
|
-
apiGatewayUrl:
|
|
32
|
-
authToken:
|
|
33
|
-
workspaceToken:
|
|
34
|
-
eventBus:
|
|
35
|
-
getWorkspaceToken:
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
let
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
26
|
+
navigate: w,
|
|
27
|
+
currentUser: T,
|
|
28
|
+
workspaceId: E,
|
|
29
|
+
tenantId: D,
|
|
30
|
+
organizationId: O,
|
|
31
|
+
apiGatewayUrl: k,
|
|
32
|
+
authToken: A,
|
|
33
|
+
workspaceToken: j,
|
|
34
|
+
eventBus: M,
|
|
35
|
+
getWorkspaceToken: N
|
|
36
|
+
}, F = o(!1), I = o(N);
|
|
37
|
+
I.current = N;
|
|
38
|
+
let L = a(() => {
|
|
39
|
+
let e = new l({
|
|
40
|
+
mutationCache: new c({ onSuccess: () => {
|
|
41
|
+
e.invalidateQueries();
|
|
42
|
+
} }),
|
|
43
|
+
defaultOptions: { queries: {
|
|
44
|
+
retry: 1,
|
|
45
|
+
refetchOnWindowFocus: !1,
|
|
46
|
+
staleTime: 300 * 1e3
|
|
47
|
+
} }
|
|
48
|
+
});
|
|
49
|
+
return e;
|
|
50
|
+
}, []), R = a(() => {
|
|
51
|
+
let r = !A && !j, i = {};
|
|
52
|
+
A && (i.Authorization = `Bearer ${A}`), j && (i["X-Workspace-Authorization"] = `Bearer ${j}`), r && (i["x-actor-id"] = "00000000-0000-0000-0000-000000000002", i["x-actor-type"] = "user", i["x-workspace-id"] = E ?? "00000000-0000-0000-0000-000000000001", i["x-gateway-auth-token"] = "");
|
|
53
|
+
let a = r ? "http://localhost:3732/graphql" : k ? `${k}/workspaces/graphql` : "/workspaces/graphql", o = new f({
|
|
46
54
|
uri: a,
|
|
47
55
|
headers: Object.keys(i).length > 0 ? i : void 0
|
|
48
|
-
}), s =
|
|
56
|
+
}), s = _(({ error: e, operation: t, forward: n }) => {
|
|
49
57
|
if (!e) return;
|
|
50
|
-
let r =
|
|
51
|
-
if (!
|
|
52
|
-
e instanceof Error &&
|
|
58
|
+
let r = S("errors" in e && Array.isArray(e.errors) ? e.errors : []);
|
|
59
|
+
if (!I.current || !r || F.current) {
|
|
60
|
+
e instanceof Error && y(e) && b();
|
|
53
61
|
return;
|
|
54
62
|
}
|
|
55
|
-
return new
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
|
|
63
|
+
return new m((e) => {
|
|
64
|
+
F.current = !0, I.current().then((r) => {
|
|
65
|
+
if (F.current = !1, !r) {
|
|
66
|
+
b(), e.error(/* @__PURE__ */ Error("Failed to refresh workspace token"));
|
|
59
67
|
return;
|
|
60
68
|
}
|
|
61
69
|
t.setContext(({ headers: e = {} }) => ({ headers: {
|
|
@@ -65,39 +73,39 @@ var S = r(null), C = () => {
|
|
|
65
73
|
let i = n(t).subscribe(e);
|
|
66
74
|
return () => i.unsubscribe();
|
|
67
75
|
}).catch((t) => {
|
|
68
|
-
|
|
76
|
+
F.current = !1, e.error(t);
|
|
69
77
|
});
|
|
70
78
|
});
|
|
71
79
|
});
|
|
72
|
-
return new
|
|
73
|
-
link:
|
|
80
|
+
return new d({
|
|
81
|
+
link: g(({ query: t }) => {
|
|
74
82
|
let n = e(t);
|
|
75
83
|
return n.kind === "OperationDefinition" && n.operation === "subscription";
|
|
76
84
|
}, new t(n({
|
|
77
85
|
url: a.replace(/^http/, "ws"),
|
|
78
86
|
connectionParams: Object.keys(i).length > 0 ? i : void 0
|
|
79
|
-
})),
|
|
80
|
-
cache: new
|
|
87
|
+
})), h([s, o])),
|
|
88
|
+
cache: new p(),
|
|
81
89
|
defaultOptions: { watchQuery: { fetchPolicy: "cache-and-network" } }
|
|
82
90
|
});
|
|
83
91
|
}, [
|
|
84
|
-
O,
|
|
85
92
|
k,
|
|
86
93
|
A,
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
94
|
+
j,
|
|
95
|
+
E
|
|
96
|
+
]), z = /* @__PURE__ */ x(u, {
|
|
97
|
+
client: L,
|
|
98
|
+
children: /* @__PURE__ */ x(C.Provider, {
|
|
99
|
+
value: P,
|
|
92
100
|
children: r
|
|
93
101
|
})
|
|
94
102
|
});
|
|
95
|
-
return i ?
|
|
96
|
-
client:
|
|
97
|
-
children: /* @__PURE__ */
|
|
103
|
+
return i ? z : /* @__PURE__ */ x(v, {
|
|
104
|
+
client: R,
|
|
105
|
+
children: /* @__PURE__ */ x(s, { children: z })
|
|
98
106
|
});
|
|
99
107
|
};
|
|
100
108
|
//#endregion
|
|
101
|
-
export {
|
|
109
|
+
export { T as ExportProvider, w as useExport };
|
|
102
110
|
|
|
103
111
|
//# sourceMappingURL=ExportProvider.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ExportProvider.js","names":[],"sources":["../../../src/export/providers/ExportProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from \"react\";\nimport { createContext, useContext, useMemo, useRef } from \"react\";\nimport { MemoryRouter } from \"react-router-dom\";\nimport { QueryClient, QueryClientProvider } from \"@tanstack/react-query\";\nimport {\n ApolloClient,\n InMemoryCache,\n HttpLink,\n from,\n split,\n Observable,\n} from \"@apollo/client\";\nimport { getMainDefinition } from \"@apollo/client/utilities\";\nimport { GraphQLWsLink } from \"@apollo/client/link/subscriptions\";\nimport { createClient } from \"graphql-ws\";\nimport { onError } from \"@apollo/client/link/error\";\nimport { ApolloProvider } from \"@apollo/client/react\";\nimport {\n isAuthNetworkError,\n redirectToLogin,\n} from \"@burdenoff/fe-libs/shared/utils\";\nimport type { ExportRootProps } from \"../types\";\n\nfunction hasSessionAuthErrors(\n errors: ReadonlyArray<{\n message?: string;\n extensions?: Record<string, unknown>;\n }> = [],\n): boolean {\n return errors.some((error) => {\n const code =\n typeof error.extensions?.code === \"string\" ? error.extensions.code : \"\";\n const errorCode =\n typeof error.extensions?.errorCode === \"string\"\n ? error.extensions.errorCode\n : \"\";\n const message = (error.message ?? \"\").toLowerCase();\n\n return (\n code === \"UNAUTHENTICATED\" ||\n errorCode === \"TOKEN_EXPIRED\" ||\n message.includes(\"token_expired\") ||\n message.includes(\"token expired\") ||\n message.includes(\"session expired\") ||\n message.includes(\"unauthorized\") ||\n message.includes(\"unauthenticated\") ||\n message.includes(\"authentication required\") ||\n message.includes(\"workspace context missing\")\n );\n });\n}\n\n/**\n * Context value for Export microfrontend\n */\ntype ExportContextValue = ExportRootProps;\n\nconst ExportContext = createContext<ExportContextValue | null>(null);\n\n/**\n * Hook to access Export context\n */\nexport const useExport = () => {\n const context = useContext(ExportContext);\n if (!context) {\n throw new Error(\"useExport must be used within ExportProvider\");\n }\n return context;\n};\n\n/**\n * Provider component for Export microfrontend\n * Sets up routing, context, Apollo Client, and React Query client\n */\nexport const ExportProvider: FC<PropsWithChildren<ExportRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n organizationId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n eventBus,\n getWorkspaceToken,\n}) => {\n const contextValue: ExportContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n organizationId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n eventBus,\n getWorkspaceToken,\n };\n\n // Track refreshing state across renders without triggering re-renders\n const isRefreshingRef = useRef(false);\n\n // Stable ref for getWorkspaceToken so it never invalidates the Apollo client memo\n const getWorkspaceTokenRef = useRef(getWorkspaceToken);\n getWorkspaceTokenRef.current = getWorkspaceToken;\n\n // Create QueryClient instance (memoized to avoid recreation on re-renders)\n const queryClient = useMemo(\n () =>\n new QueryClient({\n defaultOptions: {\n queries: {\n retry: 1,\n refetchOnWindowFocus: false,\n staleTime: 5 * 60 * 1000, // 5 minutes\n },\n },\n }),\n [],\n );\n\n // Create ApolloClient instance (memoized, recreated when tokens change)\n // The workspace gateway requires dual-token auth:\n // Authorization: Bearer <authToken> (platform identity)\n // X-Workspace-Authorization: Bearer <workspaceToken> (workspace access)\n // In dev mode (no tokens), the client adds actor headers directly and uses\n // x-gateway-auth-token so the service trusts them (TRUSTED_GATEWAY_TOKEN=dev-test-secret-2026).\n const apolloClient = useMemo(() => {\n const isDevMode = !authToken && !workspaceToken;\n const headers: Record<string, string> = {};\n\n if (authToken) {\n headers[\"Authorization\"] = `Bearer ${authToken}`;\n }\n if (workspaceToken) {\n headers[\"X-Workspace-Authorization\"] = `Bearer ${workspaceToken}`;\n }\n\n // Dev-mode: inject actor headers directly so the service can authenticate the request\n if (isDevMode) {\n headers[\"x-actor-id\"] = \"00000000-0000-0000-0000-000000000002\";\n headers[\"x-actor-type\"] = \"user\";\n headers[\"x-workspace-id\"] =\n workspaceId ?? \"00000000-0000-0000-0000-000000000001\";\n headers[\"x-gateway-auth-token\"] =\n import.meta.env.VITE_DEV_GATEWAY_TOKEN ?? \"\";\n }\n\n // In dev mode (no tokens) connect directly to the export service to bypass gateway auth\n const serviceUrl = isDevMode\n ? (import.meta.env.VITE_EXPORT_SERVICE_URL ??\n \"http://localhost:3732/graphql\")\n : apiGatewayUrl\n ? `${apiGatewayUrl}/workspaces/graphql`\n : \"/workspaces/graphql\";\n\n const httpLink = new HttpLink({\n uri: serviceUrl,\n headers: Object.keys(headers).length > 0 ? headers : undefined,\n });\n\n // Error link: refresh workspace token on TOKEN_EXPIRED and retry the operation\n const errorLink = onError(({ error, operation, forward }) => {\n if (!error) return;\n\n const errors =\n \"errors\" in error && Array.isArray(error.errors) ? error.errors : [];\n const hasAuthError = hasSessionAuthErrors(errors);\n\n if (\n !getWorkspaceTokenRef.current ||\n !hasAuthError ||\n isRefreshingRef.current\n ) {\n if (error instanceof Error && isAuthNetworkError(error)) {\n redirectToLogin();\n }\n return;\n }\n\n return new Observable((observer) => {\n isRefreshingRef.current = true;\n getWorkspaceTokenRef.current!()\n .then((freshToken) => {\n isRefreshingRef.current = false;\n if (!freshToken) {\n redirectToLogin();\n observer.error(new Error(\"Failed to refresh workspace token\"));\n return;\n }\n // Inject the fresh token into the retry request\n operation.setContext(\n ({\n headers: existingHeaders = {},\n }: {\n headers?: Record<string, string>;\n }) => ({\n headers: {\n ...existingHeaders,\n \"X-Workspace-Authorization\": `Bearer ${freshToken}`,\n },\n }),\n );\n const subscriber = forward(operation).subscribe(observer);\n return () => subscriber.unsubscribe();\n })\n .catch((err: unknown) => {\n isRefreshingRef.current = false;\n observer.error(err);\n });\n });\n });\n\n // WebSocket link for subscriptions (replace http with ws)\n const wsUrl = serviceUrl.replace(/^http/, \"ws\");\n const wsLink = new GraphQLWsLink(\n createClient({\n url: wsUrl,\n connectionParams: Object.keys(headers).length > 0 ? headers : undefined,\n }),\n );\n\n // Split link: use WebSocket for subscriptions, HTTP for queries/mutations\n const splitLink = split(\n ({ query }) => {\n const definition = getMainDefinition(query);\n return (\n definition.kind === \"OperationDefinition\" &&\n definition.operation === \"subscription\"\n );\n },\n wsLink,\n from([errorLink, httpLink]),\n );\n\n return new ApolloClient({\n link: splitLink,\n cache: new InMemoryCache(),\n defaultOptions: {\n watchQuery: {\n fetchPolicy: \"cache-and-network\",\n },\n },\n });\n }, [\n apiGatewayUrl,\n authToken,\n workspaceToken,\n workspaceId,\n // getWorkspaceToken is intentionally omitted: it's accessed via ref so the\n // Apollo client is not destroyed on every render if the host passes a new\n // function reference each render cycle.\n ]);\n\n // Wrap content with QueryClientProvider and Context.Provider\n const content = (\n <QueryClientProvider client={queryClient}>\n <ExportContext.Provider value={contextValue}>\n {children}\n </ExportContext.Provider>\n </QueryClientProvider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // and ApolloProvider so we don't create another one. When standalone (no basePath),\n // use MemoryRouter and ApolloProvider.\n if (basePath) {\n // Integrated mode: shell already has BrowserRouter and ApolloProvider, don't create nested ones\n return content;\n }\n\n // Standalone mode: create MemoryRouter and ApolloProvider for independent operation\n return (\n <ApolloProvider client={apolloClient}>\n <MemoryRouter>{content}</MemoryRouter>\n </ApolloProvider>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ExportProvider.js","names":[],"sources":["../../../src/export/providers/ExportProvider.tsx"],"sourcesContent":["import type { FC, PropsWithChildren } from \"react\";\nimport { createContext, useContext, useMemo, useRef } from \"react\";\nimport { MemoryRouter } from \"react-router-dom\";\nimport {\n MutationCache,\n QueryClient,\n QueryClientProvider,\n} from \"@tanstack/react-query\";\nimport {\n ApolloClient,\n InMemoryCache,\n HttpLink,\n from,\n split,\n Observable,\n} from \"@apollo/client\";\nimport { getMainDefinition } from \"@apollo/client/utilities\";\nimport { GraphQLWsLink } from \"@apollo/client/link/subscriptions\";\nimport { createClient } from \"graphql-ws\";\nimport { onError } from \"@apollo/client/link/error\";\nimport { ApolloProvider } from \"@apollo/client/react\";\nimport {\n isAuthNetworkError,\n redirectToLogin,\n} from \"@burdenoff/fe-libs/shared/utils\";\nimport type { ExportRootProps } from \"../types\";\n\nfunction hasSessionAuthErrors(\n errors: ReadonlyArray<{\n message?: string;\n extensions?: Record<string, unknown>;\n }> = [],\n): boolean {\n return errors.some((error) => {\n const code =\n typeof error.extensions?.code === \"string\" ? error.extensions.code : \"\";\n const errorCode =\n typeof error.extensions?.errorCode === \"string\"\n ? error.extensions.errorCode\n : \"\";\n const message = (error.message ?? \"\").toLowerCase();\n\n return (\n code === \"UNAUTHENTICATED\" ||\n errorCode === \"TOKEN_EXPIRED\" ||\n message.includes(\"token_expired\") ||\n message.includes(\"token expired\") ||\n message.includes(\"session expired\") ||\n message.includes(\"unauthorized\") ||\n message.includes(\"unauthenticated\") ||\n message.includes(\"authentication required\") ||\n message.includes(\"workspace context missing\")\n );\n });\n}\n\n/**\n * Context value for Export microfrontend\n */\ntype ExportContextValue = ExportRootProps;\n\nconst ExportContext = createContext<ExportContextValue | null>(null);\n\n/**\n * Hook to access Export context\n */\nexport const useExport = () => {\n const context = useContext(ExportContext);\n if (!context) {\n throw new Error(\"useExport must be used within ExportProvider\");\n }\n return context;\n};\n\n/**\n * Provider component for Export microfrontend\n * Sets up routing, context, Apollo Client, and React Query client\n */\nexport const ExportProvider: FC<PropsWithChildren<ExportRootProps>> = ({\n children,\n basePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n organizationId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n eventBus,\n getWorkspaceToken,\n}) => {\n const contextValue: ExportContextValue = {\n basePath,\n navigate,\n currentUser,\n workspaceId,\n tenantId,\n organizationId,\n apiGatewayUrl,\n authToken,\n workspaceToken,\n eventBus,\n getWorkspaceToken,\n };\n\n // Track refreshing state across renders without triggering re-renders\n const isRefreshingRef = useRef(false);\n\n // Stable ref for getWorkspaceToken so it never invalidates the Apollo client memo\n const getWorkspaceTokenRef = useRef(getWorkspaceToken);\n getWorkspaceTokenRef.current = getWorkspaceToken;\n\n // Create QueryClient instance (memoized to avoid recreation on re-renders)\n const queryClient = useMemo(() => {\n const client = new QueryClient({\n mutationCache: new MutationCache({\n onSuccess: () => {\n void client.invalidateQueries();\n },\n }),\n defaultOptions: {\n queries: {\n retry: 1,\n refetchOnWindowFocus: false,\n staleTime: 5 * 60 * 1000, // 5 minutes\n },\n },\n });\n return client;\n }, []);\n\n // Create ApolloClient instance (memoized, recreated when tokens change)\n // The workspace gateway requires dual-token auth:\n // Authorization: Bearer <authToken> (platform identity)\n // X-Workspace-Authorization: Bearer <workspaceToken> (workspace access)\n // In dev mode (no tokens), the client adds actor headers directly and uses\n // x-gateway-auth-token so the service trusts them (TRUSTED_GATEWAY_TOKEN=dev-test-secret-2026).\n const apolloClient = useMemo(() => {\n const isDevMode = !authToken && !workspaceToken;\n const headers: Record<string, string> = {};\n\n if (authToken) {\n headers[\"Authorization\"] = `Bearer ${authToken}`;\n }\n if (workspaceToken) {\n headers[\"X-Workspace-Authorization\"] = `Bearer ${workspaceToken}`;\n }\n\n // Dev-mode: inject actor headers directly so the service can authenticate the request\n if (isDevMode) {\n headers[\"x-actor-id\"] = \"00000000-0000-0000-0000-000000000002\";\n headers[\"x-actor-type\"] = \"user\";\n headers[\"x-workspace-id\"] =\n workspaceId ?? \"00000000-0000-0000-0000-000000000001\";\n headers[\"x-gateway-auth-token\"] =\n import.meta.env.VITE_DEV_GATEWAY_TOKEN ?? \"\";\n }\n\n // In dev mode (no tokens) connect directly to the export service to bypass gateway auth\n const serviceUrl = isDevMode\n ? (import.meta.env.VITE_EXPORT_SERVICE_URL ??\n \"http://localhost:3732/graphql\")\n : apiGatewayUrl\n ? `${apiGatewayUrl}/workspaces/graphql`\n : \"/workspaces/graphql\";\n\n const httpLink = new HttpLink({\n uri: serviceUrl,\n headers: Object.keys(headers).length > 0 ? headers : undefined,\n });\n\n // Error link: refresh workspace token on TOKEN_EXPIRED and retry the operation\n const errorLink = onError(({ error, operation, forward }) => {\n if (!error) return;\n\n const errors =\n \"errors\" in error && Array.isArray(error.errors) ? error.errors : [];\n const hasAuthError = hasSessionAuthErrors(errors);\n\n if (\n !getWorkspaceTokenRef.current ||\n !hasAuthError ||\n isRefreshingRef.current\n ) {\n if (error instanceof Error && isAuthNetworkError(error)) {\n redirectToLogin();\n }\n return;\n }\n\n return new Observable((observer) => {\n isRefreshingRef.current = true;\n getWorkspaceTokenRef.current!()\n .then((freshToken) => {\n isRefreshingRef.current = false;\n if (!freshToken) {\n redirectToLogin();\n observer.error(new Error(\"Failed to refresh workspace token\"));\n return;\n }\n // Inject the fresh token into the retry request\n operation.setContext(\n ({\n headers: existingHeaders = {},\n }: {\n headers?: Record<string, string>;\n }) => ({\n headers: {\n ...existingHeaders,\n \"X-Workspace-Authorization\": `Bearer ${freshToken}`,\n },\n }),\n );\n const subscriber = forward(operation).subscribe(observer);\n return () => subscriber.unsubscribe();\n })\n .catch((err: unknown) => {\n isRefreshingRef.current = false;\n observer.error(err);\n });\n });\n });\n\n // WebSocket link for subscriptions (replace http with ws)\n const wsUrl = serviceUrl.replace(/^http/, \"ws\");\n const wsLink = new GraphQLWsLink(\n createClient({\n url: wsUrl,\n connectionParams: Object.keys(headers).length > 0 ? headers : undefined,\n }),\n );\n\n // Split link: use WebSocket for subscriptions, HTTP for queries/mutations\n const splitLink = split(\n ({ query }) => {\n const definition = getMainDefinition(query);\n return (\n definition.kind === \"OperationDefinition\" &&\n definition.operation === \"subscription\"\n );\n },\n wsLink,\n from([errorLink, httpLink]),\n );\n\n return new ApolloClient({\n link: splitLink,\n cache: new InMemoryCache(),\n defaultOptions: {\n watchQuery: {\n fetchPolicy: \"cache-and-network\",\n },\n },\n });\n }, [\n apiGatewayUrl,\n authToken,\n workspaceToken,\n workspaceId,\n // getWorkspaceToken is intentionally omitted: it's accessed via ref so the\n // Apollo client is not destroyed on every render if the host passes a new\n // function reference each render cycle.\n ]);\n\n // Wrap content with QueryClientProvider and Context.Provider\n const content = (\n <QueryClientProvider client={queryClient}>\n <ExportContext.Provider value={contextValue}>\n {children}\n </ExportContext.Provider>\n </QueryClientProvider>\n );\n\n // When integrated into a shell (basePath provided), the shell already has a router\n // and ApolloProvider so we don't create another one. When standalone (no basePath),\n // use MemoryRouter and ApolloProvider.\n if (basePath) {\n // Integrated mode: shell already has BrowserRouter and ApolloProvider, don't create nested ones\n return content;\n }\n\n // Standalone mode: create MemoryRouter and ApolloProvider for independent operation\n return (\n <ApolloProvider client={apolloClient}>\n <MemoryRouter>{content}</MemoryRouter>\n </ApolloProvider>\n );\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-export",
|
|
3
|
-
"version": "2026.
|
|
3
|
+
"version": "2026.623.2",
|
|
4
4
|
"description": "Burdenoff Export Microfrontend - Data export management interface",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@apollo/client": "^4.0.11",
|
|
43
|
-
"@burdenoff/fe-libs": "2026.
|
|
43
|
+
"@burdenoff/fe-libs": "2026.623.6",
|
|
44
44
|
"@tanstack/react-query": "^5.90.16",
|
|
45
45
|
"clsx": "^2.1.1",
|
|
46
46
|
"graphql": "^16.10.0",
|