@gooddata/sdk-ui-pluggable-host 11.62.0-alpha.2 → 11.62.0-alpha.4
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.
- package/esm/components/HostUiContainer.d.ts.map +1 -1
- package/esm/components/HostUiContainer.js +4 -2
- package/esm/components/Root.scss +12 -0
- package/esm/components/useRedirectTarget.d.ts.map +1 -1
- package/esm/components/useRedirectTarget.js +9 -0
- package/esm/platformContext/notAuthenticatedGrace.d.ts +24 -0
- package/esm/platformContext/notAuthenticatedGrace.d.ts.map +1 -0
- package/esm/platformContext/notAuthenticatedGrace.js +39 -0
- package/esm/platformContext/tigerNotAuthenticatedHandler.d.ts +2 -1
- package/esm/platformContext/tigerNotAuthenticatedHandler.d.ts.map +1 -1
- package/esm/platformContext/tigerNotAuthenticatedHandler.js +8 -8
- package/esm/platformContext/useLoadPlatformContext.d.ts.map +1 -1
- package/esm/platformContext/useLoadPlatformContext.js +13 -2
- package/esm/platformContext/useWorkspacePermissions.d.ts.map +1 -1
- package/esm/platformContext/useWorkspacePermissions.js +12 -0
- package/esm/platformContext/useWorkspaceSettings.d.ts.map +1 -1
- package/esm/platformContext/useWorkspaceSettings.js +9 -1
- package/package.json +20 -20
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HostUiContainer.d.ts","sourceRoot":"","sources":["../../src/components/HostUiContainer.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,EAA0B,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAGH,KAAK,gBAAgB,EAExB,MAAM,2CAA2C,CAAC;AAkBnD,OAAO,wBAAwB,CAAC;AAOhC,MAAM,WAAW,qBAAqB;IAClC,GAAG,EAAE,gBAAgB,CAAC;IACtB,IAAI,EAAE,gCAAgC,EAAE,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,gBAAgB,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACtD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,EAAE,qBAAqB,
|
|
1
|
+
{"version":3,"file":"HostUiContainer.d.ts","sourceRoot":"","sources":["../../src/components/HostUiContainer.tsx"],"names":[],"mappings":"AAMA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAErD,OAAO,EAA0B,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AACpG,OAAO,EAGH,KAAK,gBAAgB,EAExB,MAAM,2CAA2C,CAAC;AAkBnD,OAAO,wBAAwB,CAAC;AAOhC,MAAM,WAAW,qBAAqB;IAClC,GAAG,EAAE,gBAAgB,CAAC;IACtB,IAAI,EAAE,gCAAgC,EAAE,CAAC;IACzC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,gBAAgB,CAAC;IACjC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACtD;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,EAAE,qBAAqB,2CA8XtG"}
|
|
@@ -128,11 +128,13 @@ export function HostUiContainer({ ctx, apps, pathname, routerNavigate, onError }
|
|
|
128
128
|
const navigate = useCallback((url) => {
|
|
129
129
|
runGuardedNavigation({
|
|
130
130
|
url,
|
|
131
|
-
|
|
131
|
+
// The page's, not the router's: an application moving its own router leaves the host
|
|
132
|
+
// router on the path it last navigated to.
|
|
133
|
+
currentPathname: window.location.pathname,
|
|
132
134
|
guardRef: appNavigationRequestRef,
|
|
133
135
|
navigate: (target) => void navigateRef.current(target),
|
|
134
136
|
});
|
|
135
|
-
}, [navigateRef
|
|
137
|
+
}, [navigateRef]);
|
|
136
138
|
const replace = useCallback((url) => {
|
|
137
139
|
void navigateRef.current(url, { replace: true });
|
|
138
140
|
}, [navigateRef]);
|
package/esm/components/Root.scss
CHANGED
|
@@ -9,6 +9,18 @@
|
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
&__error {
|
|
12
|
+
min-height: 100vh;
|
|
13
|
+
box-sizing: border-box;
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
12
18
|
padding: 2rem;
|
|
19
|
+
text-align: center;
|
|
13
20
|
}
|
|
14
21
|
}
|
|
22
|
+
|
|
23
|
+
// The app slot sits below the host header, so a viewport-high fallback would overflow it.
|
|
24
|
+
.gd-host-chrome__app-container > .gd-host-root__error {
|
|
25
|
+
min-height: 100%;
|
|
26
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useRedirectTarget.d.ts","sourceRoot":"","sources":["../../src/components/useRedirectTarget.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useRedirectTarget.d.ts","sourceRoot":"","sources":["../../src/components/useRedirectTarget.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,KAAK,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAQlF,MAAM,MAAM,mBAAmB,GACzB;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GACpB;IAAE,KAAK,EAAE,QAAQ,CAAA;CAAE,GACnB;IAAE,KAAK,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAClC;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,GACtB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC;;GAEG;AACH,wBAAgB,iBAAiB,CAC7B,IAAI,EAAE,gCAAgC,EAAE,EACxC,GAAG,EAAE,gBAAgB,EACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,MAAM,GAChB,mBAAmB,CAsErB"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
// (C) 2026 GoodData Corporation
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
+
import { isNotAuthenticated } from "@gooddata/sdk-backend-spi";
|
|
3
4
|
import { debugLog } from "../debug.js";
|
|
4
5
|
import { AppNotFoundError, resolveRedirectTarget } from "../loader/redirectLogic.js";
|
|
5
6
|
import { getBackend } from "../platformContext/backend.js";
|
|
7
|
+
import { afterNotAuthenticatedGrace } from "../platformContext/notAuthenticatedGrace.js";
|
|
6
8
|
import { getWorkspaceAccess } from "../platformContext/workspaceAccess.js";
|
|
7
9
|
/**
|
|
8
10
|
* Resolves the redirect target for the current URL and permission context.
|
|
@@ -11,6 +13,7 @@ export function useRedirectTarget(apps, ctx, pathname, search) {
|
|
|
11
13
|
const [redirectState, setRedirectState] = useState({ state: "loading" });
|
|
12
14
|
useEffect(() => {
|
|
13
15
|
let cancelled = false;
|
|
16
|
+
let cancelGrace;
|
|
14
17
|
// Preserve "render" state during re-resolution so that HostUiContainer stays mounted
|
|
15
18
|
// while the async redirect check runs. Other states (loading, error, not-found) reset to loading.
|
|
16
19
|
setRedirectState((prev) => (prev.state === "render" ? prev : { state: "loading" }));
|
|
@@ -54,11 +57,17 @@ export function useRedirectTarget(apps, ctx, pathname, search) {
|
|
|
54
57
|
else {
|
|
55
58
|
const error = e instanceof Error ? e.message : "Unknown redirect error.";
|
|
56
59
|
debugLog(`[host-app/redirect] useRedirectTarget: unexpected error → ${error}`);
|
|
60
|
+
// Stay "loading" while the backend redirects to login on a 401.
|
|
61
|
+
if (isNotAuthenticated(e)) {
|
|
62
|
+
cancelGrace = afterNotAuthenticatedGrace(() => setRedirectState({ state: "error", error }));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
57
65
|
setRedirectState({ state: "error", error });
|
|
58
66
|
}
|
|
59
67
|
});
|
|
60
68
|
return () => {
|
|
61
69
|
cancelled = true;
|
|
70
|
+
cancelGrace?.();
|
|
62
71
|
};
|
|
63
72
|
}, [apps, pathname, search, ctx]);
|
|
64
73
|
return redirectState;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How long a 401 is treated as "login redirect in progress" before it is shown as an error.
|
|
3
|
+
*
|
|
4
|
+
* @remarks
|
|
5
|
+
* The backend's not-authenticated handler navigates to the login page on every 401, so the error
|
|
6
|
+
* is normally never seen. The error surfaces only when no navigation happens, e.g. the 401 carries
|
|
7
|
+
* no authentication flow or an embedding host installed a handler that does not redirect.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export declare const NOT_AUTHENTICATED_GRACE_MS = 10000;
|
|
12
|
+
/**
|
|
13
|
+
* Calls `onExpired` once the grace period passes. Returns a function that cancels the call.
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export declare function afterNotAuthenticatedGrace(onExpired: () => void): () => void;
|
|
18
|
+
/**
|
|
19
|
+
* Resolves once the grace period passes, or immediately when `signal` aborts.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export declare function waitForNotAuthenticatedGrace(signal: AbortSignal): Promise<void>;
|
|
24
|
+
//# sourceMappingURL=notAuthenticatedGrace.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"notAuthenticatedGrace.d.ts","sourceRoot":"","sources":["../../src/platformContext/notAuthenticatedGrace.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,eAAO,MAAM,0BAA0B,QAAS,CAAC;AAEjD;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,SAAS,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAG5E;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB/E"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// (C) 2026 GoodData Corporation
|
|
2
|
+
/**
|
|
3
|
+
* How long a 401 is treated as "login redirect in progress" before it is shown as an error.
|
|
4
|
+
*
|
|
5
|
+
* @remarks
|
|
6
|
+
* The backend's not-authenticated handler navigates to the login page on every 401, so the error
|
|
7
|
+
* is normally never seen. The error surfaces only when no navigation happens, e.g. the 401 carries
|
|
8
|
+
* no authentication flow or an embedding host installed a handler that does not redirect.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export const NOT_AUTHENTICATED_GRACE_MS = 10_000;
|
|
13
|
+
/**
|
|
14
|
+
* Calls `onExpired` once the grace period passes. Returns a function that cancels the call.
|
|
15
|
+
*
|
|
16
|
+
* @internal
|
|
17
|
+
*/
|
|
18
|
+
export function afterNotAuthenticatedGrace(onExpired) {
|
|
19
|
+
const timer = setTimeout(onExpired, NOT_AUTHENTICATED_GRACE_MS);
|
|
20
|
+
return () => clearTimeout(timer);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Resolves once the grace period passes, or immediately when `signal` aborts.
|
|
24
|
+
*
|
|
25
|
+
* @internal
|
|
26
|
+
*/
|
|
27
|
+
export function waitForNotAuthenticatedGrace(signal) {
|
|
28
|
+
return new Promise((resolve) => {
|
|
29
|
+
if (signal.aborted) {
|
|
30
|
+
resolve();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const cancel = afterNotAuthenticatedGrace(resolve);
|
|
34
|
+
signal.addEventListener("abort", () => {
|
|
35
|
+
cancel();
|
|
36
|
+
resolve();
|
|
37
|
+
}, { once: true });
|
|
38
|
+
});
|
|
39
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type IAuthenticationContext, type NotAuthenticated } from "@gooddata/sdk-backend-spi";
|
|
2
|
+
import { type RedirectToTigerAuthenticationHandler } from "@gooddata/sdk-backend-tiger";
|
|
2
3
|
/** Not authenticated handler will be called every time 401 is returned from Tiger backend. */
|
|
3
|
-
export declare function createNotAuthenticatedHandler(externalProviderId?: string): (ctx: IAuthenticationContext, err: NotAuthenticated) => void;
|
|
4
|
+
export declare function createNotAuthenticatedHandler(externalProviderId?: string, redirectHandler?: RedirectToTigerAuthenticationHandler): (ctx: IAuthenticationContext, err: NotAuthenticated) => void;
|
|
4
5
|
//# sourceMappingURL=tigerNotAuthenticatedHandler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tigerNotAuthenticatedHandler.d.ts","sourceRoot":"","sources":["../../src/platformContext/tigerNotAuthenticatedHandler.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,sBAAsB,EAAE,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"tigerNotAuthenticatedHandler.d.ts","sourceRoot":"","sources":["../../src/platformContext/tigerNotAuthenticatedHandler.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,sBAAsB,EAAE,KAAK,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAC/F,OAAO,EACH,KAAK,oCAAoC,EAG5C,MAAM,6BAA6B,CAAC;AAErC,8FAA8F;AAE9F,wBAAgB,6BAA6B,CACzC,kBAAkB,CAAC,EAAE,MAAM,EAC3B,eAAe,GAAE,oCAEkB,GACpC,CAAC,GAAG,EAAE,sBAAsB,EAAE,GAAG,EAAE,gBAAgB,KAAK,IAAI,CAU9D"}
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
import { throttle } from "lodash-es";
|
|
3
3
|
import { createRedirectToTigerAuthenticationWithParams, redirectToTigerAuthentication, } from "@gooddata/sdk-backend-tiger";
|
|
4
4
|
/** Not authenticated handler will be called every time 401 is returned from Tiger backend. */
|
|
5
|
-
export function createNotAuthenticatedHandler(externalProviderId
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
leading:
|
|
11
|
-
trailing:
|
|
5
|
+
export function createNotAuthenticatedHandler(externalProviderId, redirectHandler = externalProviderId
|
|
6
|
+
? createRedirectToTigerAuthenticationWithParams({ externalProviderId })
|
|
7
|
+
: redirectToTigerAuthentication) {
|
|
8
|
+
// Boot fires several requests in parallel. Redirect on the first 401 and ignore the rest.
|
|
9
|
+
const throttledRedirectHandler = throttle(redirectHandler, 500, {
|
|
10
|
+
leading: true,
|
|
11
|
+
trailing: false,
|
|
12
12
|
});
|
|
13
13
|
return (ctx, err) => {
|
|
14
|
-
|
|
14
|
+
throttledRedirectHandler(ctx, err);
|
|
15
15
|
};
|
|
16
16
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useLoadPlatformContext.d.ts","sourceRoot":"","sources":["../../src/platformContext/useLoadPlatformContext.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useLoadPlatformContext.d.ts","sourceRoot":"","sources":["../../src/platformContext/useLoadPlatformContext.ts"],"names":[],"mappings":"AAQA,OAAO,EAA2B,KAAK,gBAAgB,EAAE,MAAM,2CAA2C,CAAC;AAO3G,OAAO,EACH,KAAK,6BAA6B,EAGrC,MAAM,0BAA0B,CAAC;AAElC,OAAO,EACH,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAElC,MAAM,YAAY,CAAC;AAYpB;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,IAAI,0BAA0B,CAAC,gBAAgB,CAAC,CA6JrF;AAED;;;;GAIG;AACH,MAAM,WAAW,+BAA+B;IAC5C,YAAY,CAAC,SAAS,EAAE,6BAA6B,GAAG,IAAI,CAAC;IAC7D,SAAS,EAAE,MAAM,QAAQ,CAAC,0BAA0B,CAAC,uBAAuB,CAAC,CAAC,CAAC;IAC/E,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;IAChD,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AA4ED;;GAEG;AACH,eAAO,MAAM,8BAA8B,EAAE,+BACA,CAAC;AAE9C;;;;;;GAMG;AACH,wBAAgB,gCAAgC,CAAC,SAAS,EAAE,6BAA6B,GAAG,IAAI,CAE/F"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
// (C) 2026 GoodData Corporation
|
|
2
2
|
import { useEffect, useMemo, useState, useSyncExternalStore } from "react";
|
|
3
3
|
import { useLocation } from "react-router";
|
|
4
|
+
import { isNotAuthenticated } from "@gooddata/sdk-backend-spi";
|
|
4
5
|
import { isLocale } from "@gooddata/sdk-model";
|
|
5
6
|
import { isProduction } from "../lib/isProduction.js";
|
|
6
7
|
import { getApplicationScopeFromPath, getWorkspaceIdFromPath } from "../loader/routing.js";
|
|
7
8
|
import { resolveAvailableWorkspaceApplications } from "../registry/pluggableApplicationsRegistry.js";
|
|
8
9
|
import { getBackend } from "./backend.js";
|
|
9
10
|
import { HostApplicationDisabledError, loadPlatformContext, } from "./loadPlatformContext.js";
|
|
11
|
+
import { waitForNotAuthenticatedGrace } from "./notAuthenticatedGrace.js";
|
|
10
12
|
import { useWorkspaceColorPalette } from "./useWorkspaceColorPalette.js";
|
|
11
13
|
import { useWorkspacePermissions } from "./useWorkspacePermissions.js";
|
|
12
14
|
import { useWorkspaceSettings } from "./useWorkspaceSettings.js";
|
|
@@ -171,12 +173,13 @@ class BackendPlatformContextProviderClass {
|
|
|
171
173
|
this._setResult(this._loadingStateMemo);
|
|
172
174
|
}
|
|
173
175
|
this._abortController = new AbortController();
|
|
176
|
+
const signal = this._abortController.signal;
|
|
174
177
|
try {
|
|
175
178
|
if (shouldWaitForInjectedApiToken()) {
|
|
176
|
-
await waitForInjectedApiToken(
|
|
179
|
+
await waitForInjectedApiToken(signal);
|
|
177
180
|
}
|
|
178
181
|
const ctx = await loadPlatformContext({
|
|
179
|
-
signal
|
|
182
|
+
signal,
|
|
180
183
|
callbacks: this._callbacks,
|
|
181
184
|
});
|
|
182
185
|
if (!this._abortController.signal.aborted) {
|
|
@@ -191,6 +194,14 @@ class BackendPlatformContextProviderClass {
|
|
|
191
194
|
if (e instanceof DOMException && e.name === "AbortError") {
|
|
192
195
|
return;
|
|
193
196
|
}
|
|
197
|
+
// The backend redirects to login on a 401. Keep the loader up so the error does not
|
|
198
|
+
// flash before the browser leaves the page.
|
|
199
|
+
if (isNotAuthenticated(e)) {
|
|
200
|
+
await waitForNotAuthenticatedGrace(signal);
|
|
201
|
+
if (signal.aborted) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
194
205
|
console.error("[host-runtime/platform-context] Failed to load platform context.", e);
|
|
195
206
|
const message = e instanceof Error ? e.message : "Unknown platform context error.";
|
|
196
207
|
if (!this._abortController.signal.aborted) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWorkspacePermissions.d.ts","sourceRoot":"","sources":["../../src/platformContext/useWorkspacePermissions.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,kBAAkB,
|
|
1
|
+
{"version":3,"file":"useWorkspacePermissions.d.ts","sourceRoot":"","sources":["../../src/platformContext/useWorkspacePermissions.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,kBAAkB,EAAsB,MAAM,2BAA2B,CAAC;AACxF,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAKjE,KAAK,yBAAyB,GACxB;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACjB;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GACpB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,WAAW,EAAE,qBAAqB,CAAA;CAAE,GACtD;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,GACtB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAYxC;;;;;;;;GAQG;AACH,wBAAgB,uBAAuB,CACnC,OAAO,EAAE,kBAAkB,GAAG,SAAS,EACvC,WAAW,EAAE,MAAM,GAAG,SAAS,GAChC,yBAAyB,CAuE3B"}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
// (C) 2026 GoodData Corporation
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
+
import { isNotAuthenticated } from "@gooddata/sdk-backend-spi";
|
|
4
|
+
import { afterNotAuthenticatedGrace } from "./notAuthenticatedGrace.js";
|
|
3
5
|
import { classifyWorkspaceAccessError } from "./workspaceAccess.js";
|
|
4
6
|
const IDLE = { state: "idle" };
|
|
5
7
|
const LOADING = { state: "loading" };
|
|
@@ -26,6 +28,7 @@ export function useWorkspacePermissions(backend, workspaceId) {
|
|
|
26
28
|
return;
|
|
27
29
|
}
|
|
28
30
|
let cancelled = false;
|
|
31
|
+
let cancelGrace;
|
|
29
32
|
setSnapshot({ backend, workspaceId, state: LOADING });
|
|
30
33
|
const update = (state) => {
|
|
31
34
|
if (!cancelled) {
|
|
@@ -49,10 +52,19 @@ export function useWorkspacePermissions(backend, workspaceId) {
|
|
|
49
52
|
return;
|
|
50
53
|
}
|
|
51
54
|
const error = e instanceof Error ? e.message : "Unknown error loading workspace permissions.";
|
|
55
|
+
// Stay "loading" while the backend redirects to login on a 401.
|
|
56
|
+
if (isNotAuthenticated(e)) {
|
|
57
|
+
if (cancelled) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
cancelGrace = afterNotAuthenticatedGrace(() => update({ state: "error", error }));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
52
63
|
update({ state: "error", error });
|
|
53
64
|
});
|
|
54
65
|
return () => {
|
|
55
66
|
cancelled = true;
|
|
67
|
+
cancelGrace?.();
|
|
56
68
|
};
|
|
57
69
|
}, [backend, workspaceId]);
|
|
58
70
|
if (!backend || !workspaceId) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWorkspaceSettings.d.ts","sourceRoot":"","sources":["../../src/platformContext/useWorkspaceSettings.ts"],"names":[],"mappings":"AAIA,OAAO,EAEH,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,
|
|
1
|
+
{"version":3,"file":"useWorkspaceSettings.d.ts","sourceRoot":"","sources":["../../src/platformContext/useWorkspaceSettings.ts"],"names":[],"mappings":"AAIA,OAAO,EAEH,KAAK,kBAAkB,EACvB,KAAK,sBAAsB,EAE9B,MAAM,2BAA2B,CAAC;AAInC,KAAK,sBAAsB,GACrB;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACjB;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,GACpB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,sBAAsB,CAAA;CAAE,GACpD;IAAE,KAAK,EAAE,WAAW,CAAA;CAAE,GACtB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAChC,OAAO,EAAE,kBAAkB,GAAG,SAAS,EACvC,WAAW,EAAE,MAAM,GAAG,SAAS,GAChC,sBAAsB,CA8CxB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// (C) 2026 GoodData Corporation
|
|
2
2
|
import { useEffect, useState } from "react";
|
|
3
|
-
import { UnexpectedResponseError, } from "@gooddata/sdk-backend-spi";
|
|
3
|
+
import { UnexpectedResponseError, isNotAuthenticated, } from "@gooddata/sdk-backend-spi";
|
|
4
|
+
import { afterNotAuthenticatedGrace } from "./notAuthenticatedGrace.js";
|
|
4
5
|
/**
|
|
5
6
|
* Loads effective workspace-scoped settings for the current user.
|
|
6
7
|
*
|
|
@@ -18,6 +19,7 @@ export function useWorkspaceSettings(backend, workspaceId) {
|
|
|
18
19
|
return;
|
|
19
20
|
}
|
|
20
21
|
let cancelled = false;
|
|
22
|
+
let cancelGrace;
|
|
21
23
|
setSettingsState({ state: "loading" });
|
|
22
24
|
backend
|
|
23
25
|
.workspace(workspaceId)
|
|
@@ -36,10 +38,16 @@ export function useWorkspaceSettings(backend, workspaceId) {
|
|
|
36
38
|
return;
|
|
37
39
|
}
|
|
38
40
|
const error = e instanceof Error ? e.message : "Unknown error loading workspace settings.";
|
|
41
|
+
// Stay "loading" while the backend redirects to login on a 401.
|
|
42
|
+
if (isNotAuthenticated(e)) {
|
|
43
|
+
cancelGrace = afterNotAuthenticatedGrace(() => setSettingsState({ state: "error", error }));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
39
46
|
setSettingsState({ state: "error", error });
|
|
40
47
|
});
|
|
41
48
|
return () => {
|
|
42
49
|
cancelled = true;
|
|
50
|
+
cancelGrace?.();
|
|
43
51
|
};
|
|
44
52
|
}, [backend, workspaceId]);
|
|
45
53
|
return settingsState;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gooddata/sdk-ui-pluggable-host",
|
|
3
|
-
"version": "11.62.0-alpha.
|
|
3
|
+
"version": "11.62.0-alpha.4",
|
|
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,25 +29,26 @@
|
|
|
29
29
|
"dependencies": {
|
|
30
30
|
"@module-federation/runtime": "2.6.0",
|
|
31
31
|
"lodash-es": "^4.17.23",
|
|
32
|
-
"@gooddata/sdk-backend-base": "11.62.0-alpha.
|
|
33
|
-
"@gooddata/sdk-backend-
|
|
34
|
-
"@gooddata/sdk-backend-
|
|
35
|
-
"@gooddata/sdk-
|
|
36
|
-
"@gooddata/sdk-
|
|
37
|
-
"@gooddata/sdk-
|
|
38
|
-
"@gooddata/sdk-
|
|
39
|
-
"@gooddata/sdk-
|
|
40
|
-
"@gooddata/sdk-ui-ext": "11.62.0-alpha.
|
|
41
|
-
"@gooddata/sdk-ui-
|
|
42
|
-
"@gooddata/sdk-ui-gen-ai": "11.62.0-alpha.
|
|
43
|
-
"@gooddata/sdk-ui-
|
|
44
|
-
"@gooddata/sdk-ui-
|
|
45
|
-
"@gooddata/util": "11.62.0-alpha.
|
|
32
|
+
"@gooddata/sdk-backend-base": "11.62.0-alpha.4",
|
|
33
|
+
"@gooddata/sdk-backend-tiger": "11.62.0-alpha.4",
|
|
34
|
+
"@gooddata/sdk-backend-spi": "11.62.0-alpha.4",
|
|
35
|
+
"@gooddata/sdk-model": "11.62.0-alpha.4",
|
|
36
|
+
"@gooddata/sdk-ui": "11.62.0-alpha.4",
|
|
37
|
+
"@gooddata/sdk-ui-application-header": "11.62.0-alpha.4",
|
|
38
|
+
"@gooddata/sdk-pluggable-application-model": "11.62.0-alpha.4",
|
|
39
|
+
"@gooddata/sdk-embedding": "11.62.0-alpha.4",
|
|
40
|
+
"@gooddata/sdk-ui-ext": "11.62.0-alpha.4",
|
|
41
|
+
"@gooddata/sdk-ui-semantic-search": "11.62.0-alpha.4",
|
|
42
|
+
"@gooddata/sdk-ui-gen-ai": "11.62.0-alpha.4",
|
|
43
|
+
"@gooddata/sdk-ui-kit": "11.62.0-alpha.4",
|
|
44
|
+
"@gooddata/sdk-ui-theme-provider": "11.62.0-alpha.4",
|
|
45
|
+
"@gooddata/util": "11.62.0-alpha.4"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@microsoft/api-documenter": "7.30.16",
|
|
49
49
|
"@microsoft/api-extractor": "7.59.2",
|
|
50
50
|
"@testing-library/dom": "10.4.2",
|
|
51
|
+
"@testing-library/jest-dom": "7.0.1",
|
|
51
52
|
"@testing-library/react": "16.3.0",
|
|
52
53
|
"@types/lodash-es": "4.17.12",
|
|
53
54
|
"@types/node": "24.12.0",
|
|
@@ -55,7 +56,7 @@
|
|
|
55
56
|
"@types/react-dom": "19.1.7",
|
|
56
57
|
"@typescript-eslint/eslint-plugin": "8.58.0",
|
|
57
58
|
"@typescript-eslint/parser": "8.58.0",
|
|
58
|
-
"@vitest/coverage-v8": "
|
|
59
|
+
"@vitest/coverage-v8": "5.0.4",
|
|
59
60
|
"eslint": "^9.39.2",
|
|
60
61
|
"eslint-plugin-headers": "1.3.3",
|
|
61
62
|
"eslint-plugin-jsdoc": "62.1.0",
|
|
@@ -79,10 +80,9 @@
|
|
|
79
80
|
"tslib": "2.8.1",
|
|
80
81
|
"typescript": "7.0.2",
|
|
81
82
|
"vite": "8.0.16",
|
|
82
|
-
"vitest": "
|
|
83
|
-
"
|
|
84
|
-
"@gooddata/
|
|
85
|
-
"@gooddata/oxlint-config": "11.62.0-alpha.2"
|
|
83
|
+
"vitest": "5.0.4",
|
|
84
|
+
"@gooddata/eslint-config": "11.62.0-alpha.4",
|
|
85
|
+
"@gooddata/oxlint-config": "11.62.0-alpha.4"
|
|
86
86
|
},
|
|
87
87
|
"peerDependencies": {
|
|
88
88
|
"react": ">=18.3.1",
|