@gooddata/sdk-ui-pluggable-host 11.40.0-alpha.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.
Files changed (126) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +20 -0
  3. package/esm/assets/logo-white.svg +3 -0
  4. package/esm/components/FullScreenLoader.d.ts +1 -0
  5. package/esm/components/FullScreenLoader.js +8 -0
  6. package/esm/components/HostUiContainer.d.ts +16 -0
  7. package/esm/components/HostUiContainer.js +141 -0
  8. package/esm/components/HostUiContainer.scss +5 -0
  9. package/esm/components/Root.d.ts +16 -0
  10. package/esm/components/Root.js +64 -0
  11. package/esm/components/Root.scss +14 -0
  12. package/esm/components/lib/translations.d.ts +7 -0
  13. package/esm/components/lib/translations.js +64 -0
  14. package/esm/components/useRedirectNavigation.d.ts +7 -0
  15. package/esm/components/useRedirectNavigation.js +23 -0
  16. package/esm/components/useRedirectTarget.d.ts +19 -0
  17. package/esm/components/useRedirectTarget.js +62 -0
  18. package/esm/debug.d.ts +9 -0
  19. package/esm/debug.js +18 -0
  20. package/esm/index.d.ts +11 -0
  21. package/esm/index.js +10 -0
  22. package/esm/lib/chunkReloadGuard.d.ts +89 -0
  23. package/esm/lib/chunkReloadGuard.js +203 -0
  24. package/esm/lib/hostNotifications.d.ts +20 -0
  25. package/esm/lib/hostNotifications.js +50 -0
  26. package/esm/lib/isProduction.d.ts +12 -0
  27. package/esm/lib/isProduction.js +13 -0
  28. package/esm/loader/lastVisitedApp.d.ts +11 -0
  29. package/esm/loader/lastVisitedApp.js +43 -0
  30. package/esm/loader/localLoader.d.ts +16 -0
  31. package/esm/loader/localLoader.js +38 -0
  32. package/esm/loader/pluggableApplicationsLoader.d.ts +13 -0
  33. package/esm/loader/pluggableApplicationsLoader.js +55 -0
  34. package/esm/loader/redirectLogic.d.ts +30 -0
  35. package/esm/loader/redirectLogic.js +143 -0
  36. package/esm/loader/remoteLoader.d.ts +5 -0
  37. package/esm/loader/remoteLoader.js +117 -0
  38. package/esm/loader/remoteUrlSecurity.d.ts +1 -0
  39. package/esm/loader/remoteUrlSecurity.js +26 -0
  40. package/esm/loader/routing.d.ts +22 -0
  41. package/esm/loader/routing.js +87 -0
  42. package/esm/platformContext/backend.d.ts +44 -0
  43. package/esm/platformContext/backend.js +131 -0
  44. package/esm/platformContext/bootstrap.d.ts +15 -0
  45. package/esm/platformContext/bootstrap.js +122 -0
  46. package/esm/platformContext/loadPlatformContext.d.ts +18 -0
  47. package/esm/platformContext/loadPlatformContext.js +50 -0
  48. package/esm/platformContext/tigerNotAuthenticatedHandler.d.ts +3 -0
  49. package/esm/platformContext/tigerNotAuthenticatedHandler.js +16 -0
  50. package/esm/platformContext/types.d.ts +17 -0
  51. package/esm/platformContext/types.js +2 -0
  52. package/esm/platformContext/useLoadPlatformContext.d.ts +35 -0
  53. package/esm/platformContext/useLoadPlatformContext.js +131 -0
  54. package/esm/platformContext/useWorkspacePermissions.d.ts +26 -0
  55. package/esm/platformContext/useWorkspacePermissions.js +52 -0
  56. package/esm/platformContext/useWorkspaceSettings.d.ts +25 -0
  57. package/esm/platformContext/useWorkspaceSettings.js +46 -0
  58. package/esm/registry/pluggableApplicationsRegistry.d.ts +55 -0
  59. package/esm/registry/pluggableApplicationsRegistry.js +203 -0
  60. package/esm/sdk-ui-pluggable-host.d.ts +262 -0
  61. package/esm/styles/global.css +16 -0
  62. package/esm/translations/de-DE.json +34 -0
  63. package/esm/translations/en-AU.json +34 -0
  64. package/esm/translations/en-GB.json +34 -0
  65. package/esm/translations/en-US.json +130 -0
  66. package/esm/translations/es-419.json +34 -0
  67. package/esm/translations/es-ES.json +34 -0
  68. package/esm/translations/fi-FI.json +34 -0
  69. package/esm/translations/fr-CA.json +34 -0
  70. package/esm/translations/fr-FR.json +34 -0
  71. package/esm/translations/id-ID.json +34 -0
  72. package/esm/translations/it-IT.json +34 -0
  73. package/esm/translations/ja-JP.json +34 -0
  74. package/esm/translations/ko-KR.json +34 -0
  75. package/esm/translations/nl-NL.json +34 -0
  76. package/esm/translations/pl-PL.json +34 -0
  77. package/esm/translations/pt-BR.json +34 -0
  78. package/esm/translations/pt-PT.json +34 -0
  79. package/esm/translations/ru-RU.json +34 -0
  80. package/esm/translations/sl-SI.json +34 -0
  81. package/esm/translations/th-TH.json +34 -0
  82. package/esm/translations/tr-TR.json +34 -0
  83. package/esm/translations/uk-UA.json +34 -0
  84. package/esm/translations/vi-VN.json +34 -0
  85. package/esm/translations/zh-HK.json +34 -0
  86. package/esm/translations/zh-Hans.json +34 -0
  87. package/esm/translations/zh-Hant.json +34 -0
  88. package/esm/tsdoc-metadata.json +11 -0
  89. package/esm/types/lifecycle.d.ts +18 -0
  90. package/esm/types/lifecycle.js +2 -0
  91. package/esm/ui/DefaultHostUi.d.ts +12 -0
  92. package/esm/ui/DefaultHostUi.js +101 -0
  93. package/esm/ui/DefaultHostUi.scss +8 -0
  94. package/esm/ui/GenAIChat.d.ts +43 -0
  95. package/esm/ui/GenAIChat.js +102 -0
  96. package/esm/ui/HostChrome.d.ts +19 -0
  97. package/esm/ui/HostChrome.js +115 -0
  98. package/esm/ui/HostChrome.scss +24 -0
  99. package/esm/ui/HostIntlProvider.d.ts +9 -0
  100. package/esm/ui/HostIntlProvider.js +13 -0
  101. package/esm/ui/HostNotificationDispatcher.d.ts +12 -0
  102. package/esm/ui/HostNotificationDispatcher.js +42 -0
  103. package/esm/ui/PluggableApplicationRenderer.d.ts +10 -0
  104. package/esm/ui/PluggableApplicationRenderer.js +100 -0
  105. package/esm/ui/PluggableApplicationRenderer.scss +29 -0
  106. package/esm/ui/SemanticSearch.d.ts +23 -0
  107. package/esm/ui/SemanticSearch.js +46 -0
  108. package/esm/ui/WorkspacePicker.d.ts +9 -0
  109. package/esm/ui/WorkspacePicker.js +29 -0
  110. package/esm/ui/appMenuItems.d.ts +17 -0
  111. package/esm/ui/appMenuItems.js +81 -0
  112. package/esm/ui/chromeHelpers.d.ts +17 -0
  113. package/esm/ui/chromeHelpers.js +29 -0
  114. package/esm/ui/hostChromeBem.d.ts +1 -0
  115. package/esm/ui/hostChromeBem.js +3 -0
  116. package/esm/ui/resolveHostUiModule.d.ts +8 -0
  117. package/esm/ui/resolveHostUiModule.js +22 -0
  118. package/esm/ui/useHostChromeChat.d.ts +29 -0
  119. package/esm/ui/useHostChromeChat.js +38 -0
  120. package/esm/ui/useHostChromePricing.d.ts +52 -0
  121. package/esm/ui/useHostChromePricing.js +37 -0
  122. package/esm/ui/useHostChromeSearch.d.ts +20 -0
  123. package/esm/ui/useHostChromeSearch.js +18 -0
  124. package/esm/ui/useHostChromeWorkspaceFeatures.d.ts +19 -0
  125. package/esm/ui/useHostChromeWorkspaceFeatures.js +36 -0
  126. package/package.json +114 -0
