@gooddata/sdk-ui-pluggable-host 11.57.0-alpha.6 → 11.57.0
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":"chunkReloadGuard.d.ts","sourceRoot":"","sources":["../../src/lib/chunkReloadGuard.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"chunkReloadGuard.d.ts","sourceRoot":"","sources":["../../src/lib/chunkReloadGuard.ts"],"names":[],"mappings":"AAkCA;;;;;GAKG;AACH,MAAM,WAAW,qBAAqB;IAClC,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,UAAU,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,CAAC,IAAI,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAsC7E;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,wBAAwB,GAAG,SAAS,GAAG,IAAI,CAEhG;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,UAAU,CAAC;AAEhD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CA4DzD;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,IAAI,IAAI,CAwBjD;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACnC,4DAA4D;IAC5D,GAAG,EAAE,MAAM,CAAC;IACZ,+CAA+C;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,eAAe,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9C;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CAAC,EAClC,GAAG,EACH,UAAuB,EACvB,eAAe,GAClB,EAAE,sBAAsB,GAAG,IAAI,CAsE/B"}
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
// (C) 2026 GoodData Corporation
|
|
2
2
|
const RELOAD_FLAG_KEY = "gd-chunk-reload-guard";
|
|
3
3
|
const RELOAD_LOOP_WINDOW_MS = 30_000;
|
|
4
|
+
/**
|
|
5
|
+
* How long one recovery attempt speaks for any further stale chunks in the same document.
|
|
6
|
+
*
|
|
7
|
+
* A redeploy usually takes several imports away at once, and each failure raises its own event.
|
|
8
|
+
* Without a bound, a user who answers the unload prompt with "stay" is asked again immediately,
|
|
9
|
+
* once per failed import. The window is short on purpose: it must cover a burst of simultaneous
|
|
10
|
+
* failures without reaching the next thing the user does, so that someone who saves and carries
|
|
11
|
+
* on still gets their recovery.
|
|
12
|
+
*/
|
|
13
|
+
const RELOAD_COALESCE_WINDOW_MS = 1_000;
|
|
4
14
|
let preloadErrorHandlerInstalled = false;
|
|
5
15
|
let versionWatcherInstalled = false;
|
|
6
16
|
let staleChunkReloadListener;
|
|
17
|
+
let lastReloadAttemptAt;
|
|
18
|
+
let isReloadAttemptUnresolved = false;
|
|
7
19
|
function readReloadFlag() {
|
|
8
20
|
try {
|
|
9
21
|
const raw = sessionStorage.getItem(RELOAD_FLAG_KEY);
|
|
@@ -79,10 +91,31 @@ export function reloadForStaleChunks(reason) {
|
|
|
79
91
|
}
|
|
80
92
|
const currentHash = getCurrentHash();
|
|
81
93
|
const previous = readReloadFlag();
|
|
82
|
-
|
|
94
|
+
// A redeploy strips many chunks at once, so the failures arrive in a burst. One attempt
|
|
95
|
+
// speaks for all of them; without this the user is asked to leave once per failed import.
|
|
96
|
+
//
|
|
97
|
+
// An attempt stays unresolved while the browser's unload prompt is up, because that prompt
|
|
98
|
+
// blocks this thread: elapsed time alone would count the seconds the user spends reading it
|
|
99
|
+
// and expire the window exactly when the queued events are about to drain.
|
|
100
|
+
if (isReloadAttemptUnresolved) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (lastReloadAttemptAt !== undefined && Date.now() - lastReloadAttemptAt < RELOAD_COALESCE_WINDOW_MS) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
// Still running after an attempt means the navigation was cancelled — by a `beforeunload`
|
|
107
|
+
// handler the user answered with "stay". The flag is then this document's own, and must not
|
|
108
|
+
// stand in for a reload that never happened. The loop guard is unaffected: it protects the
|
|
109
|
+
// case where a reload DID replace the document, whose fresh module state starts undefined.
|
|
110
|
+
const staleFlagIsOurOwn = lastReloadAttemptAt !== undefined;
|
|
111
|
+
if (!staleFlagIsOurOwn &&
|
|
112
|
+
previous?.hash === currentHash &&
|
|
113
|
+
Date.now() - previous.at < RELOAD_LOOP_WINDOW_MS) {
|
|
83
114
|
console.error(`[host-runtime/chunk-reload-guard] Skipping reload for "${reason}" — already reloaded for the same build (${currentHash}) within ${RELOAD_LOOP_WINDOW_MS}ms.`);
|
|
84
115
|
return;
|
|
85
116
|
}
|
|
117
|
+
isReloadAttemptUnresolved = true;
|
|
118
|
+
lastReloadAttemptAt = Date.now();
|
|
86
119
|
writeReloadFlag(currentHash);
|
|
87
120
|
console.warn(`[host-runtime/chunk-reload-guard] Reloading page due to: ${reason}`);
|
|
88
121
|
try {
|
|
@@ -95,6 +128,13 @@ export function reloadForStaleChunks(reason) {
|
|
|
95
128
|
const target = new URL(window.location.href);
|
|
96
129
|
target.searchParams.set(STALE_CHUNK_RELOAD_PARAM, String(Date.now()));
|
|
97
130
|
window.location.replace(target.toString());
|
|
131
|
+
// Reached only if the navigation did not happen — a `beforeunload` the user answered with
|
|
132
|
+
// "stay". This runs after that prompt closes, since the prompt blocks the thread, so the
|
|
133
|
+
// coalescing window starts from the moment the user is back rather than from the attempt.
|
|
134
|
+
window.setTimeout(() => {
|
|
135
|
+
isReloadAttemptUnresolved = false;
|
|
136
|
+
lastReloadAttemptAt = Date.now();
|
|
137
|
+
}, 0);
|
|
98
138
|
}
|
|
99
139
|
/**
|
|
100
140
|
* Installs a window listener for `vite:preloadError`. When fired (e.g. a chunk has
|
|
@@ -120,6 +160,12 @@ export function installPreloadErrorHandler() {
|
|
|
120
160
|
// error from asPluggableApp instead of a preload failure. If the loop guard
|
|
121
161
|
// suppresses the reload, the original rejection still gives the caller a
|
|
122
162
|
// truthful failure to handle.
|
|
163
|
+
//
|
|
164
|
+
// Reload synchronously, from inside the dispatch. Deferring it — to a timer or even a
|
|
165
|
+
// microtask — hands control back to the failing import first, and a rejected `React.lazy`
|
|
166
|
+
// with no error boundary above it unmounts the tree that installed the page's
|
|
167
|
+
// `beforeunload` guard. The reload would then discard unsaved work unasked, which is the
|
|
168
|
+
// whole thing this guard exists to prevent.
|
|
123
169
|
reloadForStaleChunks(`vite:preloadError (${preloadEvent.payload?.message ?? "unknown"})`);
|
|
124
170
|
});
|
|
125
171
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HostChrome.d.ts","sourceRoot":"","sources":["../../src/ui/HostChrome.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuC,KAAK,SAAS,EAAwB,MAAM,OAAO,CAAC;AAElG,OAAO,EACH,KAAK,iBAAiB,EAEtB,KAAK,gCAAgC,EACxC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACxB,MAAM,2CAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"HostChrome.d.ts","sourceRoot":"","sources":["../../src/ui/HostChrome.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAuC,KAAK,SAAS,EAAwB,MAAM,OAAO,CAAC;AAElG,OAAO,EACH,KAAK,iBAAiB,EAEtB,KAAK,gCAAgC,EACxC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACH,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,gBAAgB,EACxB,MAAM,2CAA2C,CAAC;AA8BnD,OAAO,mBAAmB,CAAC;AAK3B,OAAO,0CAA0C,CAAC;AAClD,OAAO,6CAA6C,CAAC;AACrD,OAAO,sDAAsD,CAAC;AAC9D,OAAO,0DAA0D,CAAC;AAiBlE,MAAM,WAAW,gBAAgB;IAC7B,GAAG,EAAE,gBAAgB,CAAC;IACtB,oBAAoB,EAAE,gCAAgC,EAAE,CAAC;IACzD,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,YAAY,CAAC,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAC1C;;;OAGG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,4FAA4F;IAC5F,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,CACf,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,iBAAiB,EAC/B,YAAY,CAAC,EAAE,OAAO,EACtB,kBAAkB,CAAC,EAAE,OAAO,KAC3B,IAAI,CAAC;IACV;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,wBAAgB,UAAU,CAAC,EACvB,GAAG,EACH,oBAAoB,EACpB,QAAQ,EACR,UAAU,EACV,SAAS,EAAE,UAAU,EACrB,aAAa,EACb,YAAmB,EACnB,YAAoB,EACpB,UAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,QAAQ,GACX,EAAE,gBAAgB,2CAiPlB"}
|
package/esm/ui/HostChrome.js
CHANGED
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
3
3
|
import { useCallback, useMemo } from "react";
|
|
4
4
|
import { BackendProvider, resolveLocale } from "@gooddata/sdk-ui";
|
|
5
5
|
import { AppHeaderNotifications } from "@gooddata/sdk-ui-application-header";
|
|
6
|
-
import { AppHeader, DocumentHeader, ToastsCenterContextProvider, generateHeaderAccountMenuItems, generateHeaderStaticHelpMenuItems, } from "@gooddata/sdk-ui-kit";
|
|
6
|
+
import { AppHeader, DocumentHeader, ToastsCenterContextProvider, generateHeaderAccountMenuItems, generateHeaderCommonHelpMenuItems, generateHeaderStaticHelpMenuItems, } from "@gooddata/sdk-ui-kit";
|
|
7
7
|
import { defaultHeaderTheme } from "@gooddata/sdk-ui-theme-provider";
|
|
8
8
|
import defaultLogoUrl from "../assets/logo-white.svg";
|
|
9
9
|
import { getAppLifecycleCallbacks } from "../loader/pluggableApplicationsLoader.js";
|
|
@@ -58,12 +58,15 @@ export function HostChrome({ ctx, resolvedApplications, pathname, onNavigate, on
|
|
|
58
58
|
telemetry: shellTelemetry,
|
|
59
59
|
});
|
|
60
60
|
const { menuItemsGroups, messages: appMessages } = useMemo(() => buildAppMenu(resolvedApplications, ctx, pathname, ctx.preferredLocale), [resolvedApplications, ctx, pathname]);
|
|
61
|
-
// The default help menu links exclusively to GoodData resources (documentation,
|
|
62
|
-
// community, Slack). When white-labeling is enabled (the "Hide links to GoodData
|
|
63
|
-
// setting) those must not leak into the branded header, so the host renders no
|
|
64
|
-
// menu.
|
|
61
|
+
// The default help menu links exclusively to GoodData resources (product documentation,
|
|
62
|
+
// university, community, Slack). When white-labeling is enabled (the "Hide links to GoodData
|
|
63
|
+
// documentation" setting) those must not leak into the branded header, so the host renders no
|
|
64
|
+
// default help menu. An app that needs app-specific links replaces the whole menu through
|
|
65
|
+
// headerOptions; an app that pushes nothing gets these product-wide links. (LX-2960)
|
|
65
66
|
const helpMenuItems = useMemo(() => headerOptions?.helpMenuItems ??
|
|
66
|
-
(ctx.whiteLabeling?.enabled
|
|
67
|
+
(ctx.whiteLabeling?.enabled
|
|
68
|
+
? []
|
|
69
|
+
: [...generateHeaderCommonHelpMenuItems(), ...generateHeaderStaticHelpMenuItems()]), [headerOptions, ctx.whiteLabeling?.enabled]);
|
|
67
70
|
const accountMenuItems = useMemo(() => generateHeaderAccountMenuItems(ctx.workspacePermissions ?? {}, features.workspaceId, ctx.settings), [ctx.workspacePermissions, features.workspaceId, ctx.settings]);
|
|
68
71
|
const handleMenuItemClick = useCallback((item, e) => {
|
|
69
72
|
e?.preventDefault();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gooddata/sdk-ui-pluggable-host",
|
|
3
|
-
"version": "11.57.0
|
|
3
|
+
"version": "11.57.0",
|
|
4
4
|
"description": "GoodData SDK runtime for hosting pluggable applications — registry, loader, routing, platform context, default UI chrome",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "GoodData Corporation",
|
|
@@ -29,20 +29,20 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@module-federation/runtime": "2.6.0",
|
|
31
31
|
"lodash-es": "^4.17.23",
|
|
32
|
-
"@gooddata/sdk-backend-base": "11.57.0
|
|
33
|
-
"@gooddata/sdk-
|
|
34
|
-
"@gooddata/sdk-
|
|
35
|
-
"@gooddata/sdk-model": "11.57.0
|
|
36
|
-
"@gooddata/sdk-
|
|
37
|
-
"@gooddata/sdk-
|
|
38
|
-
"@gooddata/sdk-ui-application-header": "11.57.0
|
|
39
|
-
"@gooddata/sdk-
|
|
40
|
-
"@gooddata/sdk-
|
|
41
|
-
"@gooddata/sdk-ui-
|
|
42
|
-
"@gooddata/sdk-ui-
|
|
43
|
-
"@gooddata/sdk-ui-
|
|
44
|
-
"@gooddata/
|
|
45
|
-
"@gooddata/
|
|
32
|
+
"@gooddata/sdk-backend-base": "11.57.0",
|
|
33
|
+
"@gooddata/sdk-embedding": "11.57.0",
|
|
34
|
+
"@gooddata/sdk-backend-spi": "11.57.0",
|
|
35
|
+
"@gooddata/sdk-pluggable-application-model": "11.57.0",
|
|
36
|
+
"@gooddata/sdk-ui": "11.57.0",
|
|
37
|
+
"@gooddata/sdk-backend-tiger": "11.57.0",
|
|
38
|
+
"@gooddata/sdk-ui-application-header": "11.57.0",
|
|
39
|
+
"@gooddata/sdk-model": "11.57.0",
|
|
40
|
+
"@gooddata/sdk-ui-gen-ai": "11.57.0",
|
|
41
|
+
"@gooddata/sdk-ui-semantic-search": "11.57.0",
|
|
42
|
+
"@gooddata/sdk-ui-theme-provider": "11.57.0",
|
|
43
|
+
"@gooddata/sdk-ui-kit": "11.57.0",
|
|
44
|
+
"@gooddata/util": "11.57.0",
|
|
45
|
+
"@gooddata/sdk-ui-ext": "11.57.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@microsoft/api-documenter": "7.30.10",
|
|
@@ -81,8 +81,8 @@
|
|
|
81
81
|
"vite": "8.0.16",
|
|
82
82
|
"vitest": "4.1.8",
|
|
83
83
|
"vitest-dom": "0.1.1",
|
|
84
|
-
"@gooddata/
|
|
85
|
-
"@gooddata/
|
|
84
|
+
"@gooddata/oxlint-config": "11.57.0",
|
|
85
|
+
"@gooddata/eslint-config": "11.57.0"
|
|
86
86
|
},
|
|
87
87
|
"peerDependencies": {
|
|
88
88
|
"react": ">=18.3.1",
|