From 9bedd418cb80dc9d90221c9f7993f85dd2e8d905 Mon Sep 17 00:00:00 2001 From: Steven Zhang Date: Mon, 2 Nov 2026 10:38:51 -0500 Subject: [PATCH] feat: add fetch-based eventsource entry point to the react sdk --- .../__tests__/client/LDReactClient.test.ts | 17 +++++++++++++++ .../client/provider/LDReactProvider.test.tsx | 11 ++++++++++ packages/sdk/react/package.json | 21 +++++++++++++++++-- .../sdk/react/src/fetch-eventsource/index.ts | 14 +++++++++++++ packages/sdk/react/tsup.config.js | 12 +++++++++++ .../package-config-tests/baseline/attw.json | 4 ++++ .../baseline/runtime.json | 6 ++++++ .../package-config-tests/baseline/types.json | 20 ++++++++++++++++++ 8 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 packages/sdk/react/src/fetch-eventsource/index.ts diff --git a/packages/sdk/react/__tests__/client/LDReactClient.test.ts b/packages/sdk/react/__tests__/client/LDReactClient.test.ts index ff7ae21377..ace7c670b8 100644 --- a/packages/sdk/react/__tests__/client/LDReactClient.test.ts +++ b/packages/sdk/react/__tests__/client/LDReactClient.test.ts @@ -84,6 +84,23 @@ it('returns getInitializationState() === "initializing" initially', () => { expect(client.getInitializationState()).toBe('initializing'); }); +it('passes the eventSource option to the base client unmodified', () => { + const { mock } = makeMockBaseClient(); + (createBaseClient as jest.Mock).mockReturnValue(mock); + const eventSource = { + createEventSource: jest.fn(), + capabilities: { readTimeout: true, customMethod: true, headers: true }, + }; + + createClient('test-id', { kind: 'user', key: 'u1' }, { eventSource }); + + expect(createBaseClient).toHaveBeenLastCalledWith( + 'test-id', + { kind: 'user', key: 'u1' }, + expect.objectContaining({ eventSource }), + ); +}); + it('sets initializedState to "complete" after start() resolves', async () => { const { mock, resolveStart } = makeMockBaseClient(); (createBaseClient as jest.Mock).mockReturnValue(mock); diff --git a/packages/sdk/react/__tests__/client/provider/LDReactProvider.test.tsx b/packages/sdk/react/__tests__/client/provider/LDReactProvider.test.tsx index 583c458e6c..321109af16 100644 --- a/packages/sdk/react/__tests__/client/provider/LDReactProvider.test.tsx +++ b/packages/sdk/react/__tests__/client/provider/LDReactProvider.test.tsx @@ -340,6 +340,17 @@ describe('createLDReactProvider (convenience factory)', () => { ); }); + it('passes the eventSource option to createClient unmodified', () => { + const eventSource = { + createEventSource: jest.fn(), + capabilities: { readTimeout: true, customMethod: true, headers: true }, + }; + createLDReactProvider('sdk-key', { kind: 'user', key: 'u1' }, { ldOptions: { eventSource } }); + + const clientOptions = (createClient as jest.Mock).mock.calls[0][2]; + expect(clientOptions.eventSource).toBe(eventSource); + }); + it('does not forward deferInitialization/startOptions/reactContext to createClient', () => { const reactContext = initLDReactContext(); const startOptions = { timeout: 3 }; diff --git a/packages/sdk/react/package.json b/packages/sdk/react/package.json index 0e743d7b1e..bc963d46f9 100644 --- a/packages/sdk/react/package.json +++ b/packages/sdk/react/package.json @@ -36,6 +36,16 @@ "default": "./dist/index.cjs" } }, + "./fetch-eventsource": { + "import": { + "types": "./dist/fetch-eventsource.d.ts", + "default": "./dist/fetch-eventsource.js" + }, + "require": { + "types": "./dist/fetch-eventsource.d.cts", + "default": "./dist/fetch-eventsource.cjs" + } + }, "./server": { "import": { "types": "./dist/server.d.ts", @@ -47,6 +57,13 @@ } } }, + "typesVersions": { + "*": { + "fetch-eventsource": [ + "./dist/fetch-eventsource.d.ts" + ] + } + }, "files": [ "dist" ], @@ -66,8 +83,8 @@ "typescript": "6.0.3" }, "dependencies": { - "@launchdarkly/js-client-sdk": "^4.14.0", - "@launchdarkly/js-server-sdk-common": "^2.26.1" + "@launchdarkly/js-client-sdk": "4.14.0", + "@launchdarkly/js-server-sdk-common": "2.26.1" }, "peerDependencies": { "react": ">=18.0.0", diff --git a/packages/sdk/react/src/fetch-eventsource/index.ts b/packages/sdk/react/src/fetch-eventsource/index.ts new file mode 100644 index 0000000000..8bb7e523d1 --- /dev/null +++ b/packages/sdk/react/src/fetch-eventsource/index.ts @@ -0,0 +1,14 @@ +/** + * Optional `fetch()`-based EventSource for the React SDK's streaming connection. + * + * This entry point re-exports the browser SDK's `fetch-eventsource` entry. Pass + * `fetchBrowserEventSource` as the `eventSource` option to opt in. The native browser EventSource + * remains the default, and only an application that imports this entry pays for the alternative. + * + * The `@launchdarkly/eventsource` dependency is optional. If an install omits optional + * dependencies, install it directly before this entry point is imported. + * + * @packageDocumentation + */ +export { fetchBrowserEventSource } from '@launchdarkly/js-client-sdk/fetch-eventsource'; +export type { LDEventSourceFactory } from '@launchdarkly/js-client-sdk/fetch-eventsource'; diff --git a/packages/sdk/react/tsup.config.js b/packages/sdk/react/tsup.config.js index d1c756c588..d371d30444 100644 --- a/packages/sdk/react/tsup.config.js +++ b/packages/sdk/react/tsup.config.js @@ -44,4 +44,16 @@ export default defineConfig([ mangleProps(opts); }, }, + { + ...sharedOptions, + entry: { 'fetch-eventsource': 'src/fetch-eventsource/index.ts' }, + clean: false, + // Keep the browser SDK's entry as an import so `@launchdarkly/eventsource` stays an optional + // dependency of js-client-sdk and is not bundled or required from this package. + noExternal: sharedOptions.noExternal.filter((name) => name !== '@launchdarkly/js-client-sdk'), + external: ['@launchdarkly/js-client-sdk'], + esbuildOptions(opts) { + mangleProps(opts); + }, + }, ]); diff --git a/scripts/package-config-tests/baseline/attw.json b/scripts/package-config-tests/baseline/attw.json index a934e5d59d..0b8defec65 100644 --- a/scripts/package-config-tests/baseline/attw.json +++ b/scripts/package-config-tests/baseline/attw.json @@ -107,6 +107,10 @@ "@launchdarkly/react-sdk | node10": "NoResolution", "@launchdarkly/react-sdk | node16-cjs": "ok", "@launchdarkly/react-sdk | node16-esm": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | bundler": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | node10": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | node16-cjs": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | node16-esm": "ok", "@launchdarkly/react-sdk/server | bundler": "ok", "@launchdarkly/react-sdk/server | node10": "NoResolution", "@launchdarkly/react-sdk/server | node16-cjs": "ok", diff --git a/scripts/package-config-tests/baseline/runtime.json b/scripts/package-config-tests/baseline/runtime.json index db2e709468..11eb6d587d 100644 --- a/scripts/package-config-tests/baseline/runtime.json +++ b/scripts/package-config-tests/baseline/runtime.json @@ -161,6 +161,12 @@ "@launchdarkly/react-sdk | esbuild-esm-node": "ok", "@launchdarkly/react-sdk | import": "ok", "@launchdarkly/react-sdk | require": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | esbuild-cjs-node": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | esbuild-esm-browser": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | esbuild-esm-neutral": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | esbuild-esm-node": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | import": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | require": "ok", "@launchdarkly/react-sdk/server | esbuild-cjs-node": "ok", "@launchdarkly/react-sdk/server | esbuild-esm-browser": "ok", "@launchdarkly/react-sdk/server | esbuild-esm-neutral": "ok", diff --git a/scripts/package-config-tests/baseline/types.json b/scripts/package-config-tests/baseline/types.json index c8f0c672aa..a6bcf8113a 100644 --- a/scripts/package-config-tests/baseline/types.json +++ b/scripts/package-config-tests/baseline/types.json @@ -543,6 +543,26 @@ "@launchdarkly/react-sdk | nodenext-esm | ts6.0.3": "ok", "@launchdarkly/react-sdk | preserve | ts5.9.3": "ok", "@launchdarkly/react-sdk | preserve | ts6.0.3": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | bundler | ts5.0.4": "TS1165 TS1169 TS2304 TS2339 TS2502 TS2726", + "@launchdarkly/react-sdk/fetch-eventsource | bundler | ts5.9.3": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | bundler | ts6.0.3": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | node10 | ts5.0.4": "TS1165 TS1169 TS2304 TS2339 TS2502 TS2726", + "@launchdarkly/react-sdk/fetch-eventsource | node10 | ts5.9.3": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | node10 | ts6.0.3": "TS5107", + "@launchdarkly/react-sdk/fetch-eventsource | node16-cjs | ts5.0.4": "TS1165 TS1169 TS2304 TS2339 TS2502 TS2726", + "@launchdarkly/react-sdk/fetch-eventsource | node16-cjs | ts5.9.3": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | node16-cjs | ts6.0.3": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | node16-esm | ts5.0.4": "TS1165 TS1169 TS2304 TS2339 TS2502 TS2726", + "@launchdarkly/react-sdk/fetch-eventsource | node16-esm | ts5.9.3": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | node16-esm | ts6.0.3": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | nodenext-cjs | ts5.0.4": "TS1165 TS1169 TS2304 TS2339 TS2502 TS2726", + "@launchdarkly/react-sdk/fetch-eventsource | nodenext-cjs | ts5.9.3": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | nodenext-cjs | ts6.0.3": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | nodenext-esm | ts5.0.4": "TS1165 TS1169 TS2304 TS2339 TS2502 TS2726", + "@launchdarkly/react-sdk/fetch-eventsource | nodenext-esm | ts5.9.3": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | nodenext-esm | ts6.0.3": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | preserve | ts5.9.3": "ok", + "@launchdarkly/react-sdk/fetch-eventsource | preserve | ts6.0.3": "ok", "@launchdarkly/react-sdk/server | bundler | ts5.0.4": "TS1165 TS1169 TS2304 TS2339 TS2502 TS2726", "@launchdarkly/react-sdk/server | bundler | ts5.9.3": "ok", "@launchdarkly/react-sdk/server | bundler | ts6.0.3": "ok",