From 1da3eda1f26fc4e5caca271fedc0bff6ee0d6541 Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Thu, 18 Dec 2025 17:12:36 -0600 Subject: [PATCH 01/12] chore(deps): update LaunchDarkly SDK to @launchdarkly/js-client-sdk@0.11.0 - Replaced instances of 'launchdarkly-js-client-sdk' with '@launchdarkly/js-client-sdk' across various files. - Updated package.json files in demo and toolbar to include the new SDK version. - Adjusted imports in several components to align with the new SDK structure. - Ensured compatibility with existing code by handling type mismatches where necessary. --- packages/demo/package.json | 2 +- .../demo/src/hooks/useLaunchDarklyProvider.ts | 7 ++- packages/toolbar/README.md | 6 ++- packages/toolbar/package.json | 2 + packages/toolbar/rslib.config.ts | 1 + .../tests/InternalClientProvider.test.tsx | 23 +++++----- .../new/Contexts/AddContextForm.tsx | 2 +- .../components/new/Contexts/ContextItem.tsx | 2 +- .../context/FlagSdkOverrideProvider.tsx | 10 ++++- .../Toolbar/context/api/ContextsProvider.tsx | 2 +- .../telemetry/InternalClientProvider.tsx | 44 ++++++++----------- .../src/core/ui/Toolbar/utils/context.ts | 2 +- .../src/core/ui/Toolbar/utils/localStorage.ts | 2 +- packages/toolbar/src/core/utils/analytics.ts | 3 +- packages/toolbar/src/core/utils/feedback.ts | 2 +- .../src/flags/createToolbarFlagFunction.ts | 2 +- .../src/types/hooks/AfterEvaluationHook.ts | 10 +---- .../src/types/hooks/AfterIdentifyHook.ts | 8 ++-- .../toolbar/src/types/hooks/AfterTrackHook.ts | 3 +- .../toolbar/src/types/plugins/LDClient.ts | 18 ++++++++ .../types/plugins/eventInterceptionPlugin.ts | 4 +- .../src/types/plugins/flagOverridePlugin.ts | 6 +-- packages/toolbar/src/types/plugins/plugins.ts | 2 +- pnpm-lock.yaml | 9 ++-- 24 files changed, 97 insertions(+), 75 deletions(-) create mode 100644 packages/toolbar/src/types/plugins/LDClient.ts diff --git a/packages/demo/package.json b/packages/demo/package.json index 0d71fe4f..b5a50200 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -14,8 +14,8 @@ "preview": "vite preview" }, "dependencies": { + "@launchdarkly/js-client-sdk": "^0.11.0", "@launchdarkly/toolbar": "workspace:*", - "launchdarkly-js-client-sdk": "^3.9.0", "launchdarkly-react-client-sdk": "^3.9.0", "msw": "^2.12.7", "react": "^19.2.4", diff --git a/packages/demo/src/hooks/useLaunchDarklyProvider.ts b/packages/demo/src/hooks/useLaunchDarklyProvider.ts index f56e7e8e..c4bee606 100644 --- a/packages/demo/src/hooks/useLaunchDarklyProvider.ts +++ b/packages/demo/src/hooks/useLaunchDarklyProvider.ts @@ -1,5 +1,6 @@ import { useState, useEffect, useRef } from 'react'; -import { asyncWithLDProvider, type LDPlugin } from 'launchdarkly-react-client-sdk'; +import { asyncWithLDProvider} from 'launchdarkly-react-client-sdk'; +import type { LDPlugin } from '@launchdarkly/js-client-sdk'; import { DEMO_CONFIG, demoLog } from '../config/demo'; import { startMockWorker, stopMockWorker } from '../mocks'; @@ -37,12 +38,16 @@ export function useLaunchDarklyProvider(props: UseLaunchDarklyProviderProps): Us } // Initialize LaunchDarkly provider + // launchdarkly-react-client-sdk@3.9.0 still uses old types from launchdarkly-js-sdk-common, + // but our plugins implement the new @launchdarkly/js-client-sdk types const Provider = await asyncWithLDProvider({ clientSideID, options: { baseUrl, streamUrl, eventsUrl, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - Type mismatch: launchdarkly-react-client-sdk@3.9.0 uses old types from launchdarkly-js-sdk-common plugins, }, }); diff --git a/packages/toolbar/README.md b/packages/toolbar/README.md index a5361e6c..edeec1bb 100644 --- a/packages/toolbar/README.md +++ b/packages/toolbar/README.md @@ -210,7 +210,7 @@ Works with any JavaScript framework or vanilla JS. Add this script to your `inde In your corresponding code, wherever you instantiate your LaunchDarkly JS client, be sure to pass in the following plugins: ```typescript -import * as LDClient from 'launchdarkly-js-client-sdk'; +import * as LDClient from '@launchdarkly/js-client-sdk'; import { FlagOverridePlugin, EventInterceptionPlugin } from '@launchdarkly/toolbar'; const flagOverridePlugin = new FlagOverridePlugin(); @@ -221,7 +221,7 @@ const context: LDClient.LDContext = { key: 'context-key-123abc', }; -const client = LDClient.initialize('client-side-id-123abc', context, { +const client = LDClient.createClient('client-side-id-123abc', context, { plugins: [ // any other plugins you might want flagOverridePlugin, @@ -229,6 +229,8 @@ const client = LDClient.initialize('client-side-id-123abc', context, { ], }); +client.start(); + try { await client.waitForInitialization(5); // initialization succeeded, flag values are now available diff --git a/packages/toolbar/package.json b/packages/toolbar/package.json index 90082c51..6d17d435 100644 --- a/packages/toolbar/package.json +++ b/packages/toolbar/package.json @@ -141,6 +141,7 @@ "@launchdarkly/observability": "^1.0.0", "@launchdarkly/session-replay": "^1.0.0", "@launchpad-ui/components": "^0.17.12", + "@launchdarkly/js-client-sdk": "^0.11.0", "@launchpad-ui/tokens": "^0.15.1", "@lezer/highlight": "1.2.1", "@react-aria/focus": "^3.21.3", @@ -186,6 +187,7 @@ "@angular/common": ">=14.0.0 <22.0.0", "@angular/core": ">=14.0.0 <22.0.0", "launchdarkly-js-client-sdk": ">=3.9.0 <4.0.0", + "@launchdarkly/js-client-sdk": "^0.11.0", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0", "vue": "^2.7.0 || ^3.0.0" diff --git a/packages/toolbar/rslib.config.ts b/packages/toolbar/rslib.config.ts index 4608a97e..dfab8f28 100644 --- a/packages/toolbar/rslib.config.ts +++ b/packages/toolbar/rslib.config.ts @@ -87,6 +87,7 @@ export default defineConfig({ '@angular/common': '@angular/common', rxjs: 'rxjs', 'launchdarkly-js-client-sdk': 'launchdarkly-js-client-sdk', + '@launchdarkly/js-client-sdk': '@launchdarkly/js-client-sdk', }, }, plugins: [pluginReact()], diff --git a/packages/toolbar/src/core/tests/InternalClientProvider.test.tsx b/packages/toolbar/src/core/tests/InternalClientProvider.test.tsx index 7d97e561..0fbc2b59 100644 --- a/packages/toolbar/src/core/tests/InternalClientProvider.test.tsx +++ b/packages/toolbar/src/core/tests/InternalClientProvider.test.tsx @@ -3,6 +3,7 @@ import { expect, test, describe, vi, beforeEach, afterEach } from 'vitest'; // Create mocks in hoisted scope const mockLDClient = { + start: vi.fn(), waitForInitialization: vi.fn(), variation: vi.fn(), identify: vi.fn(), @@ -12,9 +13,9 @@ const mockLDClient = { track: vi.fn(), }; -vi.mock('launchdarkly-js-client-sdk', () => { +vi.mock('@launchdarkly/js-client-sdk', () => { return { - initialize: vi.fn(), + createClient: vi.fn(), }; }); @@ -71,6 +72,7 @@ vi.mock('@launchdarkly/session-replay', () => { }; }); +import React from 'react'; import { InternalClientProvider, useInternalClient, @@ -78,11 +80,11 @@ import { } from '../ui/Toolbar/context/telemetry/InternalClientProvider'; import { setToolbarFlagClient } from '../../flags/createToolbarFlagFunction'; import * as toolbarFlagClient from '../../flags/createToolbarFlagFunction'; -import { initialize } from 'launchdarkly-js-client-sdk'; +import { createClient } from '@launchdarkly/js-client-sdk'; import { LDObserve } from '@launchdarkly/observability'; import { LDRecord } from '@launchdarkly/session-replay'; -const mockInitialize = vi.mocked(initialize); +const mockCreateClient = vi.mocked(createClient); const mockLDObserveMethods = vi.mocked(LDObserve); const mockLDRecordMethods = vi.mocked(LDRecord); @@ -114,6 +116,7 @@ describe('InternalClientProvider', () => { beforeEach(() => { vi.clearAllMocks(); + mockLDClient.start.mockResolvedValue(undefined); mockLDClient.waitForInitialization.mockResolvedValue(undefined); mockLDClient.identify.mockResolvedValue(undefined); // Return false for session replay flag by default to prevent it from starting @@ -123,7 +126,7 @@ describe('InternalClientProvider', () => { mockLDClient.off.mockImplementation(() => {}); mockLDClient.track.mockImplementation(() => {}); - mockInitialize.mockReturnValue(mockLDClient); + mockCreateClient.mockReturnValue(mockLDClient as any); mockLDObserveMethods.start.mockClear(); mockLDObserveMethods.stop.mockClear(); @@ -179,7 +182,7 @@ describe('InternalClientProvider', () => { ); await waitFor(() => { - expect(mockInitialize).toHaveBeenCalledWith( + expect(mockCreateClient).toHaveBeenCalledWith( 'test-client-id-123', { kind: 'user', @@ -207,7 +210,7 @@ describe('InternalClientProvider', () => { ); await waitFor(() => { - expect(mockInitialize).toHaveBeenCalledWith( + expect(mockCreateClient).toHaveBeenCalledWith( 'test-client-id-123', customContext, expect.objectContaining({ @@ -267,7 +270,7 @@ describe('InternalClientProvider', () => { ); await waitFor(() => { - expect(mockInitialize).toHaveBeenCalledWith( + expect(mockCreateClient).toHaveBeenCalledWith( 'test-client-id-123', expect.objectContaining({ kind: 'user', @@ -275,7 +278,7 @@ describe('InternalClientProvider', () => { anonymous: true, }), expect.objectContaining({ - baseUrl: 'https://app.ld.catamorphic.com', + baseUri: 'https://app.ld.catamorphic.com', plugins: expect.any(Array), }), ); @@ -308,7 +311,7 @@ describe('InternalClientProvider', () => { ); await waitFor(() => { - expect(mockInitialize).toHaveBeenCalledWith( + expect(mockCreateClient).toHaveBeenCalledWith( 'test-client-id-123', expect.any(Object), expect.objectContaining({ diff --git a/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/AddContextForm.tsx b/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/AddContextForm.tsx index 8733df75..33b85ddf 100644 --- a/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/AddContextForm.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/AddContextForm.tsx @@ -1,6 +1,6 @@ import { useState, useCallback, useMemo } from 'react'; import { motion, AnimatePresence } from 'motion/react'; -import type { LDContext } from 'launchdarkly-js-client-sdk'; +import type { LDContext } from '@launchdarkly/js-client-sdk'; import { useContextsContext } from '../../../context/api/ContextsProvider'; import { CancelIcon } from '../../icons'; import { EASING } from '../../../constants'; diff --git a/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/ContextItem.tsx b/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/ContextItem.tsx index 719c7ca2..2ccd9640 100644 --- a/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/ContextItem.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/ContextItem.tsx @@ -1,6 +1,6 @@ import { useState, useCallback, useMemo, useEffect, useRef, memo } from 'react'; import { motion, AnimatePresence } from 'motion/react'; -import type { LDContext } from 'launchdarkly-js-client-sdk'; +import type { LDContext } from '@launchdarkly/js-client-sdk'; import * as styles from './ContextItem.module.css'; import { CopyableText } from '../../CopyableText'; import { EditIcon, DeleteIcon, CheckIcon, CancelIcon } from '../../icons'; diff --git a/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx b/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx index 9c218682..5a6fb2ff 100644 --- a/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx @@ -107,16 +107,22 @@ export function FlagSdkOverrideProvider({ children, flagOverridePlugin }: FlagSd setIsLoading(false); // Subscribe to changes with incremental updates - const handleChange = (changes: Record) => { + // NOTE: we are overloading this function so that it can handle both the old and new browser SDKs + const handleChange = (changes: Record, keys: string[]) => { setFlags((prevFlags) => { const updatedRawFlags = ldClient.allFlags(); const newFlags = buildFlags(updatedRawFlags, apiFlags); + let changedKeys = keys; + if (changedKeys === undefined) { + changedKeys = Object.keys(changes); + } + // Only update the flags that actually changed for better performance const updatedFlags = { ...prevFlags }; let hasChanges = false; - Object.keys(changes).forEach((flagKey) => { + changedKeys.forEach((flagKey) => { if (newFlags[flagKey]) { updatedFlags[flagKey] = newFlags[flagKey]; hasChanges = true; diff --git a/packages/toolbar/src/core/ui/Toolbar/context/api/ContextsProvider.tsx b/packages/toolbar/src/core/ui/Toolbar/context/api/ContextsProvider.tsx index dc935b8f..c1e84659 100644 --- a/packages/toolbar/src/core/ui/Toolbar/context/api/ContextsProvider.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/context/api/ContextsProvider.tsx @@ -1,5 +1,5 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; -import type { LDContext } from 'launchdarkly-js-client-sdk'; +import type { LDContext } from '@launchdarkly/js-client-sdk'; import { loadContexts, saveContexts, loadActiveContext, saveActiveContext } from '../../utils/localStorage'; import { usePlugins } from '../state/PluginsProvider'; import { getContextDisplayName, getContextKey, getContextKind, getStableContextId } from '../../utils/context'; diff --git a/packages/toolbar/src/core/ui/Toolbar/context/telemetry/InternalClientProvider.tsx b/packages/toolbar/src/core/ui/Toolbar/context/telemetry/InternalClientProvider.tsx index 370de4f9..3f17d020 100644 --- a/packages/toolbar/src/core/ui/Toolbar/context/telemetry/InternalClientProvider.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/context/telemetry/InternalClientProvider.tsx @@ -1,6 +1,6 @@ import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react'; -import { initialize } from 'launchdarkly-js-client-sdk'; -import type { LDClient, LDContext } from 'launchdarkly-js-client-sdk'; +import { createClient } from '@launchdarkly/js-client-sdk'; +import type { LDClient, LDContext } from '@launchdarkly/js-client-sdk'; import Observability from '@launchdarkly/observability'; import SessionReplay from '@launchdarkly/session-replay'; import { setToolbarFlagClient } from '../../../../../flags'; @@ -143,16 +143,17 @@ export function InternalClientProvider({ }); const options = { - ...(baseUrl && { baseUrl }), - ...(streamUrl && { streamUrl }), - ...(eventsUrl && { eventsUrl }), + ...(baseUrl && { baseUri: baseUrl }), + ...(streamUrl && { streamUri: streamUrl }), + ...(eventsUrl && { eventsUri: eventsUrl }), plugins: [observabilityPlugin, sessionReplayPlugin], }; - const ldClient = initialize(clientSideId, context, options); + const ldClient = createClient(clientSideId, context, options); + ldClient.start(); clientToCleanup = ldClient; - await ldClient.waitForInitialization(5); + await ldClient.waitForInitialization({ timeout: 5 }); if (mounted) { setClient(ldClient); @@ -191,27 +192,18 @@ export function InternalClientProvider({ // Wrap identify in a Promise that resolves when the onDone callback fires // This ensures flags have been re-evaluated before we return - return new Promise((resolve) => { - client.identify( - { - kind: 'multi', - account: { - key: accountId, - }, - user: { - key: memberId, - }, + return client.identify({ + kind: 'multi', + account: { + key: accountId, }, - undefined, - (err) => { - if (err) { - console.error('[InternalClientProvider] Failed to update context:', err); - resolve(); - } else { - resolve(); - } + user: { + key: memberId, }, - ); + } + ).then(() => {}) + .catch((err) => { + console.error('[InternalClientProvider] Failed to update context:', err); }); }, [client], diff --git a/packages/toolbar/src/core/ui/Toolbar/utils/context.ts b/packages/toolbar/src/core/ui/Toolbar/utils/context.ts index db1214b5..379a188d 100644 --- a/packages/toolbar/src/core/ui/Toolbar/utils/context.ts +++ b/packages/toolbar/src/core/ui/Toolbar/utils/context.ts @@ -1,4 +1,4 @@ -import type { LDContext, LDMultiKindContext, LDSingleKindContext } from 'launchdarkly-js-client-sdk'; +import type { LDContext, LDMultiKindContext, LDSingleKindContext } from '@launchdarkly/js-client-sdk'; /** * Check if two contexts are the same using their stable IDs (kind+key) diff --git a/packages/toolbar/src/core/ui/Toolbar/utils/localStorage.ts b/packages/toolbar/src/core/ui/Toolbar/utils/localStorage.ts index 4fd9b24b..cb89eccf 100644 --- a/packages/toolbar/src/core/ui/Toolbar/utils/localStorage.ts +++ b/packages/toolbar/src/core/ui/Toolbar/utils/localStorage.ts @@ -1,4 +1,4 @@ -import type { LDContext } from 'launchdarkly-js-client-sdk'; +import type { LDContext } from '@launchdarkly/js-client-sdk'; import { ToolbarPosition, TOOLBAR_POSITIONS } from '../types/toolbar'; export const TOOLBAR_STORAGE_KEYS = { diff --git a/packages/toolbar/src/core/utils/analytics.ts b/packages/toolbar/src/core/utils/analytics.ts index e83afd06..600d9029 100644 --- a/packages/toolbar/src/core/utils/analytics.ts +++ b/packages/toolbar/src/core/utils/analytics.ts @@ -1,5 +1,4 @@ -import type { LDClient } from 'launchdarkly-js-client-sdk'; - +import type { LDClient } from '@launchdarkly/js-client-sdk'; import type { FeedbackSentiment } from '../../types/analytics'; import { isDoNotTrackEnabled } from './browser'; import { sendFeedback } from './feedback'; diff --git a/packages/toolbar/src/core/utils/feedback.ts b/packages/toolbar/src/core/utils/feedback.ts index 3c9e750b..00cfb8e8 100644 --- a/packages/toolbar/src/core/utils/feedback.ts +++ b/packages/toolbar/src/core/utils/feedback.ts @@ -1,5 +1,5 @@ import { LDRecord } from '@launchdarkly/session-replay'; -import type { LDClient } from 'launchdarkly-js-client-sdk'; +import type { LDClient } from '@launchdarkly/js-client-sdk'; export type LDFeedbackSentiment = 'positive' | 'neutral' | 'negative'; diff --git a/packages/toolbar/src/flags/createToolbarFlagFunction.ts b/packages/toolbar/src/flags/createToolbarFlagFunction.ts index 2deac391..2aab794d 100644 --- a/packages/toolbar/src/flags/createToolbarFlagFunction.ts +++ b/packages/toolbar/src/flags/createToolbarFlagFunction.ts @@ -1,4 +1,4 @@ -import type { LDClient } from 'launchdarkly-js-client-sdk'; +import type { LDClient } from '@launchdarkly/js-client-sdk'; /** * Singleton storage for the toolbar's internal LaunchDarkly client. diff --git a/packages/toolbar/src/types/hooks/AfterEvaluationHook.ts b/packages/toolbar/src/types/hooks/AfterEvaluationHook.ts index 50efefd1..dfd4a9cf 100644 --- a/packages/toolbar/src/types/hooks/AfterEvaluationHook.ts +++ b/packages/toolbar/src/types/hooks/AfterEvaluationHook.ts @@ -1,10 +1,4 @@ -import type { Hook } from 'launchdarkly-js-client-sdk'; -import type { - HookMetadata, - EvaluationSeriesData, - LDEvaluationDetail, - EvaluationSeriesContext, -} from 'launchdarkly-js-sdk-common'; +import type { Hook, HookMetadata, EvaluationSeriesData, LDEvaluationDetail, EvaluationSeriesContext } from '@launchdarkly/js-client-sdk'; import type { EventFilter, ProcessedEvent, SyntheticEventContext } from '../events'; export type AfterEvaluationHookConfig = { @@ -42,7 +36,7 @@ export class AfterEvaluationHook implements Hook { value: detail.value, variation: detail.variationIndex, default: hookContext.defaultValue, - reason: detail.reason, + reason: detail.reason ?? undefined, creationDate: Date.now(), // Note: We don't have access to version, trackEvents, or debugEventsUntilDate // from the afterEvaluation hook, so these will be undefined diff --git a/packages/toolbar/src/types/hooks/AfterIdentifyHook.ts b/packages/toolbar/src/types/hooks/AfterIdentifyHook.ts index 7c47f3eb..70c123e8 100644 --- a/packages/toolbar/src/types/hooks/AfterIdentifyHook.ts +++ b/packages/toolbar/src/types/hooks/AfterIdentifyHook.ts @@ -1,11 +1,9 @@ -import type { Hook } from 'launchdarkly-js-client-sdk'; -import type { - HookMetadata, +import type { Hook, HookMetadata, IdentifySeriesContext, IdentifySeriesData, IdentifySeriesResult, - LDContext, -} from 'launchdarkly-js-sdk-common'; + LDContext, } from '@launchdarkly/js-client-sdk'; + import type { EventFilter, ProcessedEvent, SyntheticEventContext } from '../events'; export type AfterIdentifyHookConfig = { diff --git a/packages/toolbar/src/types/hooks/AfterTrackHook.ts b/packages/toolbar/src/types/hooks/AfterTrackHook.ts index bfd867d0..7617a5e4 100644 --- a/packages/toolbar/src/types/hooks/AfterTrackHook.ts +++ b/packages/toolbar/src/types/hooks/AfterTrackHook.ts @@ -1,5 +1,4 @@ -import type { Hook } from 'launchdarkly-js-client-sdk'; -import type { HookMetadata, TrackSeriesContext } from 'launchdarkly-js-sdk-common'; +import type { Hook, HookMetadata, TrackSeriesContext } from '@launchdarkly/js-client-sdk'; import type { EventFilter, ProcessedEvent, SyntheticEventContext } from '../events'; export type AfterTrackHookConfig = { diff --git a/packages/toolbar/src/types/plugins/LDClient.ts b/packages/toolbar/src/types/plugins/LDClient.ts new file mode 100644 index 00000000..fc5c122d --- /dev/null +++ b/packages/toolbar/src/types/plugins/LDClient.ts @@ -0,0 +1,18 @@ +import { LDFlagSet, Hook, LDPluginEnvironmentMetadata } from "launchdarkly-js-client-sdk"; +import { LDIdentifyResult } from "@launchdarkly/js-client-sdk"; + +/** + * LDClient based on the LDClient type in the SDK package with + * a narrower structure to ensure that they can be used by + * our plugins. + */ +export interface LDClient { + track(key: string, data?: any, metricValue?: number): void; + identify(ctx: any): Promise | Promise | Promise; + addHook(hook: Hook): void; + getHooks?(metadata: LDPluginEnvironmentMetadata): Hook[]; + allFlags(): LDFlagSet; + getContext(): any; + on(key: string, callback: (...args: any[]) => void): void; + off(key: string, callback: (...args: any[]) => void): void; +} diff --git a/packages/toolbar/src/types/plugins/eventInterceptionPlugin.ts b/packages/toolbar/src/types/plugins/eventInterceptionPlugin.ts index 47d2218e..ef5085e9 100644 --- a/packages/toolbar/src/types/plugins/eventInterceptionPlugin.ts +++ b/packages/toolbar/src/types/plugins/eventInterceptionPlugin.ts @@ -1,4 +1,4 @@ -import type { Hook, LDClient, LDPluginEnvironmentMetadata, LDPluginMetadata } from 'launchdarkly-js-client-sdk'; +import type { Hook, LDClient, LDPluginEnvironmentMetadata, LDPluginMetadata } from '@launchdarkly/js-client-sdk'; import { AfterTrackHook, AfterIdentifyHook, AfterEvaluationHook, EventStore } from '../hooks'; import type { EventFilter, ProcessedEvent } from '../events'; import type { IEventInterceptionPlugin } from './plugins'; @@ -84,7 +84,7 @@ export class EventInterceptionPlugin implements IEventInterceptionPlugin { return [this.afterTrackHook, this.afterIdentifyHook, this.afterEvaluationHook]; } - register(ldClient: LDClient): void { + register(ldClient: LDClient, _metadata: LDPluginEnvironmentMetadata): void { this.ldClient = ldClient; } diff --git a/packages/toolbar/src/types/plugins/flagOverridePlugin.ts b/packages/toolbar/src/types/plugins/flagOverridePlugin.ts index 548326f0..931e28fa 100644 --- a/packages/toolbar/src/types/plugins/flagOverridePlugin.ts +++ b/packages/toolbar/src/types/plugins/flagOverridePlugin.ts @@ -1,11 +1,11 @@ import type { - LDClient, LDDebugOverride, LDPluginMetadata, LDFlagSet, Hook, LDPluginEnvironmentMetadata, -} from 'launchdarkly-js-client-sdk'; + LDClient, +} from '@launchdarkly/js-client-sdk'; import type { IFlagOverridePlugin } from './plugins'; /** @@ -48,7 +48,7 @@ export class FlagOverridePlugin implements IFlagOverridePlugin { /** * Called when the plugin is registered with the LaunchDarkly client */ - register(ldClient: LDClient): void { + register(ldClient: LDClient, _metadata: LDPluginEnvironmentMetadata): void { this.ldClient = ldClient; } diff --git a/packages/toolbar/src/types/plugins/plugins.ts b/packages/toolbar/src/types/plugins/plugins.ts index 592d6517..ac22f493 100644 --- a/packages/toolbar/src/types/plugins/plugins.ts +++ b/packages/toolbar/src/types/plugins/plugins.ts @@ -1,4 +1,4 @@ -import type { LDClient, LDDebugOverride, LDFlagSet, LDFlagValue, LDPlugin } from 'launchdarkly-js-client-sdk'; +import type { LDClient, LDFlagSet, LDFlagValue, LDPlugin, LDDebugOverride } from '@launchdarkly/js-client-sdk'; import type { ProcessedEvent } from '../events'; export interface IFlagOverridePlugin extends LDPlugin, LDDebugOverride { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 56831f39..afe1ebdf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,12 +53,12 @@ importers: packages/demo: dependencies: + '@launchdarkly/js-client-sdk': + specifier: ^0.11.0 + version: 0.11.0 '@launchdarkly/toolbar': specifier: workspace:* version: link:../toolbar - launchdarkly-js-client-sdk: - specifier: ^3.9.0 - version: 3.9.0 launchdarkly-react-client-sdk: specifier: ^3.9.0 version: 3.9.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -150,6 +150,9 @@ importers: '@codemirror/view': specifier: ^6.39.11 version: 6.39.11 + '@launchdarkly/js-client-sdk': + specifier: ^0.11.0 + version: 0.11.0 '@launchdarkly/observability': specifier: ^1.0.0 version: 1.0.0 From 3b553162eb9ff54fa5644a511e35038904aa208f Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Thu, 18 Dec 2025 18:00:40 -0600 Subject: [PATCH 02/12] refactor(tests): update imports to use @launchdarkly/js-client-sdk - Changed import paths in test files to reflect the new SDK structure. - Updated mock implementations to align with the new SDK methods. - Ensured compatibility with existing tests by adjusting mock behavior. --- packages/demo/src/hooks/useLaunchDarklyProvider.ts | 2 +- .../toolbar/src/core/tests/ContextItem.test.tsx | 2 +- .../src/core/tests/ContextsProvider.test.tsx | 2 +- .../core/tests/createToolbarFlagFunction.test.ts | 2 +- .../core/tests/hooks/AfterEvaluationHook.test.ts | 2 +- .../src/core/tests/hooks/AfterIdentifyHook.test.ts | 4 ++-- .../context/telemetry/InternalClientProvider.tsx | 13 +++++++------ .../toolbar/src/types/hooks/AfterEvaluationHook.ts | 8 +++++++- .../toolbar/src/types/hooks/AfterIdentifyHook.ts | 7 +++++-- 9 files changed, 26 insertions(+), 16 deletions(-) diff --git a/packages/demo/src/hooks/useLaunchDarklyProvider.ts b/packages/demo/src/hooks/useLaunchDarklyProvider.ts index c4bee606..3448e244 100644 --- a/packages/demo/src/hooks/useLaunchDarklyProvider.ts +++ b/packages/demo/src/hooks/useLaunchDarklyProvider.ts @@ -1,5 +1,5 @@ import { useState, useEffect, useRef } from 'react'; -import { asyncWithLDProvider} from 'launchdarkly-react-client-sdk'; +import { asyncWithLDProvider } from 'launchdarkly-react-client-sdk'; import type { LDPlugin } from '@launchdarkly/js-client-sdk'; import { DEMO_CONFIG, demoLog } from '../config/demo'; import { startMockWorker, stopMockWorker } from '../mocks'; diff --git a/packages/toolbar/src/core/tests/ContextItem.test.tsx b/packages/toolbar/src/core/tests/ContextItem.test.tsx index 3ced1fc1..694237b1 100644 --- a/packages/toolbar/src/core/tests/ContextItem.test.tsx +++ b/packages/toolbar/src/core/tests/ContextItem.test.tsx @@ -1,6 +1,6 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { expect, test, describe, vi, beforeEach } from 'vitest'; -import type { LDContext } from 'launchdarkly-js-client-sdk'; +import type { LDContext } from '@launchdarkly/js-client-sdk'; import { ContextItem } from '../ui/Toolbar/components/new/Contexts/ContextItem'; import { ContextsProvider } from '../ui/Toolbar/context/api/ContextsProvider'; import '@testing-library/jest-dom/vitest'; diff --git a/packages/toolbar/src/core/tests/ContextsProvider.test.tsx b/packages/toolbar/src/core/tests/ContextsProvider.test.tsx index 3a18b5de..b9d08c04 100644 --- a/packages/toolbar/src/core/tests/ContextsProvider.test.tsx +++ b/packages/toolbar/src/core/tests/ContextsProvider.test.tsx @@ -1,6 +1,6 @@ import { render, screen, waitFor, act } from '@testing-library/react'; import { expect, test, describe, vi, beforeEach } from 'vitest'; -import type { LDContext } from 'launchdarkly-js-client-sdk'; +import type { LDContext } from '@launchdarkly/js-client-sdk'; import { ContextsProvider, useContextsContext } from '../ui/Toolbar/context/api/ContextsProvider'; import '@testing-library/jest-dom/vitest'; import React from 'react'; diff --git a/packages/toolbar/src/core/tests/createToolbarFlagFunction.test.ts b/packages/toolbar/src/core/tests/createToolbarFlagFunction.test.ts index 36d165ad..7afc4aa5 100644 --- a/packages/toolbar/src/core/tests/createToolbarFlagFunction.test.ts +++ b/packages/toolbar/src/core/tests/createToolbarFlagFunction.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect, vi, beforeEach } from 'vitest'; -import type { LDClient } from 'launchdarkly-js-client-sdk'; +import type { LDClient } from '@launchdarkly/js-client-sdk'; import { createToolbarFlagFunction, setToolbarFlagClient, diff --git a/packages/toolbar/src/core/tests/hooks/AfterEvaluationHook.test.ts b/packages/toolbar/src/core/tests/hooks/AfterEvaluationHook.test.ts index 4813d687..c17dd624 100644 --- a/packages/toolbar/src/core/tests/hooks/AfterEvaluationHook.test.ts +++ b/packages/toolbar/src/core/tests/hooks/AfterEvaluationHook.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { AfterEvaluationHook, type AfterEvaluationHookConfig } from '../../../types/hooks/AfterEvaluationHook'; -import type { EvaluationSeriesData, EvaluationSeriesContext, LDEvaluationDetail } from 'launchdarkly-js-sdk-common'; +import type { EvaluationSeriesData, EvaluationSeriesContext, LDEvaluationDetail } from '@launchdarkly/js-client-sdk'; import { EventFilter, ProcessedEvent } from '../../../types'; // Mock console methods to avoid noise in test output diff --git a/packages/toolbar/src/core/tests/hooks/AfterIdentifyHook.test.ts b/packages/toolbar/src/core/tests/hooks/AfterIdentifyHook.test.ts index 3268bfec..730fc881 100644 --- a/packages/toolbar/src/core/tests/hooks/AfterIdentifyHook.test.ts +++ b/packages/toolbar/src/core/tests/hooks/AfterIdentifyHook.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { AfterIdentifyHook, type AfterIdentifyHookConfig } from '../../../types/hooks/AfterIdentifyHook'; -import type { IdentifySeriesData, IdentifySeriesContext, IdentifySeriesResult } from 'launchdarkly-js-sdk-common'; +import type { IdentifySeriesData, IdentifySeriesContext, IdentifySeriesResult } from '@launchdarkly/js-client-sdk'; import { EventFilter, ProcessedEvent } from '../../../types'; // Mock console methods to avoid noise in test output @@ -106,7 +106,7 @@ describe('AfterIdentifyHook', () => { it('should handle contexts without key', () => { const contextWithoutKey = { - context: { kind: 'user', name: 'No Key User' }, + context: { kind: 'user', name: 'No Key User', key: '' }, }; hook.afterIdentify(contextWithoutKey, mockData, mockResult); diff --git a/packages/toolbar/src/core/ui/Toolbar/context/telemetry/InternalClientProvider.tsx b/packages/toolbar/src/core/ui/Toolbar/context/telemetry/InternalClientProvider.tsx index 3f17d020..d26e020c 100644 --- a/packages/toolbar/src/core/ui/Toolbar/context/telemetry/InternalClientProvider.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/context/telemetry/InternalClientProvider.tsx @@ -192,7 +192,8 @@ export function InternalClientProvider({ // Wrap identify in a Promise that resolves when the onDone callback fires // This ensures flags have been re-evaluated before we return - return client.identify({ + return client + .identify({ kind: 'multi', account: { key: accountId, @@ -200,11 +201,11 @@ export function InternalClientProvider({ user: { key: memberId, }, - } - ).then(() => {}) - .catch((err) => { - console.error('[InternalClientProvider] Failed to update context:', err); - }); + }) + .then(() => {}) + .catch((err) => { + console.error('[InternalClientProvider] Failed to update context:', err); + }); }, [client], ); diff --git a/packages/toolbar/src/types/hooks/AfterEvaluationHook.ts b/packages/toolbar/src/types/hooks/AfterEvaluationHook.ts index dfd4a9cf..e2da3974 100644 --- a/packages/toolbar/src/types/hooks/AfterEvaluationHook.ts +++ b/packages/toolbar/src/types/hooks/AfterEvaluationHook.ts @@ -1,4 +1,10 @@ -import type { Hook, HookMetadata, EvaluationSeriesData, LDEvaluationDetail, EvaluationSeriesContext } from '@launchdarkly/js-client-sdk'; +import type { + Hook, + HookMetadata, + EvaluationSeriesData, + LDEvaluationDetail, + EvaluationSeriesContext, +} from '@launchdarkly/js-client-sdk'; import type { EventFilter, ProcessedEvent, SyntheticEventContext } from '../events'; export type AfterEvaluationHookConfig = { diff --git a/packages/toolbar/src/types/hooks/AfterIdentifyHook.ts b/packages/toolbar/src/types/hooks/AfterIdentifyHook.ts index 70c123e8..accfaf34 100644 --- a/packages/toolbar/src/types/hooks/AfterIdentifyHook.ts +++ b/packages/toolbar/src/types/hooks/AfterIdentifyHook.ts @@ -1,8 +1,11 @@ -import type { Hook, HookMetadata, +import type { + Hook, + HookMetadata, IdentifySeriesContext, IdentifySeriesData, IdentifySeriesResult, - LDContext, } from '@launchdarkly/js-client-sdk'; + LDContext, +} from '@launchdarkly/js-client-sdk'; import type { EventFilter, ProcessedEvent, SyntheticEventContext } from '../events'; From fb4db141ac15798d4f1a2c70fe0d829ab1af66bc Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Fri, 19 Dec 2025 10:59:26 -0600 Subject: [PATCH 03/12] chore: update test and script references --- packages/toolbar/src/core/tests/context-utils.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolbar/src/core/tests/context-utils.test.ts b/packages/toolbar/src/core/tests/context-utils.test.ts index 4c185ac8..d834565b 100644 --- a/packages/toolbar/src/core/tests/context-utils.test.ts +++ b/packages/toolbar/src/core/tests/context-utils.test.ts @@ -1,5 +1,5 @@ import { expect, test, describe } from 'vitest'; -import type { LDContext, LDMultiKindContext, LDSingleKindContext } from 'launchdarkly-js-client-sdk'; +import type { LDContext, LDMultiKindContext, LDSingleKindContext } from '@launchdarkly/js-client-sdk'; import { areContextsEqual, getContextKind, From cfd0f652ba42af8f710062ae47fa57bff1b94a3f Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Wed, 24 Dec 2025 10:41:32 -0500 Subject: [PATCH 04/12] refactor: adding new compat interfaces - Added new compat interfaces so that toolbar could recognize browser v3 and v4 clients. - Changed import paths to use '@launchdarkly/toolbar' and updated related types. > Eventually we should probably provide this compat layer in the browser SDK code itself. --- .../demo/src/hooks/useLaunchDarklyProvider.ts | 6 +-- .../Toolbar/context/api/ContextsProvider.tsx | 2 +- .../toolbar/src/core/utils/urlOverrides.ts | 2 +- packages/toolbar/src/types/compat/Hook.ts | 50 +++++++++++++++++++ packages/toolbar/src/types/compat/LDClient.ts | 15 ++++++ packages/toolbar/src/types/compat/LDPlugin.ts | 30 +++++++++++ .../types/plugins/eventInterceptionPlugin.ts | 4 +- .../src/types/plugins/flagOverridePlugin.ts | 2 +- packages/toolbar/src/types/plugins/index.ts | 2 + packages/toolbar/src/types/plugins/plugins.ts | 4 +- 10 files changed, 107 insertions(+), 10 deletions(-) create mode 100644 packages/toolbar/src/types/compat/Hook.ts create mode 100644 packages/toolbar/src/types/compat/LDClient.ts create mode 100644 packages/toolbar/src/types/compat/LDPlugin.ts diff --git a/packages/demo/src/hooks/useLaunchDarklyProvider.ts b/packages/demo/src/hooks/useLaunchDarklyProvider.ts index 3448e244..d5227d8b 100644 --- a/packages/demo/src/hooks/useLaunchDarklyProvider.ts +++ b/packages/demo/src/hooks/useLaunchDarklyProvider.ts @@ -1,6 +1,6 @@ import { useState, useEffect, useRef } from 'react'; import { asyncWithLDProvider } from 'launchdarkly-react-client-sdk'; -import type { LDPlugin } from '@launchdarkly/js-client-sdk'; +import type { LDPlugin } from '@launchdarkly/toolbar'; import { DEMO_CONFIG, demoLog } from '../config/demo'; import { startMockWorker, stopMockWorker } from '../mocks'; @@ -38,16 +38,12 @@ export function useLaunchDarklyProvider(props: UseLaunchDarklyProviderProps): Us } // Initialize LaunchDarkly provider - // launchdarkly-react-client-sdk@3.9.0 still uses old types from launchdarkly-js-sdk-common, - // but our plugins implement the new @launchdarkly/js-client-sdk types const Provider = await asyncWithLDProvider({ clientSideID, options: { baseUrl, streamUrl, eventsUrl, - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - Type mismatch: launchdarkly-react-client-sdk@3.9.0 uses old types from launchdarkly-js-sdk-common plugins, }, }); diff --git a/packages/toolbar/src/core/ui/Toolbar/context/api/ContextsProvider.tsx b/packages/toolbar/src/core/ui/Toolbar/context/api/ContextsProvider.tsx index c1e84659..5d95b1fa 100644 --- a/packages/toolbar/src/core/ui/Toolbar/context/api/ContextsProvider.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/context/api/ContextsProvider.tsx @@ -238,7 +238,7 @@ export const ContextsProvider = ({ children }: { children: React.ReactNode }) => // and setContext would skip the identify call due to equality check isSettingContextRef.current = true; ldClient - .identify(savedActiveContext) + .identify(savedActiveContext)! .then(() => { // Ensure state is in sync (it should already be from useState initialization) setActiveContextId(savedStableId); diff --git a/packages/toolbar/src/core/utils/urlOverrides.ts b/packages/toolbar/src/core/utils/urlOverrides.ts index 07134aae..647a5663 100644 --- a/packages/toolbar/src/core/utils/urlOverrides.ts +++ b/packages/toolbar/src/core/utils/urlOverrides.ts @@ -5,7 +5,7 @@ * The state includes: flag overrides, contexts, settings, starred flags, etc. */ -import { LDContext } from 'launchdarkly-js-client-sdk'; +import { LDContext } from '@launchdarkly/js-client-sdk'; import { ToolbarSettings } from '../ui/Toolbar/utils/localStorage'; /** Current version of the shared state format */ diff --git a/packages/toolbar/src/types/compat/Hook.ts b/packages/toolbar/src/types/compat/Hook.ts new file mode 100644 index 00000000..e548f0d3 --- /dev/null +++ b/packages/toolbar/src/types/compat/Hook.ts @@ -0,0 +1,50 @@ +import type { + EvaluationSeriesContext, + EvaluationSeriesData, + LDEvaluationReason, + LDFlagValue, + Hook as LDHook, +} from '@launchdarkly/js-client-sdk' + +/** + * Interface for extending SDK functionality via hooks that is compatible with both javascript client versions (<=3.x and >=4.x). + */ +export type Hook = Omit & { + /** + * This method is called during the execution of the variation method + * after the flag value has been determined. The method is executed synchronously. + * + * @param hookContext Contains read-only information about the evaluation + * being performed. + * @param data A record associated with each stage of hook invocations. Each + * stage is called with the data of the previous stage for a series. + * @param detail The result of the evaluation. This value should not be + * modified. + * @returns Data to use when executing the next state of the hook in the evaluation series. It is + * recommended to expand the previous input into the return. This helps ensure your stage remains + * compatible moving forward as more stages are added. + * ```js + * return {...data, "my-new-field": /*my data/*} + * ``` + */ + afterEvaluation?( + hookContext: EvaluationSeriesContext, + data: EvaluationSeriesData, + detail: { + /** + * The result of the flag evaluation. This will be either one of the flag's variations or + * the default value that was passed to `LDClient.variationDetail`. + */ + value: LDFlagValue + /** + * The index of the returned value within the flag's list of variations, e.g. 0 for the + * first variation-- or `null` if the default value was returned. + */ + variationIndex?: number | null + /** + * An object describing the main factor that influenced the flag evaluation value. + */ + reason?: LDEvaluationReason | null + }, + ): EvaluationSeriesData +} diff --git a/packages/toolbar/src/types/compat/LDClient.ts b/packages/toolbar/src/types/compat/LDClient.ts new file mode 100644 index 00000000..8cd69933 --- /dev/null +++ b/packages/toolbar/src/types/compat/LDClient.ts @@ -0,0 +1,15 @@ +import { LDFlagSet, LDIdentifyResult } from "@launchdarkly/js-client-sdk"; +import type { Hook } from "./Hook" + +/** + * Interface for the LaunchDarkly client that is compatible with both javascript client versions (<=3.x and >=4.x). + */ +export interface LDClient { + track(key: string, data?: any, metricValue?: number): void + identify(ctx: any): Promise | Promise | Promise + addHook(hook: Hook): void + allFlags(): LDFlagSet; + getContext(): any; + on(key: string, callback: (...args: any[]) => void): void; + off(key: string, callback: (...args: any[]) => void): void; +} diff --git a/packages/toolbar/src/types/compat/LDPlugin.ts b/packages/toolbar/src/types/compat/LDPlugin.ts new file mode 100644 index 00000000..4b2ea895 --- /dev/null +++ b/packages/toolbar/src/types/compat/LDPlugin.ts @@ -0,0 +1,30 @@ +import { LDPluginEnvironmentMetadata, LDPluginMetadata } from "@launchdarkly/js-client-sdk" +import { LDClient} from "./LDClient" +import type { Hook } from "./Hook" + +/** + * Interface for LaunchDarkly plugins that are compatible with both javascript client versions (<=3.x and >=4.x). + */ +export interface LDPlugin { + /** + * Returns the metadata for the plugin. + */ + getMetadata(): LDPluginMetadata + + /** + * Registers the plugin with the LaunchDarkly client. + * @param client - The LaunchDarkly client. + * @param environmentMetadata - The environment metadata. + */ + register( + client: LDClient, + environmentMetadata?: LDPluginEnvironmentMetadata, + ): void + + /** + * Returns the hooks for the plugin. + * @param metadata - The environment metadata. + * @returns The hooks for the plugin. + */ + getHooks?(metadata: LDPluginEnvironmentMetadata): Hook[] +} diff --git a/packages/toolbar/src/types/plugins/eventInterceptionPlugin.ts b/packages/toolbar/src/types/plugins/eventInterceptionPlugin.ts index ef5085e9..3325e189 100644 --- a/packages/toolbar/src/types/plugins/eventInterceptionPlugin.ts +++ b/packages/toolbar/src/types/plugins/eventInterceptionPlugin.ts @@ -1,5 +1,7 @@ -import type { Hook, LDClient, LDPluginEnvironmentMetadata, LDPluginMetadata } from '@launchdarkly/js-client-sdk'; +import type { LDPluginEnvironmentMetadata, LDPluginMetadata } from '@launchdarkly/js-client-sdk'; import { AfterTrackHook, AfterIdentifyHook, AfterEvaluationHook, EventStore } from '../hooks'; +import type { LDClient } from '../compat/LDClient'; +import type { Hook } from '../compat/Hook'; import type { EventFilter, ProcessedEvent } from '../events'; import type { IEventInterceptionPlugin } from './plugins'; import { ANALYTICS_EVENT_PREFIX } from '../analytics'; diff --git a/packages/toolbar/src/types/plugins/flagOverridePlugin.ts b/packages/toolbar/src/types/plugins/flagOverridePlugin.ts index 931e28fa..15cdff3f 100644 --- a/packages/toolbar/src/types/plugins/flagOverridePlugin.ts +++ b/packages/toolbar/src/types/plugins/flagOverridePlugin.ts @@ -4,9 +4,9 @@ import type { LDFlagSet, Hook, LDPluginEnvironmentMetadata, - LDClient, } from '@launchdarkly/js-client-sdk'; import type { IFlagOverridePlugin } from './plugins'; +import { LDClient } from '../compat/LDClient'; /** * Configuration options for the FlagOverridePlugin diff --git a/packages/toolbar/src/types/plugins/index.ts b/packages/toolbar/src/types/plugins/index.ts index 3562bf0a..7fb86283 100644 --- a/packages/toolbar/src/types/plugins/index.ts +++ b/packages/toolbar/src/types/plugins/index.ts @@ -1,3 +1,5 @@ export * from './eventInterceptionPlugin'; export * from './flagOverridePlugin'; export * from './plugins'; +export * from '../compat/LDPlugin'; +export * from '../compat/LDClient'; diff --git a/packages/toolbar/src/types/plugins/plugins.ts b/packages/toolbar/src/types/plugins/plugins.ts index ac22f493..d6bf8616 100644 --- a/packages/toolbar/src/types/plugins/plugins.ts +++ b/packages/toolbar/src/types/plugins/plugins.ts @@ -1,4 +1,6 @@ -import type { LDClient, LDFlagSet, LDFlagValue, LDPlugin, LDDebugOverride } from '@launchdarkly/js-client-sdk'; +import type { LDFlagSet, LDFlagValue, LDDebugOverride } from '@launchdarkly/js-client-sdk'; +import type { LDPlugin } from '../compat/LDPlugin'; +import type { LDClient } from '../compat/LDClient'; import type { ProcessedEvent } from '../events'; export interface IFlagOverridePlugin extends LDPlugin, LDDebugOverride { From 20a9407516e552ea2cc52574e3903dd94f7d54a1 Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Mon, 12 Jan 2026 19:30:52 -0600 Subject: [PATCH 05/12] styles: fix lint issues --- packages/toolbar/src/types/compat/Hook.ts | 88 +++++++++---------- packages/toolbar/src/types/compat/LDClient.ts | 10 +-- packages/toolbar/src/types/compat/LDPlugin.ts | 41 ++++----- 3 files changed, 68 insertions(+), 71 deletions(-) diff --git a/packages/toolbar/src/types/compat/Hook.ts b/packages/toolbar/src/types/compat/Hook.ts index e548f0d3..bd8be250 100644 --- a/packages/toolbar/src/types/compat/Hook.ts +++ b/packages/toolbar/src/types/compat/Hook.ts @@ -1,50 +1,50 @@ import type { - EvaluationSeriesContext, - EvaluationSeriesData, - LDEvaluationReason, - LDFlagValue, - Hook as LDHook, -} from '@launchdarkly/js-client-sdk' + EvaluationSeriesContext, + EvaluationSeriesData, + LDEvaluationReason, + LDFlagValue, + Hook as LDHook, +} from '@launchdarkly/js-client-sdk'; /** * Interface for extending SDK functionality via hooks that is compatible with both javascript client versions (<=3.x and >=4.x). */ export type Hook = Omit & { - /** - * This method is called during the execution of the variation method - * after the flag value has been determined. The method is executed synchronously. - * - * @param hookContext Contains read-only information about the evaluation - * being performed. - * @param data A record associated with each stage of hook invocations. Each - * stage is called with the data of the previous stage for a series. - * @param detail The result of the evaluation. This value should not be - * modified. - * @returns Data to use when executing the next state of the hook in the evaluation series. It is - * recommended to expand the previous input into the return. This helps ensure your stage remains - * compatible moving forward as more stages are added. - * ```js - * return {...data, "my-new-field": /*my data/*} - * ``` - */ - afterEvaluation?( - hookContext: EvaluationSeriesContext, - data: EvaluationSeriesData, - detail: { - /** - * The result of the flag evaluation. This will be either one of the flag's variations or - * the default value that was passed to `LDClient.variationDetail`. - */ - value: LDFlagValue - /** - * The index of the returned value within the flag's list of variations, e.g. 0 for the - * first variation-- or `null` if the default value was returned. - */ - variationIndex?: number | null - /** - * An object describing the main factor that influenced the flag evaluation value. - */ - reason?: LDEvaluationReason | null - }, - ): EvaluationSeriesData -} + /** + * This method is called during the execution of the variation method + * after the flag value has been determined. The method is executed synchronously. + * + * @param hookContext Contains read-only information about the evaluation + * being performed. + * @param data A record associated with each stage of hook invocations. Each + * stage is called with the data of the previous stage for a series. + * @param detail The result of the evaluation. This value should not be + * modified. + * @returns Data to use when executing the next state of the hook in the evaluation series. It is + * recommended to expand the previous input into the return. This helps ensure your stage remains + * compatible moving forward as more stages are added. + * ```js + * return {...data, "my-new-field": /*my data/*} + * ``` + */ + afterEvaluation?( + hookContext: EvaluationSeriesContext, + data: EvaluationSeriesData, + detail: { + /** + * The result of the flag evaluation. This will be either one of the flag's variations or + * the default value that was passed to `LDClient.variationDetail`. + */ + value: LDFlagValue; + /** + * The index of the returned value within the flag's list of variations, e.g. 0 for the + * first variation-- or `null` if the default value was returned. + */ + variationIndex?: number | null; + /** + * An object describing the main factor that influenced the flag evaluation value. + */ + reason?: LDEvaluationReason | null; + }, + ): EvaluationSeriesData; +}; diff --git a/packages/toolbar/src/types/compat/LDClient.ts b/packages/toolbar/src/types/compat/LDClient.ts index 8cd69933..8d60a2b8 100644 --- a/packages/toolbar/src/types/compat/LDClient.ts +++ b/packages/toolbar/src/types/compat/LDClient.ts @@ -1,13 +1,13 @@ -import { LDFlagSet, LDIdentifyResult } from "@launchdarkly/js-client-sdk"; -import type { Hook } from "./Hook" +import { LDFlagSet, LDIdentifyResult } from '@launchdarkly/js-client-sdk'; +import type { Hook } from './Hook'; /** * Interface for the LaunchDarkly client that is compatible with both javascript client versions (<=3.x and >=4.x). */ export interface LDClient { - track(key: string, data?: any, metricValue?: number): void - identify(ctx: any): Promise | Promise | Promise - addHook(hook: Hook): void + track(key: string, data?: any, metricValue?: number): void; + identify(ctx: any): Promise | Promise | Promise; + addHook(hook: Hook): void; allFlags(): LDFlagSet; getContext(): any; on(key: string, callback: (...args: any[]) => void): void; diff --git a/packages/toolbar/src/types/compat/LDPlugin.ts b/packages/toolbar/src/types/compat/LDPlugin.ts index 4b2ea895..e5c6db0b 100644 --- a/packages/toolbar/src/types/compat/LDPlugin.ts +++ b/packages/toolbar/src/types/compat/LDPlugin.ts @@ -1,30 +1,27 @@ -import { LDPluginEnvironmentMetadata, LDPluginMetadata } from "@launchdarkly/js-client-sdk" -import { LDClient} from "./LDClient" -import type { Hook } from "./Hook" +import { LDPluginEnvironmentMetadata, LDPluginMetadata } from '@launchdarkly/js-client-sdk'; +import { LDClient } from './LDClient'; +import type { Hook } from './Hook'; /** * Interface for LaunchDarkly plugins that are compatible with both javascript client versions (<=3.x and >=4.x). */ export interface LDPlugin { - /** - * Returns the metadata for the plugin. - */ - getMetadata(): LDPluginMetadata + /** + * Returns the metadata for the plugin. + */ + getMetadata(): LDPluginMetadata; - /** - * Registers the plugin with the LaunchDarkly client. - * @param client - The LaunchDarkly client. - * @param environmentMetadata - The environment metadata. - */ - register( - client: LDClient, - environmentMetadata?: LDPluginEnvironmentMetadata, - ): void + /** + * Registers the plugin with the LaunchDarkly client. + * @param client - The LaunchDarkly client. + * @param environmentMetadata - The environment metadata. + */ + register(client: LDClient, environmentMetadata?: LDPluginEnvironmentMetadata): void; - /** - * Returns the hooks for the plugin. - * @param metadata - The environment metadata. - * @returns The hooks for the plugin. - */ - getHooks?(metadata: LDPluginEnvironmentMetadata): Hook[] + /** + * Returns the hooks for the plugin. + * @param metadata - The environment metadata. + * @returns The hooks for the plugin. + */ + getHooks?(metadata: LDPluginEnvironmentMetadata): Hook[]; } From 9af8a58a01dbd08d4a5f638df02a2fac06b07002 Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Tue, 13 Jan 2026 10:16:39 -0600 Subject: [PATCH 06/12] fix: intellisense for plugin compat --- packages/demo/package.json | 2 +- .../demo/src/hooks/useLaunchDarklyProvider.ts | 3 +- packages/toolbar/README.md | 6 +-- packages/toolbar/package.json | 2 +- .../src/core/tests/ContextItem.test.tsx | 2 +- .../src/core/tests/ContextsProvider.test.tsx | 2 +- .../tests/InternalClientProvider.test.tsx | 23 ++++----- .../src/core/tests/context-utils.test.ts | 2 +- .../tests/createToolbarFlagFunction.test.ts | 2 +- .../tests/hooks/AfterEvaluationHook.test.ts | 2 +- .../tests/hooks/AfterIdentifyHook.test.ts | 4 +- .../new/Contexts/AddContextForm.tsx | 2 +- .../components/new/Contexts/ContextItem.tsx | 2 +- .../context/FlagSdkOverrideProvider.tsx | 3 +- .../Toolbar/context/api/ContextsProvider.tsx | 4 +- .../telemetry/InternalClientProvider.tsx | 47 +++++++++-------- .../src/core/ui/Toolbar/utils/context.ts | 2 +- .../src/core/ui/Toolbar/utils/localStorage.ts | 2 +- packages/toolbar/src/core/utils/analytics.ts | 2 +- .../toolbar/src/core/utils/urlOverrides.ts | 2 +- .../src/flags/createToolbarFlagFunction.ts | 2 +- packages/toolbar/src/types/compat/Hook.ts | 50 ------------------- packages/toolbar/src/types/compat/LDClient.ts | 15 ------ packages/toolbar/src/types/compat/LDPlugin.ts | 27 ---------- .../src/types/hooks/AfterEvaluationHook.ts | 6 +-- .../src/types/hooks/AfterIdentifyHook.ts | 5 +- .../toolbar/src/types/hooks/AfterTrackHook.ts | 3 +- .../toolbar/src/types/plugins/LDClient.ts | 4 +- .../types/plugins/eventInterceptionPlugin.ts | 7 ++- .../src/types/plugins/flagOverridePlugin.ts | 6 +-- packages/toolbar/src/types/plugins/index.ts | 2 - packages/toolbar/src/types/plugins/plugins.ts | 13 +++-- pnpm-lock.yaml | 36 +++++++++++-- 33 files changed, 113 insertions(+), 179 deletions(-) delete mode 100644 packages/toolbar/src/types/compat/Hook.ts delete mode 100644 packages/toolbar/src/types/compat/LDClient.ts delete mode 100644 packages/toolbar/src/types/compat/LDPlugin.ts diff --git a/packages/demo/package.json b/packages/demo/package.json index b5a50200..0d71fe4f 100644 --- a/packages/demo/package.json +++ b/packages/demo/package.json @@ -14,8 +14,8 @@ "preview": "vite preview" }, "dependencies": { - "@launchdarkly/js-client-sdk": "^0.11.0", "@launchdarkly/toolbar": "workspace:*", + "launchdarkly-js-client-sdk": "^3.9.0", "launchdarkly-react-client-sdk": "^3.9.0", "msw": "^2.12.7", "react": "^19.2.4", diff --git a/packages/demo/src/hooks/useLaunchDarklyProvider.ts b/packages/demo/src/hooks/useLaunchDarklyProvider.ts index d5227d8b..f56e7e8e 100644 --- a/packages/demo/src/hooks/useLaunchDarklyProvider.ts +++ b/packages/demo/src/hooks/useLaunchDarklyProvider.ts @@ -1,6 +1,5 @@ import { useState, useEffect, useRef } from 'react'; -import { asyncWithLDProvider } from 'launchdarkly-react-client-sdk'; -import type { LDPlugin } from '@launchdarkly/toolbar'; +import { asyncWithLDProvider, type LDPlugin } from 'launchdarkly-react-client-sdk'; import { DEMO_CONFIG, demoLog } from '../config/demo'; import { startMockWorker, stopMockWorker } from '../mocks'; diff --git a/packages/toolbar/README.md b/packages/toolbar/README.md index edeec1bb..a5361e6c 100644 --- a/packages/toolbar/README.md +++ b/packages/toolbar/README.md @@ -210,7 +210,7 @@ Works with any JavaScript framework or vanilla JS. Add this script to your `inde In your corresponding code, wherever you instantiate your LaunchDarkly JS client, be sure to pass in the following plugins: ```typescript -import * as LDClient from '@launchdarkly/js-client-sdk'; +import * as LDClient from 'launchdarkly-js-client-sdk'; import { FlagOverridePlugin, EventInterceptionPlugin } from '@launchdarkly/toolbar'; const flagOverridePlugin = new FlagOverridePlugin(); @@ -221,7 +221,7 @@ const context: LDClient.LDContext = { key: 'context-key-123abc', }; -const client = LDClient.createClient('client-side-id-123abc', context, { +const client = LDClient.initialize('client-side-id-123abc', context, { plugins: [ // any other plugins you might want flagOverridePlugin, @@ -229,8 +229,6 @@ const client = LDClient.createClient('client-side-id-123abc', context, { ], }); -client.start(); - try { await client.waitForInitialization(5); // initialization succeeded, flag values are now available diff --git a/packages/toolbar/package.json b/packages/toolbar/package.json index 6d17d435..aa24aab5 100644 --- a/packages/toolbar/package.json +++ b/packages/toolbar/package.json @@ -141,7 +141,7 @@ "@launchdarkly/observability": "^1.0.0", "@launchdarkly/session-replay": "^1.0.0", "@launchpad-ui/components": "^0.17.12", - "@launchdarkly/js-client-sdk": "^0.11.0", + "@launchdarkly/js-client-sdk": "^0.12.1", "@launchpad-ui/tokens": "^0.15.1", "@lezer/highlight": "1.2.1", "@react-aria/focus": "^3.21.3", diff --git a/packages/toolbar/src/core/tests/ContextItem.test.tsx b/packages/toolbar/src/core/tests/ContextItem.test.tsx index 694237b1..3ced1fc1 100644 --- a/packages/toolbar/src/core/tests/ContextItem.test.tsx +++ b/packages/toolbar/src/core/tests/ContextItem.test.tsx @@ -1,6 +1,6 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { expect, test, describe, vi, beforeEach } from 'vitest'; -import type { LDContext } from '@launchdarkly/js-client-sdk'; +import type { LDContext } from 'launchdarkly-js-client-sdk'; import { ContextItem } from '../ui/Toolbar/components/new/Contexts/ContextItem'; import { ContextsProvider } from '../ui/Toolbar/context/api/ContextsProvider'; import '@testing-library/jest-dom/vitest'; diff --git a/packages/toolbar/src/core/tests/ContextsProvider.test.tsx b/packages/toolbar/src/core/tests/ContextsProvider.test.tsx index b9d08c04..3a18b5de 100644 --- a/packages/toolbar/src/core/tests/ContextsProvider.test.tsx +++ b/packages/toolbar/src/core/tests/ContextsProvider.test.tsx @@ -1,6 +1,6 @@ import { render, screen, waitFor, act } from '@testing-library/react'; import { expect, test, describe, vi, beforeEach } from 'vitest'; -import type { LDContext } from '@launchdarkly/js-client-sdk'; +import type { LDContext } from 'launchdarkly-js-client-sdk'; import { ContextsProvider, useContextsContext } from '../ui/Toolbar/context/api/ContextsProvider'; import '@testing-library/jest-dom/vitest'; import React from 'react'; diff --git a/packages/toolbar/src/core/tests/InternalClientProvider.test.tsx b/packages/toolbar/src/core/tests/InternalClientProvider.test.tsx index 0fbc2b59..7d97e561 100644 --- a/packages/toolbar/src/core/tests/InternalClientProvider.test.tsx +++ b/packages/toolbar/src/core/tests/InternalClientProvider.test.tsx @@ -3,7 +3,6 @@ import { expect, test, describe, vi, beforeEach, afterEach } from 'vitest'; // Create mocks in hoisted scope const mockLDClient = { - start: vi.fn(), waitForInitialization: vi.fn(), variation: vi.fn(), identify: vi.fn(), @@ -13,9 +12,9 @@ const mockLDClient = { track: vi.fn(), }; -vi.mock('@launchdarkly/js-client-sdk', () => { +vi.mock('launchdarkly-js-client-sdk', () => { return { - createClient: vi.fn(), + initialize: vi.fn(), }; }); @@ -72,7 +71,6 @@ vi.mock('@launchdarkly/session-replay', () => { }; }); -import React from 'react'; import { InternalClientProvider, useInternalClient, @@ -80,11 +78,11 @@ import { } from '../ui/Toolbar/context/telemetry/InternalClientProvider'; import { setToolbarFlagClient } from '../../flags/createToolbarFlagFunction'; import * as toolbarFlagClient from '../../flags/createToolbarFlagFunction'; -import { createClient } from '@launchdarkly/js-client-sdk'; +import { initialize } from 'launchdarkly-js-client-sdk'; import { LDObserve } from '@launchdarkly/observability'; import { LDRecord } from '@launchdarkly/session-replay'; -const mockCreateClient = vi.mocked(createClient); +const mockInitialize = vi.mocked(initialize); const mockLDObserveMethods = vi.mocked(LDObserve); const mockLDRecordMethods = vi.mocked(LDRecord); @@ -116,7 +114,6 @@ describe('InternalClientProvider', () => { beforeEach(() => { vi.clearAllMocks(); - mockLDClient.start.mockResolvedValue(undefined); mockLDClient.waitForInitialization.mockResolvedValue(undefined); mockLDClient.identify.mockResolvedValue(undefined); // Return false for session replay flag by default to prevent it from starting @@ -126,7 +123,7 @@ describe('InternalClientProvider', () => { mockLDClient.off.mockImplementation(() => {}); mockLDClient.track.mockImplementation(() => {}); - mockCreateClient.mockReturnValue(mockLDClient as any); + mockInitialize.mockReturnValue(mockLDClient); mockLDObserveMethods.start.mockClear(); mockLDObserveMethods.stop.mockClear(); @@ -182,7 +179,7 @@ describe('InternalClientProvider', () => { ); await waitFor(() => { - expect(mockCreateClient).toHaveBeenCalledWith( + expect(mockInitialize).toHaveBeenCalledWith( 'test-client-id-123', { kind: 'user', @@ -210,7 +207,7 @@ describe('InternalClientProvider', () => { ); await waitFor(() => { - expect(mockCreateClient).toHaveBeenCalledWith( + expect(mockInitialize).toHaveBeenCalledWith( 'test-client-id-123', customContext, expect.objectContaining({ @@ -270,7 +267,7 @@ describe('InternalClientProvider', () => { ); await waitFor(() => { - expect(mockCreateClient).toHaveBeenCalledWith( + expect(mockInitialize).toHaveBeenCalledWith( 'test-client-id-123', expect.objectContaining({ kind: 'user', @@ -278,7 +275,7 @@ describe('InternalClientProvider', () => { anonymous: true, }), expect.objectContaining({ - baseUri: 'https://app.ld.catamorphic.com', + baseUrl: 'https://app.ld.catamorphic.com', plugins: expect.any(Array), }), ); @@ -311,7 +308,7 @@ describe('InternalClientProvider', () => { ); await waitFor(() => { - expect(mockCreateClient).toHaveBeenCalledWith( + expect(mockInitialize).toHaveBeenCalledWith( 'test-client-id-123', expect.any(Object), expect.objectContaining({ diff --git a/packages/toolbar/src/core/tests/context-utils.test.ts b/packages/toolbar/src/core/tests/context-utils.test.ts index d834565b..4c185ac8 100644 --- a/packages/toolbar/src/core/tests/context-utils.test.ts +++ b/packages/toolbar/src/core/tests/context-utils.test.ts @@ -1,5 +1,5 @@ import { expect, test, describe } from 'vitest'; -import type { LDContext, LDMultiKindContext, LDSingleKindContext } from '@launchdarkly/js-client-sdk'; +import type { LDContext, LDMultiKindContext, LDSingleKindContext } from 'launchdarkly-js-client-sdk'; import { areContextsEqual, getContextKind, diff --git a/packages/toolbar/src/core/tests/createToolbarFlagFunction.test.ts b/packages/toolbar/src/core/tests/createToolbarFlagFunction.test.ts index 7afc4aa5..36d165ad 100644 --- a/packages/toolbar/src/core/tests/createToolbarFlagFunction.test.ts +++ b/packages/toolbar/src/core/tests/createToolbarFlagFunction.test.ts @@ -1,5 +1,5 @@ import { describe, test, expect, vi, beforeEach } from 'vitest'; -import type { LDClient } from '@launchdarkly/js-client-sdk'; +import type { LDClient } from 'launchdarkly-js-client-sdk'; import { createToolbarFlagFunction, setToolbarFlagClient, diff --git a/packages/toolbar/src/core/tests/hooks/AfterEvaluationHook.test.ts b/packages/toolbar/src/core/tests/hooks/AfterEvaluationHook.test.ts index c17dd624..4813d687 100644 --- a/packages/toolbar/src/core/tests/hooks/AfterEvaluationHook.test.ts +++ b/packages/toolbar/src/core/tests/hooks/AfterEvaluationHook.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { AfterEvaluationHook, type AfterEvaluationHookConfig } from '../../../types/hooks/AfterEvaluationHook'; -import type { EvaluationSeriesData, EvaluationSeriesContext, LDEvaluationDetail } from '@launchdarkly/js-client-sdk'; +import type { EvaluationSeriesData, EvaluationSeriesContext, LDEvaluationDetail } from 'launchdarkly-js-sdk-common'; import { EventFilter, ProcessedEvent } from '../../../types'; // Mock console methods to avoid noise in test output diff --git a/packages/toolbar/src/core/tests/hooks/AfterIdentifyHook.test.ts b/packages/toolbar/src/core/tests/hooks/AfterIdentifyHook.test.ts index 730fc881..3268bfec 100644 --- a/packages/toolbar/src/core/tests/hooks/AfterIdentifyHook.test.ts +++ b/packages/toolbar/src/core/tests/hooks/AfterIdentifyHook.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect, beforeEach, vi } from 'vitest'; import { AfterIdentifyHook, type AfterIdentifyHookConfig } from '../../../types/hooks/AfterIdentifyHook'; -import type { IdentifySeriesData, IdentifySeriesContext, IdentifySeriesResult } from '@launchdarkly/js-client-sdk'; +import type { IdentifySeriesData, IdentifySeriesContext, IdentifySeriesResult } from 'launchdarkly-js-sdk-common'; import { EventFilter, ProcessedEvent } from '../../../types'; // Mock console methods to avoid noise in test output @@ -106,7 +106,7 @@ describe('AfterIdentifyHook', () => { it('should handle contexts without key', () => { const contextWithoutKey = { - context: { kind: 'user', name: 'No Key User', key: '' }, + context: { kind: 'user', name: 'No Key User' }, }; hook.afterIdentify(contextWithoutKey, mockData, mockResult); diff --git a/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/AddContextForm.tsx b/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/AddContextForm.tsx index 33b85ddf..8733df75 100644 --- a/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/AddContextForm.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/AddContextForm.tsx @@ -1,6 +1,6 @@ import { useState, useCallback, useMemo } from 'react'; import { motion, AnimatePresence } from 'motion/react'; -import type { LDContext } from '@launchdarkly/js-client-sdk'; +import type { LDContext } from 'launchdarkly-js-client-sdk'; import { useContextsContext } from '../../../context/api/ContextsProvider'; import { CancelIcon } from '../../icons'; import { EASING } from '../../../constants'; diff --git a/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/ContextItem.tsx b/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/ContextItem.tsx index 2ccd9640..719c7ca2 100644 --- a/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/ContextItem.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/ContextItem.tsx @@ -1,6 +1,6 @@ import { useState, useCallback, useMemo, useEffect, useRef, memo } from 'react'; import { motion, AnimatePresence } from 'motion/react'; -import type { LDContext } from '@launchdarkly/js-client-sdk'; +import type { LDContext } from 'launchdarkly-js-client-sdk'; import * as styles from './ContextItem.module.css'; import { CopyableText } from '../../CopyableText'; import { EditIcon, DeleteIcon, CheckIcon, CancelIcon } from '../../icons'; diff --git a/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx b/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx index 5a6fb2ff..9d0153a0 100644 --- a/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx @@ -107,7 +107,6 @@ export function FlagSdkOverrideProvider({ children, flagOverridePlugin }: FlagSd setIsLoading(false); // Subscribe to changes with incremental updates - // NOTE: we are overloading this function so that it can handle both the old and new browser SDKs const handleChange = (changes: Record, keys: string[]) => { setFlags((prevFlags) => { const updatedRawFlags = ldClient.allFlags(); @@ -115,7 +114,7 @@ export function FlagSdkOverrideProvider({ children, flagOverridePlugin }: FlagSd let changedKeys = keys; if (changedKeys === undefined) { - changedKeys = Object.keys(changes); + changedKeys = Object.keys(changes) } // Only update the flags that actually changed for better performance diff --git a/packages/toolbar/src/core/ui/Toolbar/context/api/ContextsProvider.tsx b/packages/toolbar/src/core/ui/Toolbar/context/api/ContextsProvider.tsx index 5d95b1fa..dc935b8f 100644 --- a/packages/toolbar/src/core/ui/Toolbar/context/api/ContextsProvider.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/context/api/ContextsProvider.tsx @@ -1,5 +1,5 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; -import type { LDContext } from '@launchdarkly/js-client-sdk'; +import type { LDContext } from 'launchdarkly-js-client-sdk'; import { loadContexts, saveContexts, loadActiveContext, saveActiveContext } from '../../utils/localStorage'; import { usePlugins } from '../state/PluginsProvider'; import { getContextDisplayName, getContextKey, getContextKind, getStableContextId } from '../../utils/context'; @@ -238,7 +238,7 @@ export const ContextsProvider = ({ children }: { children: React.ReactNode }) => // and setContext would skip the identify call due to equality check isSettingContextRef.current = true; ldClient - .identify(savedActiveContext)! + .identify(savedActiveContext) .then(() => { // Ensure state is in sync (it should already be from useState initialization) setActiveContextId(savedStableId); diff --git a/packages/toolbar/src/core/ui/Toolbar/context/telemetry/InternalClientProvider.tsx b/packages/toolbar/src/core/ui/Toolbar/context/telemetry/InternalClientProvider.tsx index d26e020c..370de4f9 100644 --- a/packages/toolbar/src/core/ui/Toolbar/context/telemetry/InternalClientProvider.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/context/telemetry/InternalClientProvider.tsx @@ -1,6 +1,6 @@ import { createContext, useCallback, useContext, useEffect, useState, type ReactNode } from 'react'; -import { createClient } from '@launchdarkly/js-client-sdk'; -import type { LDClient, LDContext } from '@launchdarkly/js-client-sdk'; +import { initialize } from 'launchdarkly-js-client-sdk'; +import type { LDClient, LDContext } from 'launchdarkly-js-client-sdk'; import Observability from '@launchdarkly/observability'; import SessionReplay from '@launchdarkly/session-replay'; import { setToolbarFlagClient } from '../../../../../flags'; @@ -143,17 +143,16 @@ export function InternalClientProvider({ }); const options = { - ...(baseUrl && { baseUri: baseUrl }), - ...(streamUrl && { streamUri: streamUrl }), - ...(eventsUrl && { eventsUri: eventsUrl }), + ...(baseUrl && { baseUrl }), + ...(streamUrl && { streamUrl }), + ...(eventsUrl && { eventsUrl }), plugins: [observabilityPlugin, sessionReplayPlugin], }; - const ldClient = createClient(clientSideId, context, options); - ldClient.start(); + const ldClient = initialize(clientSideId, context, options); clientToCleanup = ldClient; - await ldClient.waitForInitialization({ timeout: 5 }); + await ldClient.waitForInitialization(5); if (mounted) { setClient(ldClient); @@ -192,20 +191,28 @@ export function InternalClientProvider({ // Wrap identify in a Promise that resolves when the onDone callback fires // This ensures flags have been re-evaluated before we return - return client - .identify({ - kind: 'multi', - account: { - key: accountId, + return new Promise((resolve) => { + client.identify( + { + kind: 'multi', + account: { + key: accountId, + }, + user: { + key: memberId, + }, }, - user: { - key: memberId, + undefined, + (err) => { + if (err) { + console.error('[InternalClientProvider] Failed to update context:', err); + resolve(); + } else { + resolve(); + } }, - }) - .then(() => {}) - .catch((err) => { - console.error('[InternalClientProvider] Failed to update context:', err); - }); + ); + }); }, [client], ); diff --git a/packages/toolbar/src/core/ui/Toolbar/utils/context.ts b/packages/toolbar/src/core/ui/Toolbar/utils/context.ts index 379a188d..db1214b5 100644 --- a/packages/toolbar/src/core/ui/Toolbar/utils/context.ts +++ b/packages/toolbar/src/core/ui/Toolbar/utils/context.ts @@ -1,4 +1,4 @@ -import type { LDContext, LDMultiKindContext, LDSingleKindContext } from '@launchdarkly/js-client-sdk'; +import type { LDContext, LDMultiKindContext, LDSingleKindContext } from 'launchdarkly-js-client-sdk'; /** * Check if two contexts are the same using their stable IDs (kind+key) diff --git a/packages/toolbar/src/core/ui/Toolbar/utils/localStorage.ts b/packages/toolbar/src/core/ui/Toolbar/utils/localStorage.ts index cb89eccf..4fd9b24b 100644 --- a/packages/toolbar/src/core/ui/Toolbar/utils/localStorage.ts +++ b/packages/toolbar/src/core/ui/Toolbar/utils/localStorage.ts @@ -1,4 +1,4 @@ -import type { LDContext } from '@launchdarkly/js-client-sdk'; +import type { LDContext } from 'launchdarkly-js-client-sdk'; import { ToolbarPosition, TOOLBAR_POSITIONS } from '../types/toolbar'; export const TOOLBAR_STORAGE_KEYS = { diff --git a/packages/toolbar/src/core/utils/analytics.ts b/packages/toolbar/src/core/utils/analytics.ts index 600d9029..ca724fe1 100644 --- a/packages/toolbar/src/core/utils/analytics.ts +++ b/packages/toolbar/src/core/utils/analytics.ts @@ -1,4 +1,4 @@ -import type { LDClient } from '@launchdarkly/js-client-sdk'; +import type { LDClient } from 'launchdarkly-js-client-sdk'; import type { FeedbackSentiment } from '../../types/analytics'; import { isDoNotTrackEnabled } from './browser'; import { sendFeedback } from './feedback'; diff --git a/packages/toolbar/src/core/utils/urlOverrides.ts b/packages/toolbar/src/core/utils/urlOverrides.ts index 647a5663..07134aae 100644 --- a/packages/toolbar/src/core/utils/urlOverrides.ts +++ b/packages/toolbar/src/core/utils/urlOverrides.ts @@ -5,7 +5,7 @@ * The state includes: flag overrides, contexts, settings, starred flags, etc. */ -import { LDContext } from '@launchdarkly/js-client-sdk'; +import { LDContext } from 'launchdarkly-js-client-sdk'; import { ToolbarSettings } from '../ui/Toolbar/utils/localStorage'; /** Current version of the shared state format */ diff --git a/packages/toolbar/src/flags/createToolbarFlagFunction.ts b/packages/toolbar/src/flags/createToolbarFlagFunction.ts index 2aab794d..2deac391 100644 --- a/packages/toolbar/src/flags/createToolbarFlagFunction.ts +++ b/packages/toolbar/src/flags/createToolbarFlagFunction.ts @@ -1,4 +1,4 @@ -import type { LDClient } from '@launchdarkly/js-client-sdk'; +import type { LDClient } from 'launchdarkly-js-client-sdk'; /** * Singleton storage for the toolbar's internal LaunchDarkly client. diff --git a/packages/toolbar/src/types/compat/Hook.ts b/packages/toolbar/src/types/compat/Hook.ts deleted file mode 100644 index bd8be250..00000000 --- a/packages/toolbar/src/types/compat/Hook.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { - EvaluationSeriesContext, - EvaluationSeriesData, - LDEvaluationReason, - LDFlagValue, - Hook as LDHook, -} from '@launchdarkly/js-client-sdk'; - -/** - * Interface for extending SDK functionality via hooks that is compatible with both javascript client versions (<=3.x and >=4.x). - */ -export type Hook = Omit & { - /** - * This method is called during the execution of the variation method - * after the flag value has been determined. The method is executed synchronously. - * - * @param hookContext Contains read-only information about the evaluation - * being performed. - * @param data A record associated with each stage of hook invocations. Each - * stage is called with the data of the previous stage for a series. - * @param detail The result of the evaluation. This value should not be - * modified. - * @returns Data to use when executing the next state of the hook in the evaluation series. It is - * recommended to expand the previous input into the return. This helps ensure your stage remains - * compatible moving forward as more stages are added. - * ```js - * return {...data, "my-new-field": /*my data/*} - * ``` - */ - afterEvaluation?( - hookContext: EvaluationSeriesContext, - data: EvaluationSeriesData, - detail: { - /** - * The result of the flag evaluation. This will be either one of the flag's variations or - * the default value that was passed to `LDClient.variationDetail`. - */ - value: LDFlagValue; - /** - * The index of the returned value within the flag's list of variations, e.g. 0 for the - * first variation-- or `null` if the default value was returned. - */ - variationIndex?: number | null; - /** - * An object describing the main factor that influenced the flag evaluation value. - */ - reason?: LDEvaluationReason | null; - }, - ): EvaluationSeriesData; -}; diff --git a/packages/toolbar/src/types/compat/LDClient.ts b/packages/toolbar/src/types/compat/LDClient.ts deleted file mode 100644 index 8d60a2b8..00000000 --- a/packages/toolbar/src/types/compat/LDClient.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { LDFlagSet, LDIdentifyResult } from '@launchdarkly/js-client-sdk'; -import type { Hook } from './Hook'; - -/** - * Interface for the LaunchDarkly client that is compatible with both javascript client versions (<=3.x and >=4.x). - */ -export interface LDClient { - track(key: string, data?: any, metricValue?: number): void; - identify(ctx: any): Promise | Promise | Promise; - addHook(hook: Hook): void; - allFlags(): LDFlagSet; - getContext(): any; - on(key: string, callback: (...args: any[]) => void): void; - off(key: string, callback: (...args: any[]) => void): void; -} diff --git a/packages/toolbar/src/types/compat/LDPlugin.ts b/packages/toolbar/src/types/compat/LDPlugin.ts deleted file mode 100644 index e5c6db0b..00000000 --- a/packages/toolbar/src/types/compat/LDPlugin.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { LDPluginEnvironmentMetadata, LDPluginMetadata } from '@launchdarkly/js-client-sdk'; -import { LDClient } from './LDClient'; -import type { Hook } from './Hook'; - -/** - * Interface for LaunchDarkly plugins that are compatible with both javascript client versions (<=3.x and >=4.x). - */ -export interface LDPlugin { - /** - * Returns the metadata for the plugin. - */ - getMetadata(): LDPluginMetadata; - - /** - * Registers the plugin with the LaunchDarkly client. - * @param client - The LaunchDarkly client. - * @param environmentMetadata - The environment metadata. - */ - register(client: LDClient, environmentMetadata?: LDPluginEnvironmentMetadata): void; - - /** - * Returns the hooks for the plugin. - * @param metadata - The environment metadata. - * @returns The hooks for the plugin. - */ - getHooks?(metadata: LDPluginEnvironmentMetadata): Hook[]; -} diff --git a/packages/toolbar/src/types/hooks/AfterEvaluationHook.ts b/packages/toolbar/src/types/hooks/AfterEvaluationHook.ts index e2da3974..50efefd1 100644 --- a/packages/toolbar/src/types/hooks/AfterEvaluationHook.ts +++ b/packages/toolbar/src/types/hooks/AfterEvaluationHook.ts @@ -1,10 +1,10 @@ +import type { Hook } from 'launchdarkly-js-client-sdk'; import type { - Hook, HookMetadata, EvaluationSeriesData, LDEvaluationDetail, EvaluationSeriesContext, -} from '@launchdarkly/js-client-sdk'; +} from 'launchdarkly-js-sdk-common'; import type { EventFilter, ProcessedEvent, SyntheticEventContext } from '../events'; export type AfterEvaluationHookConfig = { @@ -42,7 +42,7 @@ export class AfterEvaluationHook implements Hook { value: detail.value, variation: detail.variationIndex, default: hookContext.defaultValue, - reason: detail.reason ?? undefined, + reason: detail.reason, creationDate: Date.now(), // Note: We don't have access to version, trackEvents, or debugEventsUntilDate // from the afterEvaluation hook, so these will be undefined diff --git a/packages/toolbar/src/types/hooks/AfterIdentifyHook.ts b/packages/toolbar/src/types/hooks/AfterIdentifyHook.ts index accfaf34..7c47f3eb 100644 --- a/packages/toolbar/src/types/hooks/AfterIdentifyHook.ts +++ b/packages/toolbar/src/types/hooks/AfterIdentifyHook.ts @@ -1,12 +1,11 @@ +import type { Hook } from 'launchdarkly-js-client-sdk'; import type { - Hook, HookMetadata, IdentifySeriesContext, IdentifySeriesData, IdentifySeriesResult, LDContext, -} from '@launchdarkly/js-client-sdk'; - +} from 'launchdarkly-js-sdk-common'; import type { EventFilter, ProcessedEvent, SyntheticEventContext } from '../events'; export type AfterIdentifyHookConfig = { diff --git a/packages/toolbar/src/types/hooks/AfterTrackHook.ts b/packages/toolbar/src/types/hooks/AfterTrackHook.ts index 7617a5e4..bfd867d0 100644 --- a/packages/toolbar/src/types/hooks/AfterTrackHook.ts +++ b/packages/toolbar/src/types/hooks/AfterTrackHook.ts @@ -1,4 +1,5 @@ -import type { Hook, HookMetadata, TrackSeriesContext } from '@launchdarkly/js-client-sdk'; +import type { Hook } from 'launchdarkly-js-client-sdk'; +import type { HookMetadata, TrackSeriesContext } from 'launchdarkly-js-sdk-common'; import type { EventFilter, ProcessedEvent, SyntheticEventContext } from '../events'; export type AfterTrackHookConfig = { diff --git a/packages/toolbar/src/types/plugins/LDClient.ts b/packages/toolbar/src/types/plugins/LDClient.ts index fc5c122d..60c312bb 100644 --- a/packages/toolbar/src/types/plugins/LDClient.ts +++ b/packages/toolbar/src/types/plugins/LDClient.ts @@ -1,5 +1,5 @@ -import { LDFlagSet, Hook, LDPluginEnvironmentMetadata } from "launchdarkly-js-client-sdk"; -import { LDIdentifyResult } from "@launchdarkly/js-client-sdk"; +import { LDFlagSet, LDPluginEnvironmentMetadata } from "launchdarkly-js-client-sdk"; +import { LDIdentifyResult, Hook } from "@launchdarkly/js-client-sdk"; /** * LDClient based on the LDClient type in the SDK package with diff --git a/packages/toolbar/src/types/plugins/eventInterceptionPlugin.ts b/packages/toolbar/src/types/plugins/eventInterceptionPlugin.ts index 3325e189..1fc2a91d 100644 --- a/packages/toolbar/src/types/plugins/eventInterceptionPlugin.ts +++ b/packages/toolbar/src/types/plugins/eventInterceptionPlugin.ts @@ -1,10 +1,9 @@ -import type { LDPluginEnvironmentMetadata, LDPluginMetadata } from '@launchdarkly/js-client-sdk'; +import type { LDPluginEnvironmentMetadata, LDPluginMetadata, Hook } from 'launchdarkly-js-client-sdk'; import { AfterTrackHook, AfterIdentifyHook, AfterEvaluationHook, EventStore } from '../hooks'; -import type { LDClient } from '../compat/LDClient'; -import type { Hook } from '../compat/Hook'; import type { EventFilter, ProcessedEvent } from '../events'; import type { IEventInterceptionPlugin } from './plugins'; import { ANALYTICS_EVENT_PREFIX } from '../analytics'; +import type { LDClient } from './LDClient'; /** * Configuration options for the EventInterceptionPlugin @@ -86,7 +85,7 @@ export class EventInterceptionPlugin implements IEventInterceptionPlugin { return [this.afterTrackHook, this.afterIdentifyHook, this.afterEvaluationHook]; } - register(ldClient: LDClient, _metadata: LDPluginEnvironmentMetadata): void { + register(ldClient: LDClient): void { this.ldClient = ldClient; } diff --git a/packages/toolbar/src/types/plugins/flagOverridePlugin.ts b/packages/toolbar/src/types/plugins/flagOverridePlugin.ts index 15cdff3f..4c65c9c1 100644 --- a/packages/toolbar/src/types/plugins/flagOverridePlugin.ts +++ b/packages/toolbar/src/types/plugins/flagOverridePlugin.ts @@ -4,9 +4,9 @@ import type { LDFlagSet, Hook, LDPluginEnvironmentMetadata, -} from '@launchdarkly/js-client-sdk'; +} from 'launchdarkly-js-client-sdk'; import type { IFlagOverridePlugin } from './plugins'; -import { LDClient } from '../compat/LDClient'; +import type { LDClient } from './LDClient'; /** * Configuration options for the FlagOverridePlugin @@ -48,7 +48,7 @@ export class FlagOverridePlugin implements IFlagOverridePlugin { /** * Called when the plugin is registered with the LaunchDarkly client */ - register(ldClient: LDClient, _metadata: LDPluginEnvironmentMetadata): void { + register(ldClient: LDClient): void { this.ldClient = ldClient; } diff --git a/packages/toolbar/src/types/plugins/index.ts b/packages/toolbar/src/types/plugins/index.ts index 7fb86283..3562bf0a 100644 --- a/packages/toolbar/src/types/plugins/index.ts +++ b/packages/toolbar/src/types/plugins/index.ts @@ -1,5 +1,3 @@ export * from './eventInterceptionPlugin'; export * from './flagOverridePlugin'; export * from './plugins'; -export * from '../compat/LDPlugin'; -export * from '../compat/LDClient'; diff --git a/packages/toolbar/src/types/plugins/plugins.ts b/packages/toolbar/src/types/plugins/plugins.ts index d6bf8616..73c9c037 100644 --- a/packages/toolbar/src/types/plugins/plugins.ts +++ b/packages/toolbar/src/types/plugins/plugins.ts @@ -1,9 +1,11 @@ -import type { LDFlagSet, LDFlagValue, LDDebugOverride } from '@launchdarkly/js-client-sdk'; -import type { LDPlugin } from '../compat/LDPlugin'; -import type { LDClient } from '../compat/LDClient'; +import type { LDDebugOverride, LDFlagSet, LDFlagValue, LDPlugin, LDPluginEnvironmentMetadata } from 'launchdarkly-js-client-sdk'; +import type { LDClient } from './LDClient'; import type { ProcessedEvent } from '../events'; -export interface IFlagOverridePlugin extends LDPlugin, LDDebugOverride { +export interface IFlagOverridePlugin extends Omit, LDDebugOverride { + + register(client: LDClient, metadata: LDPluginEnvironmentMetadata): void; + /** * Sets an override value for a feature flag * @param flagKey - The key of the flag to override @@ -38,7 +40,8 @@ export interface IFlagOverridePlugin extends LDPlugin, LDDebugOverride { /** * Interface for event interception plugins that can be used with the LaunchDarkly Toolbar */ -export interface IEventInterceptionPlugin extends LDPlugin { +export interface IEventInterceptionPlugin extends Omit { + register(client: LDClient, metadata: LDPluginEnvironmentMetadata): void; /** * Gets all intercepted events from the event store * @returns Array of processed events diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index afe1ebdf..3a6fb4b7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -53,12 +53,12 @@ importers: packages/demo: dependencies: - '@launchdarkly/js-client-sdk': - specifier: ^0.11.0 - version: 0.11.0 '@launchdarkly/toolbar': specifier: workspace:* version: link:../toolbar + launchdarkly-js-client-sdk: + specifier: ^3.9.0 + version: 3.9.0 launchdarkly-react-client-sdk: specifier: ^3.9.0 version: 3.9.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -151,8 +151,8 @@ importers: specifier: ^6.39.11 version: 6.39.11 '@launchdarkly/js-client-sdk': - specifier: ^0.11.0 - version: 0.11.0 + specifier: ^0.12.1 + version: 0.12.1 '@launchdarkly/observability': specifier: ^1.0.0 version: 1.0.0 @@ -1189,9 +1189,21 @@ packages: '@launchdarkly/js-client-sdk-common@1.16.0': resolution: {integrity: sha512-W6yVVlIV/BqCLR1je3uReVfJcTNK9mznp1PWpEEBRRrapjMnLYUcCJat6yj3dyyn3VKyawXy1MkSn34gEEicjg==} + '@launchdarkly/js-client-sdk-common@1.17.1': + resolution: {integrity: sha512-ZwzMKwP3qfhpDzkjQG76PITYsrvQUT+YfqFv4MHjoD96PNRcYRhzULhDewC51OBKajWjFMYmi5MvAXZtIMDWAQ==} + '@launchdarkly/js-client-sdk@0.11.0': resolution: {integrity: sha512-PRmSsPsOi3NXjXpG4D4MoyImBibNoh9wOoWFbIMXHA26h5cMNBQjTFI6gqe8kyf2rDwqHerhrj5f734Zqwl33Q==} + '@launchdarkly/js-client-sdk@0.12.1': + resolution: {integrity: sha512-blzKe93qWFN3CoLYP5BjPE62CM1qfYQDTUEMV+RoMm4waQb80ALFNfXp4QNmOpxDAasUtVPrCn08WpRmBxdSYw==} + + '@launchdarkly/js-client-sdk@0.6.0': + resolution: {integrity: sha512-9aBgpGKXxaiPudNuQ9MoS8wFSIR1h0P2EFDP2veNJG7tBuxzDKfH3hzjW01C4/WyWN6bk7P7XKwisxw775klBQ==} + + '@launchdarkly/js-sdk-common@2.17.0': + resolution: {integrity: sha512-HYQNc4xbE58hBOO1yZsqE+BHOC36AYrHPZ5hwvlHoZRSsaoYCPM+Q/n+O+N3JhWXTqGsu6SwUzqUzTyxg8prGg==} + '@launchdarkly/js-sdk-common@2.20.0': resolution: {integrity: sha512-g1Lyi5xL7AXAP6BP8BzRcqVqIhqOSVpA5Bx8Vvj8A/4A6sIHVz2vIZluykD/bJiKg1+G9ojm+OCfdL/c0ebi0A==} @@ -6020,10 +6032,24 @@ snapshots: dependencies: '@launchdarkly/js-sdk-common': 2.20.0 + '@launchdarkly/js-client-sdk-common@1.17.1': + dependencies: + '@launchdarkly/js-sdk-common': 2.20.0 + '@launchdarkly/js-client-sdk@0.11.0': dependencies: '@launchdarkly/js-client-sdk-common': 1.16.0 + '@launchdarkly/js-client-sdk@0.12.1': + dependencies: + '@launchdarkly/js-client-sdk-common': 1.17.1 + + '@launchdarkly/js-client-sdk@0.6.0': + dependencies: + '@launchdarkly/js-client-sdk-common': 1.13.0 + + '@launchdarkly/js-sdk-common@2.17.0': {} + '@launchdarkly/js-sdk-common@2.20.0': {} '@launchdarkly/observability@1.0.0': From 99ee833a628f9819e446b29738d6f496ac5e913d Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Fri, 16 Jan 2026 10:42:04 -0600 Subject: [PATCH 07/12] test: adding tests for handling v4 change payload --- .../tests/FlagSdkOverrideProvider.test.tsx | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/toolbar/src/core/tests/FlagSdkOverrideProvider.test.tsx b/packages/toolbar/src/core/tests/FlagSdkOverrideProvider.test.tsx index ef9a21dc..39ab7e95 100644 --- a/packages/toolbar/src/core/tests/FlagSdkOverrideProvider.test.tsx +++ b/packages/toolbar/src/core/tests/FlagSdkOverrideProvider.test.tsx @@ -181,6 +181,41 @@ describe('FlagSdkOverrideProvider', () => { expect(screen.getByTestId('flag-dynamic-flag')).toHaveTextContent('Dynamic Flag: updated (original)'); }); + test('handles LaunchDarkly client change events from client v4', async () => { + mockLdClient.allFlags.mockReturnValue({ + 'dynamic-flag': 'initial', + }); + + let changeHandler: (context: object, changedKeys: string[]) => void; + mockLdClient.on.mockImplementation((event: string, handler: any) => { + if (event === 'change') { + changeHandler = handler; + } + }); + + render( + + + , + ); + + await act(async () => { + await new Promise((resolve) => setTimeout(resolve, 0)); + }); + + expect(screen.getByTestId('flag-dynamic-flag')).toHaveTextContent('Dynamic Flag: initial (original)'); + + mockLdClient.allFlags.mockReturnValue({ + 'dynamic-flag': 'updated', + }); + + await act(async () => { + changeHandler!({}, ['dynamic-flag']); + }); + + expect(screen.getByTestId('flag-dynamic-flag')).toHaveTextContent('Dynamic Flag: updated (original)'); + }); + test('handles null LaunchDarkly client gracefully', async () => { // GIVEN: Plugin returns null client (edge case) (mockFlagOverridePlugin.getClient as any).mockReturnValue(null); From 306b44335f33537781c9a65f2e3b338ff14cb7d2 Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Wed, 21 Jan 2026 15:10:00 -0600 Subject: [PATCH 08/12] chore: update browser sdk again --- packages/toolbar/package.json | 2 +- .../components/new/Contexts/AddContextForm.tsx | 2 +- .../components/new/Contexts/ContextItem.tsx | 2 +- .../context/FlagSdkOverrideProvider.tsx | 2 +- .../Toolbar/context/api/ContextsProvider.tsx | 2 +- .../src/core/ui/Toolbar/utils/localStorage.ts | 2 +- packages/toolbar/src/types/plugins/LDClient.ts | 4 ++-- .../types/plugins/eventInterceptionPlugin.ts | 2 +- .../src/types/plugins/flagOverridePlugin.ts | 2 +- packages/toolbar/src/types/plugins/plugins.ts | 9 +++++++-- pnpm-lock.yaml | 18 +++++++++--------- 11 files changed, 26 insertions(+), 21 deletions(-) diff --git a/packages/toolbar/package.json b/packages/toolbar/package.json index aa24aab5..36942688 100644 --- a/packages/toolbar/package.json +++ b/packages/toolbar/package.json @@ -141,7 +141,7 @@ "@launchdarkly/observability": "^1.0.0", "@launchdarkly/session-replay": "^1.0.0", "@launchpad-ui/components": "^0.17.12", - "@launchdarkly/js-client-sdk": "^0.12.1", + "@launchdarkly/js-client-sdk": "^0.13.0", "@launchpad-ui/tokens": "^0.15.1", "@lezer/highlight": "1.2.1", "@react-aria/focus": "^3.21.3", diff --git a/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/AddContextForm.tsx b/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/AddContextForm.tsx index 8733df75..33b85ddf 100644 --- a/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/AddContextForm.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/AddContextForm.tsx @@ -1,6 +1,6 @@ import { useState, useCallback, useMemo } from 'react'; import { motion, AnimatePresence } from 'motion/react'; -import type { LDContext } from 'launchdarkly-js-client-sdk'; +import type { LDContext } from '@launchdarkly/js-client-sdk'; import { useContextsContext } from '../../../context/api/ContextsProvider'; import { CancelIcon } from '../../icons'; import { EASING } from '../../../constants'; diff --git a/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/ContextItem.tsx b/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/ContextItem.tsx index 719c7ca2..2ccd9640 100644 --- a/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/ContextItem.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/components/new/Contexts/ContextItem.tsx @@ -1,6 +1,6 @@ import { useState, useCallback, useMemo, useEffect, useRef, memo } from 'react'; import { motion, AnimatePresence } from 'motion/react'; -import type { LDContext } from 'launchdarkly-js-client-sdk'; +import type { LDContext } from '@launchdarkly/js-client-sdk'; import * as styles from './ContextItem.module.css'; import { CopyableText } from '../../CopyableText'; import { EditIcon, DeleteIcon, CheckIcon, CancelIcon } from '../../icons'; diff --git a/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx b/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx index 9d0153a0..edc78832 100644 --- a/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx @@ -114,7 +114,7 @@ export function FlagSdkOverrideProvider({ children, flagOverridePlugin }: FlagSd let changedKeys = keys; if (changedKeys === undefined) { - changedKeys = Object.keys(changes) + changedKeys = Object.keys(changes); } // Only update the flags that actually changed for better performance diff --git a/packages/toolbar/src/core/ui/Toolbar/context/api/ContextsProvider.tsx b/packages/toolbar/src/core/ui/Toolbar/context/api/ContextsProvider.tsx index dc935b8f..c1e84659 100644 --- a/packages/toolbar/src/core/ui/Toolbar/context/api/ContextsProvider.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/context/api/ContextsProvider.tsx @@ -1,5 +1,5 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'; -import type { LDContext } from 'launchdarkly-js-client-sdk'; +import type { LDContext } from '@launchdarkly/js-client-sdk'; import { loadContexts, saveContexts, loadActiveContext, saveActiveContext } from '../../utils/localStorage'; import { usePlugins } from '../state/PluginsProvider'; import { getContextDisplayName, getContextKey, getContextKind, getStableContextId } from '../../utils/context'; diff --git a/packages/toolbar/src/core/ui/Toolbar/utils/localStorage.ts b/packages/toolbar/src/core/ui/Toolbar/utils/localStorage.ts index 4fd9b24b..cb89eccf 100644 --- a/packages/toolbar/src/core/ui/Toolbar/utils/localStorage.ts +++ b/packages/toolbar/src/core/ui/Toolbar/utils/localStorage.ts @@ -1,4 +1,4 @@ -import type { LDContext } from 'launchdarkly-js-client-sdk'; +import type { LDContext } from '@launchdarkly/js-client-sdk'; import { ToolbarPosition, TOOLBAR_POSITIONS } from '../types/toolbar'; export const TOOLBAR_STORAGE_KEYS = { diff --git a/packages/toolbar/src/types/plugins/LDClient.ts b/packages/toolbar/src/types/plugins/LDClient.ts index 60c312bb..8580a662 100644 --- a/packages/toolbar/src/types/plugins/LDClient.ts +++ b/packages/toolbar/src/types/plugins/LDClient.ts @@ -1,5 +1,5 @@ -import { LDFlagSet, LDPluginEnvironmentMetadata } from "launchdarkly-js-client-sdk"; -import { LDIdentifyResult, Hook } from "@launchdarkly/js-client-sdk"; +import { LDFlagSet, LDPluginEnvironmentMetadata } from 'launchdarkly-js-client-sdk'; +import { LDIdentifyResult, Hook } from '@launchdarkly/js-client-sdk'; /** * LDClient based on the LDClient type in the SDK package with diff --git a/packages/toolbar/src/types/plugins/eventInterceptionPlugin.ts b/packages/toolbar/src/types/plugins/eventInterceptionPlugin.ts index 1fc2a91d..6a5fe929 100644 --- a/packages/toolbar/src/types/plugins/eventInterceptionPlugin.ts +++ b/packages/toolbar/src/types/plugins/eventInterceptionPlugin.ts @@ -1,4 +1,4 @@ -import type { LDPluginEnvironmentMetadata, LDPluginMetadata, Hook } from 'launchdarkly-js-client-sdk'; +import type { LDPluginEnvironmentMetadata, LDPluginMetadata, Hook } from '@launchdarkly/js-client-sdk'; import { AfterTrackHook, AfterIdentifyHook, AfterEvaluationHook, EventStore } from '../hooks'; import type { EventFilter, ProcessedEvent } from '../events'; import type { IEventInterceptionPlugin } from './plugins'; diff --git a/packages/toolbar/src/types/plugins/flagOverridePlugin.ts b/packages/toolbar/src/types/plugins/flagOverridePlugin.ts index 4c65c9c1..5fe5ee3b 100644 --- a/packages/toolbar/src/types/plugins/flagOverridePlugin.ts +++ b/packages/toolbar/src/types/plugins/flagOverridePlugin.ts @@ -4,7 +4,7 @@ import type { LDFlagSet, Hook, LDPluginEnvironmentMetadata, -} from 'launchdarkly-js-client-sdk'; +} from '@launchdarkly/js-client-sdk'; import type { IFlagOverridePlugin } from './plugins'; import type { LDClient } from './LDClient'; diff --git a/packages/toolbar/src/types/plugins/plugins.ts b/packages/toolbar/src/types/plugins/plugins.ts index 73c9c037..1460ce2d 100644 --- a/packages/toolbar/src/types/plugins/plugins.ts +++ b/packages/toolbar/src/types/plugins/plugins.ts @@ -1,9 +1,14 @@ -import type { LDDebugOverride, LDFlagSet, LDFlagValue, LDPlugin, LDPluginEnvironmentMetadata } from 'launchdarkly-js-client-sdk'; +import type { + LDDebugOverride, + LDFlagSet, + LDFlagValue, + LDPlugin, + LDPluginEnvironmentMetadata, +} from '@launchdarkly/js-client-sdk'; import type { LDClient } from './LDClient'; import type { ProcessedEvent } from '../events'; export interface IFlagOverridePlugin extends Omit, LDDebugOverride { - register(client: LDClient, metadata: LDPluginEnvironmentMetadata): void; /** diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3a6fb4b7..7fa75917 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -151,8 +151,8 @@ importers: specifier: ^6.39.11 version: 6.39.11 '@launchdarkly/js-client-sdk': - specifier: ^0.12.1 - version: 0.12.1 + specifier: ^0.13.0 + version: 0.13.0 '@launchdarkly/observability': specifier: ^1.0.0 version: 1.0.0 @@ -1189,14 +1189,14 @@ packages: '@launchdarkly/js-client-sdk-common@1.16.0': resolution: {integrity: sha512-W6yVVlIV/BqCLR1je3uReVfJcTNK9mznp1PWpEEBRRrapjMnLYUcCJat6yj3dyyn3VKyawXy1MkSn34gEEicjg==} - '@launchdarkly/js-client-sdk-common@1.17.1': - resolution: {integrity: sha512-ZwzMKwP3qfhpDzkjQG76PITYsrvQUT+YfqFv4MHjoD96PNRcYRhzULhDewC51OBKajWjFMYmi5MvAXZtIMDWAQ==} + '@launchdarkly/js-client-sdk-common@1.17.2': + resolution: {integrity: sha512-MV4I8h2a/C6UKxsoPMLQW0ileb/qj30gfCyQl2KsM2ZJGP3hwN2mkKUUwytlL+RURpN+0XGpuQ6UIQFfePUTgQ==} '@launchdarkly/js-client-sdk@0.11.0': resolution: {integrity: sha512-PRmSsPsOi3NXjXpG4D4MoyImBibNoh9wOoWFbIMXHA26h5cMNBQjTFI6gqe8kyf2rDwqHerhrj5f734Zqwl33Q==} - '@launchdarkly/js-client-sdk@0.12.1': - resolution: {integrity: sha512-blzKe93qWFN3CoLYP5BjPE62CM1qfYQDTUEMV+RoMm4waQb80ALFNfXp4QNmOpxDAasUtVPrCn08WpRmBxdSYw==} + '@launchdarkly/js-client-sdk@0.13.0': + resolution: {integrity: sha512-x0BcTUxqaul9f1iAlz1/S9Ckp7M2x9CgCgVVFDhQidiapxLQ5K8jtSYcseLFovjPd5hJ1ZvpawN5D3vftmkA3A==} '@launchdarkly/js-client-sdk@0.6.0': resolution: {integrity: sha512-9aBgpGKXxaiPudNuQ9MoS8wFSIR1h0P2EFDP2veNJG7tBuxzDKfH3hzjW01C4/WyWN6bk7P7XKwisxw775klBQ==} @@ -6032,7 +6032,7 @@ snapshots: dependencies: '@launchdarkly/js-sdk-common': 2.20.0 - '@launchdarkly/js-client-sdk-common@1.17.1': + '@launchdarkly/js-client-sdk-common@1.17.2': dependencies: '@launchdarkly/js-sdk-common': 2.20.0 @@ -6040,9 +6040,9 @@ snapshots: dependencies: '@launchdarkly/js-client-sdk-common': 1.16.0 - '@launchdarkly/js-client-sdk@0.12.1': + '@launchdarkly/js-client-sdk@0.13.0': dependencies: - '@launchdarkly/js-client-sdk-common': 1.17.1 + '@launchdarkly/js-client-sdk-common': 1.17.2 '@launchdarkly/js-client-sdk@0.6.0': dependencies: From 172e3ebecb21374456b4beb37a59e9dd77d6c640 Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Wed, 21 Jan 2026 16:15:06 -0600 Subject: [PATCH 09/12] docs: adding comments on how the change handler is adapting the payload --- .../core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx b/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx index edc78832..95eb4431 100644 --- a/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx @@ -107,6 +107,13 @@ export function FlagSdkOverrideProvider({ children, flagOverridePlugin }: FlagSd setIsLoading(false); // Subscribe to changes with incremental updates + // NOTE: a better way to do this might be to be able to use the LDPluginEnvironmentMetadata that + // is passed in when the plugin is registered. That property has the client version number which can + // then be used to determine how to adapt the change handler. + // Currently the change handler is set up to be able to handle both <= v3 and >= v4 change events. + // <= v3: changes are passed as the first argument and as a map of flag keys and their changed values. + // >= v4: changes are passed as the second argument (the first argument is the context) and is an array of flag keys + // of changed flags. const handleChange = (changes: Record, keys: string[]) => { setFlags((prevFlags) => { const updatedRawFlags = ldClient.allFlags(); From a4149877a5d1010c5b187ab68cabb9b0f685bd45 Mon Sep 17 00:00:00 2001 From: joker23 <2494686+joker23@users.noreply.github.com> Date: Fri, 23 Jan 2026 18:18:29 -0600 Subject: [PATCH 10/12] Update packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx Co-authored-by: pranjal-jately-ld --- .../src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx b/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx index 95eb4431..523614fb 100644 --- a/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx +++ b/packages/toolbar/src/core/ui/Toolbar/context/FlagSdkOverrideProvider.tsx @@ -114,7 +114,7 @@ export function FlagSdkOverrideProvider({ children, flagOverridePlugin }: FlagSd // <= v3: changes are passed as the first argument and as a map of flag keys and their changed values. // >= v4: changes are passed as the second argument (the first argument is the context) and is an array of flag keys // of changed flags. - const handleChange = (changes: Record, keys: string[]) => { + const handleChange = (changes: Record, keys?: string[]) => { setFlags((prevFlags) => { const updatedRawFlags = ldClient.allFlags(); const newFlags = buildFlags(updatedRawFlags, apiFlags); From 86367e75bfc9160cc0cf79ebdfd127fee9020d93 Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Wed, 28 Jan 2026 14:42:34 -0600 Subject: [PATCH 11/12] fix: typing to align with browser sdk v4 --- packages/toolbar/src/core/utils/analytics.ts | 2 +- packages/toolbar/src/core/utils/feedback.ts | 2 +- packages/toolbar/src/types/plugins/LDClient.ts | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/toolbar/src/core/utils/analytics.ts b/packages/toolbar/src/core/utils/analytics.ts index ca724fe1..b6d9774b 100644 --- a/packages/toolbar/src/core/utils/analytics.ts +++ b/packages/toolbar/src/core/utils/analytics.ts @@ -1,4 +1,4 @@ -import type { LDClient } from 'launchdarkly-js-client-sdk'; +import type { LDClient } from '../../types/plugins/LDClient'; import type { FeedbackSentiment } from '../../types/analytics'; import { isDoNotTrackEnabled } from './browser'; import { sendFeedback } from './feedback'; diff --git a/packages/toolbar/src/core/utils/feedback.ts b/packages/toolbar/src/core/utils/feedback.ts index 00cfb8e8..3615a580 100644 --- a/packages/toolbar/src/core/utils/feedback.ts +++ b/packages/toolbar/src/core/utils/feedback.ts @@ -1,5 +1,5 @@ import { LDRecord } from '@launchdarkly/session-replay'; -import type { LDClient } from '@launchdarkly/js-client-sdk'; +import type { LDClient } from '../../types/plugins/LDClient'; export type LDFeedbackSentiment = 'positive' | 'neutral' | 'negative'; diff --git a/packages/toolbar/src/types/plugins/LDClient.ts b/packages/toolbar/src/types/plugins/LDClient.ts index 8580a662..6a288d8b 100644 --- a/packages/toolbar/src/types/plugins/LDClient.ts +++ b/packages/toolbar/src/types/plugins/LDClient.ts @@ -15,4 +15,5 @@ export interface LDClient { getContext(): any; on(key: string, callback: (...args: any[]) => void): void; off(key: string, callback: (...args: any[]) => void): void; + flush(): void; } From 55c20166c9a3e66b6771723f98fcd38ed06fbbda Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Wed, 18 Feb 2026 15:18:55 -0600 Subject: [PATCH 12/12] chore: update js-client-sdk to GA version --- packages/toolbar/package.json | 2 +- pnpm-lock.yaml | 22 +++++----------------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/packages/toolbar/package.json b/packages/toolbar/package.json index 36942688..e478b764 100644 --- a/packages/toolbar/package.json +++ b/packages/toolbar/package.json @@ -138,10 +138,10 @@ "@codemirror/lint": "^6.9.3", "@codemirror/state": "^6.5.4", "@codemirror/view": "^6.39.11", + "@launchdarkly/js-client-sdk": "^4.0.0", "@launchdarkly/observability": "^1.0.0", "@launchdarkly/session-replay": "^1.0.0", "@launchpad-ui/components": "^0.17.12", - "@launchdarkly/js-client-sdk": "^0.13.0", "@launchpad-ui/tokens": "^0.15.1", "@lezer/highlight": "1.2.1", "@react-aria/focus": "^3.21.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7fa75917..bd236bf6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -151,8 +151,8 @@ importers: specifier: ^6.39.11 version: 6.39.11 '@launchdarkly/js-client-sdk': - specifier: ^0.13.0 - version: 0.13.0 + specifier: ^4.0.0 + version: 4.0.0 '@launchdarkly/observability': specifier: ^1.0.0 version: 1.0.0 @@ -1195,14 +1195,8 @@ packages: '@launchdarkly/js-client-sdk@0.11.0': resolution: {integrity: sha512-PRmSsPsOi3NXjXpG4D4MoyImBibNoh9wOoWFbIMXHA26h5cMNBQjTFI6gqe8kyf2rDwqHerhrj5f734Zqwl33Q==} - '@launchdarkly/js-client-sdk@0.13.0': - resolution: {integrity: sha512-x0BcTUxqaul9f1iAlz1/S9Ckp7M2x9CgCgVVFDhQidiapxLQ5K8jtSYcseLFovjPd5hJ1ZvpawN5D3vftmkA3A==} - - '@launchdarkly/js-client-sdk@0.6.0': - resolution: {integrity: sha512-9aBgpGKXxaiPudNuQ9MoS8wFSIR1h0P2EFDP2veNJG7tBuxzDKfH3hzjW01C4/WyWN6bk7P7XKwisxw775klBQ==} - - '@launchdarkly/js-sdk-common@2.17.0': - resolution: {integrity: sha512-HYQNc4xbE58hBOO1yZsqE+BHOC36AYrHPZ5hwvlHoZRSsaoYCPM+Q/n+O+N3JhWXTqGsu6SwUzqUzTyxg8prGg==} + '@launchdarkly/js-client-sdk@4.0.0': + resolution: {integrity: sha512-jTKbVmfDsx/PsvtFsTpZTjZxp6HP5zQSMnz1okC3z/foVBsC4FxMrlWjMgZnYMNeoe2MOjDHOo6s9eQxI+zCew==} '@launchdarkly/js-sdk-common@2.20.0': resolution: {integrity: sha512-g1Lyi5xL7AXAP6BP8BzRcqVqIhqOSVpA5Bx8Vvj8A/4A6sIHVz2vIZluykD/bJiKg1+G9ojm+OCfdL/c0ebi0A==} @@ -6040,16 +6034,10 @@ snapshots: dependencies: '@launchdarkly/js-client-sdk-common': 1.16.0 - '@launchdarkly/js-client-sdk@0.13.0': + '@launchdarkly/js-client-sdk@4.0.0': dependencies: '@launchdarkly/js-client-sdk-common': 1.17.2 - '@launchdarkly/js-client-sdk@0.6.0': - dependencies: - '@launchdarkly/js-client-sdk-common': 1.13.0 - - '@launchdarkly/js-sdk-common@2.17.0': {} - '@launchdarkly/js-sdk-common@2.20.0': {} '@launchdarkly/observability@1.0.0':