diff --git a/packages/fern-docs/bundle/src/server/ld-adapter.ts b/packages/fern-docs/bundle/src/server/ld-adapter.ts index 46d8e6d4dd..ca087f3d01 100644 --- a/packages/fern-docs/bundle/src/server/ld-adapter.ts +++ b/packages/fern-docs/bundle/src/server/ld-adapter.ts @@ -47,12 +47,13 @@ interface LaunchDarklyInfo { clientSideId: string; contextEndpoint: string; context: ld.LDContext | undefined; - defaultFlags: Record | undefined; + defaultFlags: object | undefined; options: | { baseUrl: string | undefined; streamUrl: string | undefined; eventsUrl: string | undefined; + hash: string | undefined; } | undefined; } @@ -81,21 +82,21 @@ export async function withLaunchDarkly( streamUrl: launchDarklyConfig.options?.["stream-url"], eventsUrl: launchDarklyConfig.options?.["events-url"], }; - const defaultFlags = launchDarklyConfig["sdk-key"] + const { flags, json, hash } = launchDarklyConfig["sdk-key"] ? await fetchInitialFlags(launchDarklyConfig["sdk-key"], context, options) - : undefined; + : { flags: undefined, json: undefined, hash: undefined }; return [ { clientSideId: launchDarklyConfig["client-side-id"], contextEndpoint: launchDarklyConfig["context-endpoint"], context, - defaultFlags, - options, + defaultFlags: json, + options: { ...options, hash }, }, // Note: if sdk-key is set, then middleware will automatically switch to 100% getServerSideProps // because getServerSideProps must determine whether any given page should be rendered or not. launchDarklyConfig["sdk-key"] - ? await createLdPredicate({ flags: defaultFlags }) + ? await createLdPredicate({ flags }) : createDefaultFeatureFlagPredicate(), ]; } @@ -133,7 +134,11 @@ export const createLdPredicate = async ({ }; }; -function fetchInitialFlags( +// this is an in-memory "singleton" of all LD clients +// TODO: there should be a way to close the clients when the server shuts down +const ldClientMap = new Map(); + +async function fetchInitialFlags( sdkKey: string, context: ld.LDContext, options?: { @@ -141,17 +146,39 @@ function fetchInitialFlags( streamUrl?: string; eventsUrl?: string; } -): Promise> | undefined { +): Promise<{ + flags: Record | undefined; + json: object | undefined; + hash: string | undefined; +}> { try { - const ldClient = ld.init(sdkKey, { - baseUri: options?.baseUrl, - streamUri: options?.streamUrl, - eventsUri: options?.eventsUrl, - stream: false, + const ldClient = + ldClientMap.get(sdkKey) ?? + ld.init(sdkKey, { + baseUri: options?.baseUrl, + streamUri: options?.streamUrl, + eventsUri: options?.eventsUrl, + stream: false, + sendEvents: false, + diagnosticOptOut: true, + }); + ldClientMap.set(sdkKey, ldClient); + const flags = await ldClient.allFlagsState(context, { + clientSideOnly: true, // these flags will be passed to the client side + detailsOnlyForTrackedFlags: true, }); - return ldClient.allFlagsState(context).then((flags) => flags.allValues()); + + return { + flags: flags.allValues(), + json: flags.toJSON(), + hash: ldClient.secureModeHash(context), + }; } catch (error) { console.error(error); - return undefined; + return { + flags: undefined, + json: undefined, + hash: undefined, + }; } } diff --git a/packages/fern-docs/ui/src/atoms/types.ts b/packages/fern-docs/ui/src/atoms/types.ts index 12a86c6f45..086189d9f7 100644 --- a/packages/fern-docs/ui/src/atoms/types.ts +++ b/packages/fern-docs/ui/src/atoms/types.ts @@ -56,12 +56,13 @@ export interface LaunchDarklyInfo { clientSideId: string; contextEndpoint: string; context: LDContext | undefined; - defaultFlags: Record | undefined; + defaultFlags: object | undefined; options: | { baseUrl: string | undefined; streamUrl: string | undefined; eventsUrl: string | undefined; + hash: string | undefined; } | undefined; } diff --git a/packages/fern-docs/ui/src/feature-flags/LDFeatureFlagProvider.tsx b/packages/fern-docs/ui/src/feature-flags/LDFeatureFlagProvider.tsx index 89f7d89e63..9cc5954f84 100644 --- a/packages/fern-docs/ui/src/feature-flags/LDFeatureFlagProvider.tsx +++ b/packages/fern-docs/ui/src/feature-flags/LDFeatureFlagProvider.tsx @@ -22,12 +22,13 @@ interface Props extends PropsWithChildren { */ defaultContext?: LDContext; - defaultFlags?: Record; + defaultFlags?: object; options?: { baseUrl?: string; streamUrl?: string; eventsUrl?: string; + hash?: string; }; } @@ -90,7 +91,7 @@ const IdentifyWrapper = ({ const res = await fetch(endpoint, { credentials: "include" }); return { context: (await res.json()) as LDContext, - hash: res.headers.get("x-hash"), + hash: res.headers.get("x-secure-mode-hash"), }; }, {