@@ -0,0 +1,122 @@
1
+ // (C) 2026 GoodData Corporation
2
+ import { PantherTier, } from "@gooddata/sdk-pluggable-application-model";
3
+ const DEFAULT_WHITE_LABELING = { enabled: false };
4
+ const BOOTSTRAP_LOG_PREFIX = "[host-runtime/bootstrap]";
5
+ function resolvePantherTier(entitlements) {
6
+ const tier = entitlements.find((e) => e.name === "Tier")?.value?.toUpperCase();
7
+ switch (tier) {
8
+ case PantherTier.INTERNAL:
9
+ return PantherTier.INTERNAL;
10
+ case PantherTier.POC:
11
+ return PantherTier.POC;
12
+ case PantherTier.LABS:
13
+ return PantherTier.LABS;
14
+ case PantherTier.DEMO:
15
+ return PantherTier.DEMO;
16
+ case PantherTier.TRIAL:
17
+ default:
18
+ return PantherTier.TRIAL;
19
+ }
20
+ }
21
+ export const PLATFORM_CONTEXT_VERSION = "1.0";
22
+ function logBootstrapWarning(message, error) {
23
+ // Keep fallback behavior while making silent backend failures visible.
24
+ console.error(`${BOOTSTRAP_LOG_PREFIX} ${message}`, error);
25
+ }
26
+ function logAndRethrow(message) {
27
+ return (error) => {
28
+ logBootstrapWarning(message, error);
29
+ throw error;
30
+ };
31
+ }
32
+ async function fetchOrganizationDetails(backend, organizationId, includeAdditionalDetails = true) {
33
+ return backend
34
+ .organization(organizationId)
35
+ .getDescriptor(includeAdditionalDetails)
36
+ .catch((error) => {
37
+ logBootstrapWarning("Failed to load organization descriptor.", error);
38
+ return undefined;
39
+ });
40
+ }
41
+ export async function bootstrapApplication(backend) {
42
+ const entitlementsPromise = backend
43
+ .entitlements()
44
+ .resolveEntitlements()
45
+ .catch((error) => {
46
+ logBootstrapWarning("Failed to resolve entitlements.", error);
47
+ return [];
48
+ });
49
+ const [userProfile, userSettings] = await Promise.all([
50
+ backend
51
+ .currentUser()
52
+ .getUserWithDetails()
53
+ .catch(logAndRethrow("Failed to load current user profile.")),
54
+ backend
55
+ .currentUser()
56
+ .settings()
57
+ .getSettings()
58
+ .catch(logAndRethrow("Failed to load current user settings.")),
59
+ ]);
60
+ const includeAdditionalDetails = userProfile.permissions?.includes("MANAGE") ?? false;
61
+ const organizationId = userProfile.organizationId ??
62
+ (await backend
63
+ .organizations()
64
+ .getCurrentOrganization()
65
+ .catch(logAndRethrow("Failed to load current organization."))).organizationId;
66
+ const [userInfo, organizationDescriptor, entitlements, theme] = await Promise.all([
67
+ backend
68
+ .organization(organizationId)
69
+ .users()
70
+ .getUser(userProfile.login)
71
+ .catch((error) => {
72
+ logBootstrapWarning("Failed to load user details from organization.", error);
73
+ return undefined;
74
+ }),
75
+ fetchOrganizationDetails(backend, organizationId, includeAdditionalDetails),
76
+ entitlementsPromise,
77
+ backend
78
+ .organization(organizationId)
79
+ .styling()
80
+ .getTheme()
81
+ .catch((error) => {
82
+ logBootstrapWarning("Failed to load organization theme.", error);
83
+ return {};
84
+ }),
85
+ ]);
86
+ const user = {
87
+ ...userProfile,
88
+ email: userInfo?.email ?? userProfile.email,
89
+ };
90
+ const organizationPermissions = {
91
+ canManageOrganization: user.permissions?.includes("MANAGE") ?? false,
92
+ hasBaseUiAccess: user.permissions?.includes("BASE_UI_ACCESS") ?? false,
93
+ canCreateDevToken: user.permissions?.includes("SELF_CREATE_TOKEN") ?? false,
94
+ };
95
+ const organization = organizationDescriptor
96
+ ? {
97
+ id: organizationDescriptor.id,
98
+ title: organizationDescriptor.title,
99
+ bootstrapUser: organizationDescriptor.bootstrapUser,
100
+ bootstrapUserGroup: organizationDescriptor.bootstrapUserGroup,
101
+ identityProviderType: organizationDescriptor.identityProviderType,
102
+ }
103
+ : { id: organizationId, title: userProfile.organizationName };
104
+ const pantherTier = resolvePantherTier(entitlements);
105
+ const whiteLabelingSetting = userSettings.whiteLabeling;
106
+ const whiteLabeling = {
107
+ enabled: whiteLabelingSetting?.enabled ?? DEFAULT_WHITE_LABELING.enabled,
108
+ logoUrl: whiteLabelingSetting?.logoUrl,
109
+ faviconUrl: whiteLabelingSetting?.faviconUrl,
110
+ appleTouchIconUrl: whiteLabelingSetting?.appleTouchIconUrl,
111
+ };
112
+ return {
113
+ user,
114
+ userSettings,
115
+ organization,
116
+ organizationPermissions,
117
+ entitlements,
118
+ whiteLabeling,
119
+ pantherTier,
120
+ theme,
121
+ };
122
+ }
@@ -0,0 +1,18 @@
1
+ import { type IAuthCredentials } from "@gooddata/sdk-pluggable-application-model";
2
+ import { type IBackendPlatformContext } from "./types.js";
3
+ export declare class HostApplicationDisabledError extends Error {
4
+ constructor();
5
+ }
6
+ /**
7
+ * @alpha
8
+ */
9
+ export interface ILoadPlatformContextCallbacks {
10
+ onBootstrapError?: (error: string, context: string) => void;
11
+ onLoaded?: (durationMs: number) => void;
12
+ }
13
+ export interface ILoadPlatformContextOptions {
14
+ signal?: AbortSignal;
15
+ auth?: IAuthCredentials;
16
+ callbacks?: ILoadPlatformContextCallbacks;
17
+ }
18
+ export declare function loadPlatformContext(options?: ILoadPlatformContextOptions): Promise<IBackendPlatformContext>;
@@ -0,0 +1,50 @@
1
+ // (C) 2026 GoodData Corporation
2
+ import { now } from "../debug.js";
3
+ import { getAuthCredentials, getBackend, reinitializeBackend } from "./backend.js";
4
+ import { PLATFORM_CONTEXT_VERSION, bootstrapApplication } from "./bootstrap.js";
5
+ export class HostApplicationDisabledError extends Error {
6
+ constructor() {
7
+ super("Host application is disabled by feature flag.");
8
+ this.name = "HostApplicationDisabledError";
9
+ }
10
+ }
11
+ function throwIfAborted(signal) {
12
+ if (signal?.aborted) {
13
+ throw new DOMException("Aborted", "AbortError");
14
+ }
15
+ }
16
+ export async function loadPlatformContext(options = {}) {
17
+ const start = now();
18
+ if (options.auth) {
19
+ reinitializeBackend(options.auth);
20
+ }
21
+ const backend = getBackend();
22
+ throwIfAborted(options.signal);
23
+ let bootstrap;
24
+ try {
25
+ bootstrap = await bootstrapApplication(backend);
26
+ }
27
+ catch (e) {
28
+ options.callbacks?.onBootstrapError?.(e instanceof Error ? e.message : "Unknown bootstrap error", "loadPlatformContext");
29
+ throw e;
30
+ }
31
+ throwIfAborted(options.signal);
32
+ if (bootstrap.userSettings["enableShellApplication"] !== true) {
33
+ throw new HostApplicationDisabledError();
34
+ }
35
+ const elapsed = now() - start;
36
+ options.callbacks?.onLoaded?.(elapsed);
37
+ return {
38
+ version: PLATFORM_CONTEXT_VERSION,
39
+ auth: getAuthCredentials(),
40
+ user: bootstrap.user,
41
+ organization: bootstrap.organization,
42
+ organizationPermissions: bootstrap.organizationPermissions,
43
+ entitlements: bootstrap.entitlements,
44
+ userSettings: bootstrap.userSettings,
45
+ whiteLabeling: bootstrap.whiteLabeling,
46
+ pantherTier: bootstrap.pantherTier,
47
+ theme: bootstrap.theme,
48
+ embeddingMode: "none",
49
+ };
50
+ }
@@ -0,0 +1,3 @@
1
+ import { type IAuthenticationContext, type NotAuthenticated } from "@gooddata/sdk-backend-spi";
2
+ /** 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;
@@ -0,0 +1,16 @@
1
+ // (C) 2026 GoodData Corporation
2
+ import { throttle } from "lodash-es";
3
+ import { createRedirectToTigerAuthenticationWithParams, redirectToTigerAuthentication, } from "@gooddata/sdk-backend-tiger";
4
+ /** Not authenticated handler will be called every time 401 is returned from Tiger backend. */
5
+ export function createNotAuthenticatedHandler(externalProviderId) {
6
+ const redirectHandler = externalProviderId
7
+ ? createRedirectToTigerAuthenticationWithParams({ externalProviderId })
8
+ : redirectToTigerAuthentication;
9
+ const debouncedRedirectHandler = throttle(redirectHandler, 500, {
10
+ leading: false,
11
+ trailing: true,
12
+ });
13
+ return (ctx, err) => {
14
+ debouncedRedirectHandler(ctx, err);
15
+ };
16
+ }
@@ -0,0 +1,17 @@
1
+ import { type IPlatformContext } from "@gooddata/sdk-pluggable-application-model";
2
+ export interface IPlatformContextLoadingResult {
3
+ state: "loading";
4
+ }
5
+ export interface IPlatformContextReadyResult<TContext> {
6
+ state: "ready";
7
+ ctx: TContext;
8
+ }
9
+ export interface IPlatformContextErrorResult {
10
+ state: "error";
11
+ error: string;
12
+ }
13
+ type RoutePlatformContextFields = "currentWorkspaceId" | "currentApplicationScope" | "workspacePermissions" | "workspaceSettings" | "settings" | "preferredLocale";
14
+ export type IRoutePlatformContext = Pick<IPlatformContext, RoutePlatformContextFields>;
15
+ export type IBackendPlatformContext = Omit<IPlatformContext, RoutePlatformContextFields>;
16
+ export type IPlatformContextLoadResult<TContext> = IPlatformContextLoadingResult | IPlatformContextReadyResult<TContext> | IPlatformContextErrorResult;
17
+ export {};
@@ -0,0 +1,2 @@
1
+ // (C) 2026 GoodData Corporation
2
+ export {};
@@ -0,0 +1,35 @@
1
+ import { type IPlatformContext } from "@gooddata/sdk-pluggable-application-model";
2
+ import { type ILoadPlatformContextCallbacks } from "./loadPlatformContext.js";
3
+ import { type IBackendPlatformContext, type IPlatformContextLoadResult } from "./types.js";
4
+ /**
5
+ * Builds the platform context inside the host application.
6
+ *
7
+ * @remarks
8
+ * This is intentionally host-owned and can evolve as host bootstrapping grows.
9
+ *
10
+ * @internal
11
+ */
12
+ export declare function useLoadPlatformContext(): IPlatformContextLoadResult<IPlatformContext>;
13
+ /**
14
+ * Subscribable provider of the host's backend platform context (org / user / settings).
15
+ *
16
+ * @internal
17
+ */
18
+ export interface IBackendPlatformContextProvider {
19
+ setCallbacks(callbacks: ILoadPlatformContextCallbacks): void;
20
+ getResult: () => Readonly<IPlatformContextLoadResult<IBackendPlatformContext>>;
21
+ subscribe: (listener: () => void) => () => void;
22
+ load: () => Promise<void>;
23
+ }
24
+ /**
25
+ * @internal
26
+ */
27
+ export declare const BackendPlatformContextProvider: IBackendPlatformContextProvider;
28
+ /**
29
+ * Registers the host's platform-context lifecycle callbacks. Must be called once at
30
+ * host boot before `<Root>` is rendered so the very first load can report telemetry
31
+ * / errors back to the host application.
32
+ *
33
+ * @alpha
34
+ */
35
+ export declare function registerPlatformContextCallbacks(callbacks: ILoadPlatformContextCallbacks): void;
@@ -0,0 +1,131 @@
1
+ // (C) 2026 GoodData Corporation
2
+ import { useMemo, useSyncExternalStore } from "react";
3
+ import { useLocation } from "react-router";
4
+ import { isLocale } from "@gooddata/sdk-model";
5
+ import { isProduction } from "../lib/isProduction.js";
6
+ import { getApplicationScopeFromPath, getWorkspaceIdFromPath } from "../loader/routing.js";
7
+ import { getBackend } from "./backend.js";
8
+ import { HostApplicationDisabledError, loadPlatformContext, } from "./loadPlatformContext.js";
9
+ import { useWorkspacePermissions } from "./useWorkspacePermissions.js";
10
+ import { useWorkspaceSettings } from "./useWorkspaceSettings.js";
11
+ function redirectToAppRoot() {
12
+ const rootUrl = new URL("/", window.location.origin).toString();
13
+ window.location.assign(rootUrl);
14
+ }
15
+ /**
16
+ * Builds the platform context inside the host application.
17
+ *
18
+ * @remarks
19
+ * This is intentionally host-owned and can evolve as host bootstrapping grows.
20
+ *
21
+ * @internal
22
+ */
23
+ export function useLoadPlatformContext() {
24
+ const backendContext = useSyncExternalStore(BackendPlatformContextProvider.subscribe, BackendPlatformContextProvider.getResult);
25
+ const { pathname } = useLocation();
26
+ const applicationScope = getApplicationScopeFromPath(pathname);
27
+ const workspaceId = getWorkspaceIdFromPath(pathname);
28
+ const backend = backendContext.state === "ready" ? getBackend() : undefined;
29
+ const workspacePermissionsState = useWorkspacePermissions(backend, workspaceId);
30
+ const workspaceSettingsState = useWorkspaceSettings(backend, workspaceId);
31
+ return useMemo(() => {
32
+ if (backendContext.state !== "ready") {
33
+ return backendContext;
34
+ }
35
+ // Block rendering until workspace permissions AND settings are available when inside a workspace route.
36
+ const permissionsLoading = workspacePermissionsState.state === "loading" || workspacePermissionsState.state === "idle";
37
+ const settingsLoading = workspaceSettingsState.state === "loading" || workspaceSettingsState.state === "idle";
38
+ if (workspaceId !== undefined && (permissionsLoading || settingsLoading)) {
39
+ return { state: "loading" };
40
+ }
41
+ // Surface fetch failures so the user sees an error rather than a silent 404
42
+ if (workspaceId !== undefined && workspacePermissionsState.state === "error") {
43
+ return { state: "error", error: workspacePermissionsState.error };
44
+ }
45
+ if (workspaceId !== undefined && workspaceSettingsState.state === "error") {
46
+ return { state: "error", error: workspaceSettingsState.error };
47
+ }
48
+ const workspacePermissions = workspacePermissionsState.state === "ready" ? workspacePermissionsState.permissions : undefined;
49
+ const workspaceSettings = workspaceSettingsState.state === "ready" ? workspaceSettingsState.settings : undefined;
50
+ const settings = workspaceSettings ?? backendContext.ctx.userSettings;
51
+ const preferredLocale = isLocale(settings.locale) ? settings.locale : undefined;
52
+ const routeCtx = {
53
+ currentApplicationScope: applicationScope,
54
+ currentWorkspaceId: workspaceId,
55
+ workspacePermissions,
56
+ workspaceSettings,
57
+ settings,
58
+ preferredLocale,
59
+ };
60
+ return { state: "ready", ctx: { ...backendContext.ctx, ...routeCtx } };
61
+ }, [backendContext, applicationScope, workspaceId, workspacePermissionsState, workspaceSettingsState]);
62
+ }
63
+ class BackendPlatformContextProviderClass {
64
+ _loadingStateMemo = { state: "loading" };
65
+ _result = this._loadingStateMemo;
66
+ _abortController = new AbortController();
67
+ _listeners = new Set();
68
+ _loadStarted = false;
69
+ _callbacks;
70
+ setCallbacks(callbacks) {
71
+ this._callbacks = callbacks;
72
+ }
73
+ getResult = () => this._result;
74
+ subscribe = (listener) => {
75
+ if (!this._loadStarted) {
76
+ void this.load();
77
+ }
78
+ this._listeners.add(listener);
79
+ return () => this._listeners.delete(listener);
80
+ };
81
+ load = async () => {
82
+ this._loadStarted = true;
83
+ this._abortController.abort();
84
+ const keepCurrentContextMounted = this._result.state === "ready";
85
+ if (!keepCurrentContextMounted) {
86
+ this._setResult(this._loadingStateMemo);
87
+ }
88
+ this._abortController = new AbortController();
89
+ try {
90
+ const ctx = await loadPlatformContext({
91
+ signal: this._abortController.signal,
92
+ callbacks: this._callbacks,
93
+ });
94
+ if (!this._abortController.signal.aborted) {
95
+ this._setResult({ state: "ready", ctx });
96
+ }
97
+ }
98
+ catch (e) {
99
+ if (e instanceof HostApplicationDisabledError && isProduction) {
100
+ redirectToAppRoot(); // In dev env, this would just loop due to app routing
101
+ return;
102
+ }
103
+ if (e instanceof DOMException && e.name === "AbortError") {
104
+ return;
105
+ }
106
+ console.error("[host-runtime/platform-context] Failed to load platform context.", e);
107
+ const message = e instanceof Error ? e.message : "Unknown platform context error.";
108
+ if (!this._abortController.signal.aborted) {
109
+ this._setResult({ state: "error", error: message });
110
+ }
111
+ }
112
+ };
113
+ _setResult(value) {
114
+ this._result = value;
115
+ this._listeners.forEach((listener) => listener());
116
+ }
117
+ }
118
+ /**
119
+ * @internal
120
+ */
121
+ export const BackendPlatformContextProvider = new BackendPlatformContextProviderClass();
122
+ /**
123
+ * Registers the host's platform-context lifecycle callbacks. Must be called once at
124
+ * host boot before `<Root>` is rendered so the very first load can report telemetry
125
+ * / errors back to the host application.
126
+ *
127
+ * @alpha
128
+ */
129
+ export function registerPlatformContextCallbacks(callbacks) {
130
+ BackendPlatformContextProvider.setCallbacks(callbacks);
131
+ }
@@ -0,0 +1,26 @@
1
+ import { type IAnalyticalBackend } from "@gooddata/sdk-backend-spi";
2
+ import { type IWorkspacePermissions } from "@gooddata/sdk-model";
3
+ type WorkspacePermissionsState = {
4
+ state: "idle";
5
+ } | {
6
+ state: "loading";
7
+ } | {
8
+ state: "ready";
9
+ permissions: IWorkspacePermissions;
10
+ } | {
11
+ state: "forbidden";
12
+ } | {
13
+ state: "error";
14
+ error: string;
15
+ };
16
+ /**
17
+ * Loads workspace permissions for the current user.
18
+ *
19
+ * @remarks
20
+ * Returns `"idle"` when `backend` or `workspaceId` is absent.
21
+ * Re-fetches whenever `workspaceId` changes.
22
+ *
23
+ * @internal
24
+ */
25
+ export declare function useWorkspacePermissions(backend: IAnalyticalBackend | undefined, workspaceId: string | undefined): WorkspacePermissionsState;
26
+ export {};
@@ -0,0 +1,52 @@
1
+ // (C) 2026 GoodData Corporation
2
+ import { useEffect, useState } from "react";
3
+ import { UnexpectedResponseError } from "@gooddata/sdk-backend-spi";
4
+ /**
5
+ * Loads workspace permissions for the current user.
6
+ *
7
+ * @remarks
8
+ * Returns `"idle"` when `backend` or `workspaceId` is absent.
9
+ * Re-fetches whenever `workspaceId` changes.
10
+ *
11
+ * @internal
12
+ */
13
+ export function useWorkspacePermissions(backend, workspaceId) {
14
+ const [permissionsState, setPermissionsState] = useState({
15
+ state: "idle",
16
+ });
17
+ useEffect(() => {
18
+ if (!backend || !workspaceId) {
19
+ setPermissionsState((prev) => (prev.state === "idle" ? prev : { state: "idle" }));
20
+ return;
21
+ }
22
+ let cancelled = false;
23
+ setPermissionsState({ state: "loading" });
24
+ backend
25
+ .workspace(workspaceId)
26
+ .permissions()
27
+ .getPermissionsForCurrentUser()
28
+ .then((permissions) => {
29
+ if (!cancelled) {
30
+ setPermissionsState({ state: "ready", permissions });
31
+ }
32
+ })
33
+ .catch((e) => {
34
+ if (cancelled)
35
+ return;
36
+ // 403/404 on the workspace permissions endpoint means no access — Tiger uses
37
+ // 404 to avoid leaking workspace existence (same message as 403). Signal
38
+ // "forbidden" so the platform context reaches "ready" with undefined permissions
39
+ // and the mounted app can render its own access-denied UI.
40
+ if (e instanceof UnexpectedResponseError && (e.httpStatus === 403 || e.httpStatus === 404)) {
41
+ setPermissionsState({ state: "forbidden" });
42
+ return;
43
+ }
44
+ const error = e instanceof Error ? e.message : "Unknown error loading workspace permissions.";
45
+ setPermissionsState({ state: "error", error });
46
+ });
47
+ return () => {
48
+ cancelled = true;
49
+ };
50
+ }, [backend, workspaceId]);
51
+ return permissionsState;
52
+ }
@@ -0,0 +1,25 @@
1
+ import { type IAnalyticalBackend, type IUserWorkspaceSettings } from "@gooddata/sdk-backend-spi";
2
+ type WorkspaceSettingsState = {
3
+ state: "idle";
4
+ } | {
5
+ state: "loading";
6
+ } | {
7
+ state: "ready";
8
+ settings: IUserWorkspaceSettings;
9
+ } | {
10
+ state: "forbidden";
11
+ } | {
12
+ state: "error";
13
+ error: string;
14
+ };
15
+ /**
16
+ * Loads effective workspace-scoped settings for the current user.
17
+ *
18
+ * @remarks
19
+ * Returns `"idle"` when `backend` or `workspaceId` is absent.
20
+ * Re-fetches whenever `workspaceId` changes.
21
+ *
22
+ * @internal
23
+ */
24
+ export declare function useWorkspaceSettings(backend: IAnalyticalBackend | undefined, workspaceId: string | undefined): WorkspaceSettingsState;
25
+ export {};
@@ -0,0 +1,46 @@
1
+ // (C) 2026 GoodData Corporation
2
+ import { useEffect, useState } from "react";
3
+ import { UnexpectedResponseError, } from "@gooddata/sdk-backend-spi";
4
+ /**
5
+ * Loads effective workspace-scoped settings for the current user.
6
+ *
7
+ * @remarks
8
+ * Returns `"idle"` when `backend` or `workspaceId` is absent.
9
+ * Re-fetches whenever `workspaceId` changes.
10
+ *
11
+ * @internal
12
+ */
13
+ export function useWorkspaceSettings(backend, workspaceId) {
14
+ const [settingsState, setSettingsState] = useState({ state: "idle" });
15
+ useEffect(() => {
16
+ if (!backend || !workspaceId) {
17
+ setSettingsState((prev) => (prev.state === "idle" ? prev : { state: "idle" }));
18
+ return;
19
+ }
20
+ let cancelled = false;
21
+ setSettingsState({ state: "loading" });
22
+ backend
23
+ .workspace(workspaceId)
24
+ .settings()
25
+ .getSettingsForCurrentUser()
26
+ .then((settings) => {
27
+ if (!cancelled) {
28
+ setSettingsState({ state: "ready", settings });
29
+ }
30
+ })
31
+ .catch((e) => {
32
+ if (cancelled)
33
+ return;
34
+ if (e instanceof UnexpectedResponseError && (e.httpStatus === 403 || e.httpStatus === 404)) {
35
+ setSettingsState({ state: "forbidden" });
36
+ return;
37
+ }
38
+ const error = e instanceof Error ? e.message : "Unknown error loading workspace settings.";
39
+ setSettingsState({ state: "error", error });
40
+ });
41
+ return () => {
42
+ cancelled = true;
43
+ };
44
+ }, [backend, workspaceId]);
45
+ return settingsState;
46
+ }
@@ -0,0 +1,55 @@
1
+ import { type ApplicationScope, type LocalPluggableApplicationsRegistry, type PluggableApplicationRegistryItem, type RemotePluggableApplicationsRegistry } from "@gooddata/sdk-model";
2
+ import { type IPlatformContext } from "@gooddata/sdk-pluggable-application-model";
3
+ /**
4
+ * Registers the local pluggable applications manifest. Called by the host or harness
5
+ * before rendering.
6
+ *
7
+ * @alpha
8
+ */
9
+ export declare function registerLocalApplications(registry: LocalPluggableApplicationsRegistry): void;
10
+ /**
11
+ * @internal - exported for testing
12
+ */
13
+ export declare function getRemoteRegistry(ctx: IPlatformContext): RemotePluggableApplicationsRegistry | undefined;
14
+ interface IResolveApplicationsOptions {
15
+ /**
16
+ * Local (standard) pluggable applications
17
+ */
18
+ localApps: PluggableApplicationRegistryItem[];
19
+ /**
20
+ * Remote registry configuration from user settings, may be undefined
21
+ */
22
+ remoteRegistry: RemotePluggableApplicationsRegistry | undefined;
23
+ /**
24
+ * Platform context containing user settings, permissions, entitlements, etc.
25
+ */
26
+ ctx: IPlatformContext;
27
+ /**
28
+ * Application scope to filter by; if undefined, no apps are returned
29
+ */
30
+ scope: ApplicationScope | undefined;
31
+ }
32
+ /**
33
+ * Resolves the final list of pluggable applications from the local and remote registries.
34
+ *
35
+ * Processing steps (in order):
36
+ * 1. Filter local apps by allowedStandardApplications list (if provided in remote registry)
37
+ * 2. Filter local apps by BASE_UI_ACCESS organization permission (if restrictBaseUi is set)
38
+ * 3. Merge local and remote applications - local apps are added first, duplicates (by ID) are skipped with console.error
39
+ * 4. Apply overrides from the remote registry to the merged list
40
+ * 5. Filter out disabled applications (isEnabled: false)
41
+ * 6. Filter by application scope - keep only apps whose applicationScope matches scope; if scope is undefined, no apps pass through
42
+ * 7. Filter by requirements - check requiredSettings, requiredWorkspacePermissions, requiredOrganizationPermissions, and requiredEntitlements
43
+ * 8. Sort by menuOrder (ascending)
44
+ *
45
+ * @param options - Resolution options; see {@link IResolveApplicationsOptions}
46
+ * @returns Filtered and sorted list of pluggable applications ready for display
47
+ *
48
+ * @internal - exported for testing
49
+ */
50
+ export declare function resolveApplications({ localApps, remoteRegistry, ctx, scope }: IResolveApplicationsOptions): PluggableApplicationRegistryItem[];
51
+ /**
52
+ * Builds the resolved list of pluggable applications from the local and remote registries.
53
+ */
54
+ export declare function usePluggableApplications(ctx: IPlatformContext): PluggableApplicationRegistryItem[];
55
+ export {};