Repository: Unleash/unleash
Pull request #9723: feat: use Unleash React SDK in Admin UI
Author: login: ivarconr
Created: 2025-04-08T14:05:58Z | Merged: 2025-04-10T06:26:30Z

In this PR I integrate the Unleash React SDK with the Admin UI. 

We also take advantage of Unleash Hosted Edge behind the scenes with multiple regions to get the evaluations close to the end user. 


Diff:
diff --git a/frontend/index.html b/frontend/index.html
index 5f9089c542..1b538160b3 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -7,6 +7,7 @@
         <meta name="baseUriPath" content="::baseUriPath::" />
         <meta name="cdnPrefix" content="::cdnPrefix::" />
         <meta name="uiFlags" content="::uiFlags::" />
+        <meta name="unleashToken" content="::unleashToken::" />
         <meta name="viewport" content="width=device-width, initial-scale=1" />
         <meta name="description" content="unleash" />
         <title>Unleash</title>
diff --git a/frontend/package.json b/frontend/package.json
index d99d07ac8c..d9a0834013 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -69,6 +69,7 @@
     "@types/uuid": "^9.0.0",
     "@uiw/codemirror-theme-duotone": "4.23.10",
     "@uiw/react-codemirror": "4.23.10",
+    "@unleash/proxy-client-react": "^5.0.0",
     "@vitejs/plugin-react": "4.3.4",
     "cartesian": "^1.0.1",
     "chart.js": "3.9.1",
@@ -118,6 +119,7 @@
     "swr": "2.3.3",
     "tss-react": "4.9.15",
     "typescript": "5.4.5",
+    "unleash-proxy-client": "^3.7.3",
     "use-query-params": "^2.2.1",
     "vanilla-jsoneditor": "^0.23.0",
     "vite": "5.4.16",
diff --git a/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx b/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx
index 2ecf33090d..4742db4828 100644
--- a/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx
+++ b/frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx
@@ -28,8 +28,9 @@ import { ReactComponent as CelebatoryUnleashLogo } from 'assets/img/unleashHolid
 import { ReactComponent as CelebatoryUnleashLogoWhite } from 'assets/img/unleashHolidayDark.svg';
 import { ReactComponent as LogoOnlyWhite } from 'assets/img/logo.svg';
 import { ReactComponent as LogoOnly } from 'assets/img/logoDark.svg';
-import { useUiFlag } from 'hooks/useUiFlag';
 import { Link } from 'react-router-dom';
+import { useFlag } from '@unleash/proxy-client-react';
+import { useUiFlag } from 'hooks/useUiFlag';
 
 export const MobileNavigationSidebar: FC<{
     onClick: () => void;
@@ -110,7 +111,7 @@ export const NavigationSidebar: FC<{ NewInUnleash?: typeof NewInUnleash }> = ({
     NewInUnleash,
 }) => {
     const { routes } = useRoutes();
-    const celebatoryUnleash = useUiFlag('celebrateUnleash');
+    const celebrateUnleashFrontend = useFlag('celebrateUnleashFrontend');
 
     const [mode, setMode] = useNavigationMode();
     const [expanded, changeExpanded] = useExpanded<'configure' | 'admin'>();
@@ -138,7 +139,7 @@ export const NavigationSidebar: FC<{ NewInUnleash?: typeof NewInUnleash }> = ({
                         <ThemeMode
                             darkmode={
                                 <ConditionallyRender
-                                    condition={celebatoryUnleash}
+                                    condition={celebrateUnleashFrontend}
                                     show={<CelebatoryUnleashLogoWhite />}
                                     elseShow={
                                         <StyledUnleashLogoWhite aria-label='Unleash logo' />
@@ -147,7 +148,7 @@ export const NavigationSidebar: FC<{ NewInUnleash?: typeof NewInUnleash }> = ({
                             }
                             lightmode={
                                 <ConditionallyRender
-                                    condition={celebatoryUnleash}
+                                    condition={celebrateUnleashFrontend}
                                     show={<StyledCelebatoryLogo />}
                                     elseShow={
                                         <StyledUnleashLogo aria-label='Unleash logo' />
diff --git a/frontend/src/component/providers/UnleashFlagProvider/UnleashFlagProvider.tsx b/frontend/src/component/providers/UnleashFlagProvider/UnleashFlagProvider.tsx
new file mode 100644
index 0000000000..91c8e1c2c3
--- /dev/null
+++ b/frontend/src/component/providers/UnleashFlagProvider/UnleashFlagProvider.tsx
@@ -0,0 +1,56 @@
+import type React from 'react';
+import { type FC, useEffect } from 'react';
+import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
+import FlagProvider, { UnleashClient } from '@unleash/proxy-client-react';
+
+const UNLEASH_API = 'https://hosted.edge.getunleash.io/api/frontend';
+const DEV_TOKEN = '';
+
+let client: UnleashClient;
+let token: string;
+let started: boolean = false;
+
+export const UnleashFlagProvider: FC<{ children?: React.ReactNode }> = ({
+    children,
+}) => {
+    const getUnleashFrontendToken = (): string => {
+        const el = document.querySelector<HTMLMetaElement>(
+            'meta[name="unleashToken"]',
+        );
+
+        const content = el?.content ?? '::unleashToken::';
+        return content === '::unleashToken::' ? DEV_TOKEN : content;
+    };
+
+    // We only want to create a single client.
+    if (!client) {
+        token = getUnleashFrontendToken();
+
+        client = new UnleashClient({
+            url: UNLEASH_API,
+            clientKey: token || 'offline',
+            refreshInterval: 1,
+            appName: 'Unleash Cloud UI',
+        });
+    }
+
+    const { uiConfig } = useUiConfig();
+
+    useEffect(() => {
+        if (uiConfig.unleashContext && token) {
+            client.updateContext(uiConfig.unleashContext);
+            if (!started) {
+                started = true;
+                client.start();
+            }
+        } else {
+            // nothing
+        }
+    }, [uiConfig.unleashContext]);
+
+    return (
+        <FlagProvider unleashClient={client} startClient={false}>
+            {children}
+        </FlagProvider>
+    );
+};
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
index 031a9f6c6e..7277d05ef4 100644
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -22,6 +22,7 @@ import { Error as LayoutError } from './component/layout/Error/Error';
 import { ErrorBoundary } from 'react-error-boundary';
 import { useRecordUIErrorApi } from 'hooks/api/actions/useRecordUIErrorApi/useRecordUiErrorApi';
 import { HighlightProvider } from 'component/common/Highlight/HighlightProvider';
+import { UnleashFlagProvider } from 'component/providers/UnleashFlagProvider/UnleashFlagProvider';
 
 window.global ||= window;
 
@@ -50,23 +51,25 @@ const ApplicationRoot = () => {
                         <ThemeProvider>
                             <AnnouncerProvider>
                                 <PlausibleProvider>
-                                    <ErrorBoundary
-                                        FallbackComponent={LayoutError}
-                                        onError={sendErrorToApi}
-                                    >
-                                        <FeedbackProvider>
-                                            <FeedbackCESProvider>
-                                                <StickyProvider>
-                                                    <HighlightProvider>
-                                                        <InstanceStatus>
-                                                            <ScrollTop />
-                                                            <App />
-                                                        </InstanceStatus>
-                                                    </HighlightProvider>
-                                                </StickyProvider>
-                                            </FeedbackCESProvider>
-                                        </FeedbackProvider>
-                                    </ErrorBoundary>
+                                    <UnleashFlagProvider>
+                                        <ErrorBoundary
+                                            FallbackComponent={LayoutError}
+                                            onError={sendErrorToApi}
+                                        >
+                                            <FeedbackProvider>
+                                                <FeedbackCESProvider>
+                                                    <StickyProvider>
+                                                        <HighlightProvider>
+                                                            <InstanceStatus>
+                                                                <ScrollTop />
+                                                                <App />
+                                                            </InstanceStatus>
+                                                        </HighlightProvider>
+                                                    </StickyProvider>
+                                                </FeedbackCESProvider>
+                                            </FeedbackProvider>
+                                        </ErrorBoundary>
+                                    </UnleashFlagProvider>
                                 </PlausibleProvider>
                             </AnnouncerProvider>
                         </ThemeProvider>
diff --git a/frontend/src/interfaces/uiConfig.ts b/frontend/src/interfaces/uiConfig.ts
index 8ae21784df..829b977855 100644
--- a/frontend/src/interfaces/uiConfig.ts
+++ b/frontend/src/interfaces/uiConfig.ts
@@ -1,6 +1,8 @@
 import type { ReactNode } from 'react';
 import type { Variant } from 'utils/variants';
 import type { ResourceLimitsSchema } from '../openapi';
+import {} from '@unleash/proxy-client-react/dist/FlagContext';
+import type { IMutableContext } from 'unleash-proxy-client';
 
 export interface IUiConfig {
     authenticationType?: string;
@@ -34,6 +36,7 @@ export interface IUiConfig {
     oidcConfiguredThroughEnv?: boolean;
     samlConfiguredThroughEnv?: boolean;
     maxSessionsCount?: number;
+    unleashContext?: IMutableContext;
 }
 
 export interface IProclamationToast {
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index d01d557080..b4c25ce3cf 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -3371,6 +3371,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@unleash/proxy-client-react@npm:^5.0.0":
+  version: 5.0.0
+  resolution: "@unleash/proxy-client-react@npm:5.0.0"
+  peerDependencies:
+    unleash-proxy-client: ^3.7.3
+  checksum: 10c0/cf8d227a2db06c5ca09be4be45885306b71733540ace3980ff3794bfaa22c6b38a15446e46ca8880478c018d8ecd0f14e14cb203a6c3ba9b901853db0a9890ba
+  languageName: node
+  linkType: hard
+
 "@vitejs/plugin-react@npm:4.3.4":
   version: 4.3.4
   resolution: "@vitejs/plugin-react@npm:4.3.4"
@@ -9609,6 +9618,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"tiny-emitter@npm:^2.1.0":
+  version: 2.1.0
+  resolution: "tiny-emitter@npm:2.1.0"
+  checksum: 10c0/459c0bd6e636e80909898220eb390e1cba2b15c430b7b06cec6ac29d87acd29ef618b9b32532283af749f5d37af3534d0e3bde29fdf6bcefbf122784333c953d
+  languageName: node
+  linkType: hard
+
 "tinybench@npm:^2.9.0":
   version: 2.9.0
   resolution: "tinybench@npm:2.9.0"
@@ -10123,6 +10139,7 @@ __metadata:
     "@types/uuid": "npm:^9.0.0"
     "@uiw/codemirror-theme-duotone": "npm:4.23.10"
     "@uiw/react-codemirror": "npm:4.23.10"
+    "@unleash/proxy-client-react": "npm:^5.0.0"
     "@vitejs/plugin-react": "npm:4.3.4"
     cartesian: "npm:^1.0.1"
     chart.js: "npm:3.9.1"
@@ -10173,6 +10190,7 @@ __metadata:
     swr: "npm:2.3.3"
     tss-react: "npm:4.9.15"
     typescript: "npm:5.4.5"
+    unleash-proxy-client: "npm:^3.7.3"
     use-query-params: "npm:^2.2.1"
     vanilla-jsoneditor: "npm:^0.23.0"
     vite: "npm:5.4.16"
@@ -10184,6 +10202,16 @@ __metadata:
   languageName: unknown
   linkType: soft
 
+"unleash-proxy-client@npm:^3.7.3":
+  version: 3.7.3
+  resolution: "unleash-proxy-client@npm:3.7.3"
+  dependencies:
+    tiny-emitter: "npm:^2.1.0"
+    uuid: "npm:^9.0.1"
+  checksum: 10c0/3a061d4e3587325046fea0133fe405fef143dbcfdd6ed20c54200b46a22bf49acdccb6dcc0b250400a9ace2350b0065f856731a5712598d27c1e9266a141f559
+  languageName: node
+  linkType: hard
+
 "untildify@npm:^4.0.0":
   version: 4.0.0
   resolution: "untildify@npm:4.0.0"
@@ -10298,6 +10326,15 @@ __metadata:
   languageName: node
   linkType: hard
 
+"uuid@npm:^9.0.1":
+  version: 9.0.1
+  resolution: "uuid@npm:9.0.1"
+  bin:
+    uuid: dist/bin/uuid
+  checksum: 10c0/1607dd32ac7fc22f2d8f77051e6a64845c9bce5cd3dd8aa0070c074ec73e666a1f63c7b4e0f4bf2bc8b9d59dc85a15e17807446d9d2b17c8485fbc2147b27f9b
+  languageName: node
+  linkType: hard
+
 "uvu@npm:^0.5.0":
   version: 0.5.6
   resolution: "uvu@npm:0.5.6"
diff --git a/src/lib/__snapshots__/create-config.test.ts.snap b/src/lib/__snapshots__/create-config.test.ts.snap
index d7b451441c..126396e94a 100644
--- a/src/lib/__snapshots__/create-config.test.ts.snap
+++ b/src/lib/__snapshots__/create-config.test.ts.snap
@@ -158,6 +158,7 @@ exports[`should create default config 1`] = `
   "ui": {
     "environment": "Open Source",
   },
+  "unleashFrontendToken": undefined,
   "userInactivityThresholdInDays": 180,
   "versionCheck": {
     "enable": true,
diff --git a/src/lib/create-config.ts b/src/lib/create-config.ts
index 01d928833f..730724b47d 100644
--- a/src/lib/create-config.ts
+++ b/src/lib/create-config.ts
@@ -751,6 +751,9 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
 
     const openAIAPIKey = process.env.OPENAI_API_KEY;
 
+    const unleashFrontendToken =
+        options.unleashFrontendToken || process.env.UNLEASH_FRONTEND_TOKEN;
+
     const defaultDaysToBeConsideredInactive = 180;
     const userInactivityThresholdInDays =
         options.userInactivityThresholdInDays ??
@@ -801,6 +804,7 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
         openAIAPIKey,
         userInactivityThresholdInDays,
         buildDate: process.env.BUILD_DATE,
+        unleashFrontendToken,
     };
 }
 
diff --git a/src/lib/middleware/response-time-metrics.test.ts b/src/lib/middleware/response-time-metrics.test.ts
index db4456e50b..46fc9884db 100644
--- a/src/lib/middleware/response-time-metrics.test.ts
+++ b/src/lib/middleware/response-time-metrics.test.ts
@@ -31,6 +31,7 @@ const flagResolver = {
     isEnabled: jest.fn(),
     getAll: jest.fn(),
     getVariant: jest.fn(),
+    getStaticContext: jest.fn(),
 };
 
 // Make sure it's always cleaned up
diff --git a/src/lib/openapi/spec/ui-config-schema.ts b/src/lib/openapi/spec/ui-config-schema.ts
index 8a13059841..c0baf94c78 100644
--- a/src/lib/openapi/spec/ui-config-schema.ts
+++ b/src/lib/openapi/spec/ui-config-schema.ts
@@ -191,6 +191,11 @@ export const uiConfigSchema = {
             description: 'The maximum number of sessions that a user has.',
             example: 10,
         },
+        unleashContext: {
+            type: 'object',
+            description:
+                'The context object used to configure the Unleash instance.',
+        },
     },
     components: {
         schemas: {
diff --git a/src/lib/routes/admin-api/config.ts b/src/lib/routes/admin-api/config.ts
index e9cfbd36d2..a5205bebee 100644
--- a/src/lib/routes/admin-api/config.ts
+++ b/src/lib/routes/admin-api/config.ts
@@ -174,6 +174,12 @@ class ConfigController extends Controller {
             ...expFlags,
         };
 
+        const unleashContext = {
+            ...this.flagResolver.getStaticContext(), //clientId etc.
+            email: req.user.email,
+            userId: req.user.id,
+        };
+
         const response: UiConfigSchema = {
             ...this.config.ui,
             flags,
@@ -192,6 +198,7 @@ class ConfigController extends Controller {
             maintenanceMode,
             feedbackUriPath: this.config.feedbackUriPath,
             maxSessionsCount,
+            unleashContext: unleashContext,
         };
 
         this.openApiService.respondWithValidation(
diff --git a/src/lib/types/experimental.ts b/src/lib/types/experimental.ts
index 7c361c226c..e4b56ab478 100644
--- a/src/lib/types/experimental.ts
+++ b/src/lib/types/experimental.ts
@@ -352,6 +352,7 @@ export interface IFlagResolver {
     getAll: (context?: IFlagContext) => IFlags;
     isEnabled: (expName: IFlagKey, context?: IFlagContext) => boolean;
     getVariant: (expName: IFlagKey, context?: IFlagContext) => Variant;
+    getStaticContext: () => IFlagContext;
 }
 
 export interface IExternalFlagResolver {
diff --git a/src/lib/types/option.ts b/src/lib/types/option.ts
index fffe00539d..a14f27f949 100644
--- a/src/lib/types/option.ts
+++ b/src/lib/types/option.ts
@@ -4,6 +4,7 @@ import type { LogLevel, LogProvider } from '../logger';
 import type { ILegacyApiTokenCreate } from './models/api-token';
 import type {
     IExperimentalOptions,
+    IFlagContext,
     IFlagResolver,
     IFlags,
 } from './experimental';
@@ -165,6 +166,7 @@ export interface IUnleashOptions {
         >
     >;
     userInactivityThresholdInDays?: number;
+    unleashFrontendToken?: string;
 }
 
 export interface IEmailOption {
@@ -198,6 +200,8 @@ export interface IUIConfig {
         title: string;
     }[];
     flags?: IFlags;
+    unleashToken?: string;
+    unleashContext?: IFlagContext;
 }
 
 export interface ICspDomainOptions {
@@ -287,4 +291,5 @@ export interface IUnleashConfig {
     openAIAPIKey?: string;
     userInactivityThresholdInDays: number;
     buildDate?: string;
+    unleashFrontendToken?: string;
 }
diff --git a/src/lib/util/flag-resolver.ts b/src/lib/util/flag-resolver.ts
index ad20870385..e119d1abdc 100644
--- a/src/lib/util/flag-resolver.ts
+++ b/src/lib/util/flag-resolver.ts
@@ -61,6 +61,10 @@ export default class FlagResolver implements IFlagResolver {
         }
         return this.externalResolver.getVariant(expName, context);
     }
+
+    getStaticContext(): IFlagContext {
+        return {};
+    }
 }
 
 export const getVariantValue = <T = string>(
diff --git a/src/lib/util/load-index-html.ts b/src/lib/util/load-index-html.ts
index 531841e052..d086e3d8fc 100644
--- a/src/lib/util/load-index-html.ts
+++ b/src/lib/util/load-index-html.ts
@@ -10,6 +10,7 @@ export async function loadIndexHTML(
 ): Promise<string> {
     const { cdnPrefix, baseUriPath = '' } = config.server;
     const uiFlags = encodeURI(JSON.stringify(config.ui.flags || '{}'));
+    const unleashToken = config.unleashFrontendToken;
 
     let indexHTML: string;
     if (cdnPrefix) {
@@ -23,5 +24,11 @@ export async function loadIndexHTML(
             .toString();
     }
 
-    return rewriteHTML(indexHTML, baseUriPath, cdnPrefix, uiFlags);
+    return rewriteHTML(
+        indexHTML,
+        baseUriPath,
+        cdnPrefix,
+        uiFlags,
+        unleashToken,
+    );
 }
diff --git a/src/lib/util/rewriteHTML.ts b/src/lib/util/rewriteHTML.ts
index 58de0c11c4..726ca7b924 100644
--- a/src/lib/util/rewriteHTML.ts
+++ b/src/lib/util/rewriteHTML.ts
@@ -3,6 +3,7 @@ export const rewriteHTML = (
     rewriteValue: string,
     cdnPrefix?: string,
     uiFlags?: string,
+    unleashToken?: string,
 ): string => {
     let result = input;
     result = result.replace(/::baseUriPath::/gi, rewriteValue);
@@ -13,6 +14,8 @@ export const rewriteHTML = (
 
     result = result.replace(/::uiFlags::/gi, uiFlags || '{}');
 
+    result = result.replace(/::unleashToken::/gi, unleashToken || '');
+
     result = result.replace(
         /\/static/gi,
         `${cdnPrefix || rewriteValue}/static`,


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 15 out of 18 changed files in this pull request and generated no comments.

<details>
<summary>Files not reviewed (3)</summary>

* **frontend/index.html**: Language not supported
* **frontend/package.json**: Language not supported
* **src/lib/__snapshots__/create-config.test.ts.snap**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (2)</summary>

**frontend/src/component/providers/UnleashFlagProvider/UnleashFlagProvider.tsx:7**
* Hardcoding the development token may lead to accidental exposure or misuse in production; consider sourcing the token securely from configuration or environment variables.
```
const DEV_TOKEN = 'unleash-frontend:development.770ccc394d6b586c70744db292d7872ecbda731d35adeb6d15e63291';
```
**frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx:107**
* [nitpick] The updated flag key 'celebrateUnleashFrontend' differs from the previous 'celebrateUnleash'; please ensure that the backend flag configuration aligns with this new naming.
```
const celebatoryUnleash = useFlag('celebrateUnleashFrontend');
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T14:06:19Z
user: login: ivarconr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T14:11:39Z
user: login: ivarconr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-08T14:25:07Z
user: login: copilot-pull-request-reviewer[bot]
body: 

Copilot reviewed 14 out of 17 changed files in this pull request and generated no comments.

<details>
<summary>Files not reviewed (3)</summary>

* **frontend/index.html**: Language not supported
* **frontend/package.json**: Language not supported
* **src/lib/__snapshots__/create-config.test.ts.snap**: Language not supported
</details>

<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**frontend/src/component/layout/MainLayout/NavigationSidebar/NavigationSidebar.tsx:114**
* The variable 'celebatoryUnleash' appears to be misspelled and inconsistent with the flag name 'celebrateUnleashFrontend'. It is recommended to rename it to 'celebrateUnleashFrontend' for clarity.
```
const celebatoryUnleash = useFlag('celebrateUnleashFrontend');
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T18:33:04Z
user: login: FredrikOseberg
body: Approved with minor comments
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-09T07:00:08Z
user: login: ivarconr
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-09T07:46:19Z

Review comments:
diff_hunk: @@ -342,6 +342,7 @@ export interface IFlagResolver {
     getAll: (context?: IFlagContext) => IFlags;
     isEnabled: (expName: IFlagKey, context?: IFlagContext) => boolean;
     getVariant: (expName: IFlagKey, context?: IFlagContext) => Variant;
+    getStaticContext: () => IFlagContext;
path: src/lib/types/experimental.ts
user: login: ivarconr
body: this is a breaking change that will break the enterprise cloud implementation. 
created_at: 2025-04-08T14:11:38Z
author_association: MEMBER
diff_hunk: @@ -158,6 +160,7 @@
   },
   "packageManager": "yarn@4.7.0",
   "dependencies": {
+    "dev": "^0.1.3",
path: frontend/package.json
user: login: ivarconr
body: this was not added on purpose. Will remove it
created_at: 2025-04-08T14:25:07Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,57 @@
+import type React from 'react';
+import { type FC, useEffect } from 'react';
+import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
+import FlagProvider, { UnleashClient } from '@unleash/proxy-client-react';
+
+const UNLEASH_API = 'https://hosted.edge.getunleash.io/api/frontend';
+const DEV_TOKEN = '';
+
+let client: UnleashClient;
+let token: string;
+let started: boolean = false;
+
+export const UnleashFlagProvider: FC<{ children?: React.ReactNode }> = ({
+    children,
+}) => {
+    const getUnleashFrontendToken = (): string => {
+        const el = document.querySelector<HTMLMetaElement>(
+            'meta[name="unleashToken"]',
+        );
+
+        const content = el?.content ?? '::unleashToken::';
+        return content === '::unleashToken::' ? DEV_TOKEN : content;
+    };
+
+    // We only want to create a single client.
+    if (!client) {
+        token = getUnleashFrontendToken();
+
+        client = new UnleashClient({
+            url: UNLEASH_API,
+            clientKey: token || 'offline',
+            refreshInterval: 1,
+            appName: 'Unleash Cloud UI',
+        });
+    }
+
+    const { uiConfig } = useUiConfig();
+
+    useEffect(() => {
+        console.log('uiConfig.unleashToken', token);
path: frontend/src/component/providers/UnleashFlagProvider/UnleashFlagProvider.tsx
user: login: FredrikOseberg
body: Remove before merging
created_at: 2025-04-09T06:58:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,6 +36,8 @@ export interface IUiConfig {
     oidcConfiguredThroughEnv?: boolean;
     samlConfiguredThroughEnv?: boolean;
     maxSessionsCount?: number;
+    unleashToken?: string;
path: frontend/src/interfaces/uiConfig.ts
user: login: FredrikOseberg
body: Is this still needed?
created_at: 2025-04-09T06:59:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -34,6 +36,8 @@ export interface IUiConfig {
     oidcConfiguredThroughEnv?: boolean;
     samlConfiguredThroughEnv?: boolean;
     maxSessionsCount?: number;
+    unleashToken?: string;
path: frontend/src/interfaces/uiConfig.ts
user: login: ivarconr
body: no
created_at: 2025-04-09T07:46:18Z
author_association: MEMBER

PR comments:
user: login: vercel[bot]
created_at: 2025-04-08T14:06:05Z
body: [vc]: #8q33NtoTtoCL7kGg626XbeXWQzSCSpoWiJaRppDp5J8=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJ1bmxlYXNoLWRvY3MiLCJyb290RGlyZWN0b3J5Ijoid2Vic2l0ZSIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiIifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL3VubGVhc2gtdGVhbS91bmxlYXNoLWRvY3MvOW9IQThHREVjV0VIRUNYQnlicWJwMnlHM0tWUiIsIm5leHRDb21taXRTdGF0dXMiOiJJR05PUkVEIiwicHJldmlld1VybCI6InVubGVhc2gtZG9jcy1naXQtZml4LXVzZS11bmxlYXNoLWluLWZyb250ZW5kLXVubGVhc2gtdGVhbS52ZXJjZWwuYXBwIn1dfQ==
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)


<details><summary>1 Skipped Deployment</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **unleash-docs** | ⬜️ Ignored ([Inspect](https://vercel.com/unleash-team/unleash-docs/9oHA8GDEcWEHECXBybqbp2yG3KVR)) | [Visit Preview](https://unleash-docs-git-fix-use-unleash-in-frontend-unleash-team.vercel.app) |  | Apr 9, 2025 10:06am |
</details>


author_association: NONE
user: login: github-actions[bot]
created_at: 2025-04-08T14:06:15Z
body: <h1>Dependency Review</h1>
The following issues were found:<ul><li>✅ 0 vulnerable package(s)</li><li>✅ 0 package(s) with incompatible licenses</li><li>✅ 0 package(s) with invalid SPDX license definitions</li><li>✅ 0 package(s) with unknown licenses.</li><li>⚠️ 1 packages with OpenSSF Scorecard issues.</li></ul>
See the Details below.<h2>OpenSSF Scorecard</h2>
<table><tr><th>Package</th><th>Version</th><th>Score</th><th>Details</th></tr>
<tr><td><a href="https://github.com/Unleash/proxy-client-react"> npm/@unleash/proxy-client-react </a></td><td>^5.0.0</td>
      <td> Unknown</td><td>Unknown</td></tr>
<tr><td><a href="https://github.com/Unleash/unleash-proxy-client-js"> npm/unleash-proxy-client </a></td><td>^3.7.3</td>
      <td> Unknown</td><td>Unknown</td></tr>
<tr><td><a href="https://github.com/Unleash/proxy-client-react"> npm/@unleash/proxy-client-react </a></td><td>5.0.0</td>
      <td> Unknown</td><td>Unknown</td></tr>
<tr><td><a href="https://github.com/scottcorgan/tiny-emitter"> npm/tiny-emitter </a></td><td>2.1.0</td>
      <td>:warning: 2.1</td><td><details><summary>Details</summary><table><tr><th>Check</th><th>Score</th><th>Reason</th></tr><tr><td>Code-Review</td><td>:green_circle: 3</td><td>Found 6/17 approved changesets -- score normalized to 3</td></tr><tr><td>Packaging</td><td>:warning: -1</td><td>packaging workflow not detected</td></tr><tr><td>Token-Permissions</td><td>:warning: -1</td><td>No tokens found</td></tr><tr><td>Pinned-Dependencies</td><td>:warning: -1</td><td>no dependencies found</td></tr><tr><td>Maintained</td><td>:warning: 0</td><td>0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0</td></tr><tr><td>Dangerous-Workflow</td><td>:warning: -1</td><td>no workflows found</td></tr><tr><td>Binary-Artifacts</td><td>:green_circle: 10</td><td>no binaries found in the repo</td></tr><tr><td>CII-Best-Practices</td><td>:warning: 0</td><td>no effort to earn an OpenSSF best practices badge detected</td></tr><tr><td>Security-Policy</td><td>:warning: 0</td><td>security policy file not detected</td></tr><tr><td>Fuzzing</td><td>:warning: 0</td><td>project is not fuzzed</td></tr><tr><td>License</td><td>:green_circle: 10</td><td>license file detected</td></tr><tr><td>Signed-Releases</td><td>:warning: -1</td><td>no releases found</td></tr><tr><td>Branch-Protection</td><td>:warning: 0</td><td>branch protection not enabled on development/release branches</td></tr><tr><td>SAST</td><td>:warning: 0</td><td>SAST tool is not run on all commits -- score normalized to 0</td></tr><tr><td>Vulnerabilities</td><td>:warning: 0</td><td>30 existing vulnerabilities detected</td></tr></table></details></td></tr>
<tr><td><a href="https://github.com/Unleash/unleash-proxy-client-js"> npm/unleash-proxy-client </a></td><td>3.7.3</td>
      <td> Unknown</td><td>Unknown</td></tr>
<tr><td><a href="https://github.com/uuidjs/uuid"> npm/uuid </a></td><td>9.0.1</td>
      <td>:green_circle: 5.7</td><td><details><summary>Details</summary><table><tr><th>Check</th><th>Score</th><th>Reason</th></tr><tr><td>Code-Review</td><td>:green_circle: 4</td><td>Found 12/30 approved changesets -- score normalized to 4</td></tr><tr><td>Maintained</td><td>:green_circle: 9</td><td>6 commit(s) and 5 issue activity found in the last 90 days -- score normalized to 9</td></tr><tr><td>Dangerous-Workflow</td><td>:green_circle: 10</td><td>no dangerous workflow patterns detected</td></tr><tr><td>Security-Policy</td><td>:green_circle: 3</td><td>security policy file detected</td></tr><tr><td>CII-Best-Practices</td><td>:warning: 0</td><td>no effort to earn an OpenSSF best practices badge detected</td></tr><tr><td>Binary-Artifacts</td><td>:green_circle: 10</td><td>no binaries found in the repo</td></tr><tr><td>Token-Permissions</td><td>:warning: 0</td><td>detected GitHub workflow tokens with excessive permissions</td></tr><tr><td>Pinned-Dependencies</td><td>:green_circle: 6</td><td>dependency not pinned by hash detected -- score normalized to 6</td></tr><tr><td>Fuzzing</td><td>:warning: 0</td><td>project is not fuzzed</td></tr><tr><td>License</td><td>:green_circle: 10</td><td>license file detected</td></tr><tr><td>Branch-Protection</td><td>:warning: -1</td><td>internal error: error during branchesHandler.setup: internal error: githubv4.Query: Resource not accessible by integration</td></tr><tr><td>Signed-Releases</td><td>:warning: -1</td><td>no releases found</td></tr><tr><td>Packaging</td><td>:green_circle: 10</td><td>packaging workflow detected</td></tr><tr><td>SAST</td><td>:green_circle: 7</td><td>SAST tool is not run on all commits -- score normalized to 7</td></tr><tr><td>Vulnerabilities</td><td>:warning: 2</td><td>8 existing vulnerabilities detected</td></tr></table></details></td></tr>
</table><h2>Scanned Files</h2>
<ul><li>frontend/package.json</li><li>frontend/yarn.lock</li></ul>


<!-- dependency-review-pr-comment-marker -->
author_association: CONTRIBUTOR
user: login: ivarconr
created_at: 2025-04-08T18:24:12Z
body: @FredrikOseberg I am not able reproduce this locally, any suggestions? 

![image](https://github.com/user-attachments/assets/4107a471-36ba-48c3-92f4-a8d679fc16f1)

**Update:**
nvm, it was a rebase issue.

author_association: MEMBER
user: login: ivarconr
created_at: 2025-04-09T11:00:26Z
body: needs to be merged first: https://github.com/bricks-software/unleash-enterprise/pull/202
author_association: MEMBER