Skip to content

feat: Add an opt-in fetch-based EventSource entry to the browser SDK - #2033

Open
joker23 wants to merge 14 commits into
mainfrom
skz/sdk-3151/common-eventsource-node-consumers
Open

joker23 wants to merge 14 commits into
mainfrom
skz/sdk-3151/common-eventsource-node-consumers

Conversation

@joker23

@joker23 joker23 commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Adds an opt-in fetch()-based EventSource to the browser SDK behind its own @launchdarkly/js-client-sdk/fetch-eventsource entry point, so neither the implementation nor its dependency reaches the default bundle. The native browser EventSource remains the default. Includes the eventSource factory option plumbing and an example app.

The browser contract-test entity can be opened with ?eventsource=fetch, which makes every client use the fetch-based EventSource and advertise the client-use-post and client-event-source-http-errors capabilities. CI runs the fetch entity against both harnesses with no skip list: the v2 run covers the FDv1 path, including REPORT streaming and the tags header tests, and the v3 run covers the FDv2 path, including the usePost POST streaming variants.

The transport does not send the x-launchdarkly-tags header. The streaming service's CORS allow-list does not include it, and the browser preflight would fail.

Replaces #2034 and #2048, which GitHub auto-marked merged during a stack reorder; their bot-review history lives there.


Note

Overview
Adds an opt-in fetch()-based streaming transport for the browser SDK via a separate @launchdarkly/js-client-sdk/fetch-eventsource export (optional @launchdarkly/eventsource dependency), keeping the native EventSource as the default bundle.

Introduces the eventSource factory option: validated in browser options, wired through BrowserPlatform / BrowserRequests, and used to advertise transport capabilities (e.g. POST streaming when customMethod is true). fetchBrowserEventSource forwards stream init options but drops x-launchdarkly-tags because CORS preflight would fail.

Contract tests and CI gain a ?eventsource=fetch entity mode (injects the fetch factory, enables usePost, advertises client-use-post / client-event-source-http-errors), extra harness runs (native + fetch for FDv1/FDv2), updated suppression lists, and a small custom EventSource example app.

In shared FDv2 streaming, StreamingFDv2Base avoids duplicate interrupted results when transports invoke both the error filter and onerror for the same failure.

Reviewed by Cursor Bugbot for commit 5b13155. Bugbot is set up for automated code reviews on this repo. Configure here.

@joker23
joker23 added this pull request to stack #2035 September 18, 2026 20:32
@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

@launchdarkly/js-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 31343 bytes
Compressed size limit: 31500
Uncompressed size: 148858 bytes

@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

@launchdarkly/js-client-sdk-common size report
This is the brotli compressed size of the ESM build.
Compressed size: 26046 bytes
Compressed size limit: 44000
Uncompressed size: 168246 bytes

@github-actions

github-actions Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

@launchdarkly/js-client-sdk size report
This is the brotli compressed size of the ESM build.
Compressed size: 34246 bytes
Compressed size limit: 35000
Uncompressed size: 122308 bytes

@joker23
joker23 force-pushed the skz/sdk-3151/common-eventsource-node-consumers branch from 9c38ea8 to 724e94a Compare September 18, 2026 20:55
@joker23
joker23 force-pushed the skz/sdk-3151/common-eventsource-node-consumers branch from 724e94a to 4fe3c92 Compare September 18, 2026 21:36
@joker23
joker23 force-pushed the skz/sdk-3151/common-eventsource-node-consumers branch from 4fe3c92 to 197417a Compare September 21, 2026 13:44
@joker23
joker23 force-pushed the skz/sdk-3151/common-eventsource-node-consumers branch 4 times, most recently from b1572a2 to 684ed50 Compare September 22, 2026 19:53
@joker23
joker23 force-pushed the skz/sdk-3151/common-eventsource-node-consumers branch from 684ed50 to d5c5d4d Compare September 22, 2026 20:36
@joker23
joker23 force-pushed the skz/sdk-3151/common-eventsource-node-consumers branch from d5c5d4d to 039c061 Compare September 22, 2026 21:44
@joker23
joker23 force-pushed the skz/sdk-3151/common-eventsource-node-consumers branch from 039c061 to cd08556 Compare September 23, 2026 16:32
@joker23
joker23 force-pushed the skz/sdk-3151/common-eventsource-node-consumers branch 2 times, most recently from e73c276 to 8701dc9 Compare September 24, 2026 14:07
@joker23
joker23 force-pushed the skz/sdk-3151/common-eventsource-node-consumers branch from 8701dc9 to e4e333c Compare September 24, 2026 14:23
@joker23
joker23 force-pushed the skz/sdk-3151/common-eventsource-node-consumers branch from e4e333c to 7164a19 Compare September 24, 2026 15:20
@joker23
joker23 force-pushed the skz/sdk-3151/common-eventsource-node-consumers branch from 1f93481 to b17e046 Compare October 1, 2026 17:01
@joker23
joker23 force-pushed the skz/sdk-3151/common-eventsource-node-consumers branch 2 times, most recently from 3f05376 to fd9e7c8 Compare October 5, 2026 18:24
@joker23
joker23 force-pushed the skz/sdk-3151/common-eventsource-node-consumers branch from fd9e7c8 to 42838fd Compare October 6, 2026 13:15
@joker23
joker23 force-pushed the skz/sdk-3151/common-eventsource-node-consumers branch from 42838fd to 596108e Compare October 6, 2026 13:43
@joker23
joker23 force-pushed the skz/sdk-3151/common-eventsource-node-consumers branch 2 times, most recently from 02bfa05 to 4a7be4e Compare October 7, 2026 15:55

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 potential issue.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

// x-launchdarkly-tags. A browser preflight with that header fails, and the stream
// can never connect.
const { 'x-launchdarkly-tags': _tags, ...headers } = eventSourceInitDict.headers;
return createEventSource(url, {

@devin-ai-integration devin-ai-integration Bot Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Network failures reported as HTTP errors

For a status-less fetch failure, alreadyReported suppresses the network error after handleError queues an HTTP error with status 0. Clients receive ERROR_RESPONSE instead of NETWORK_ERROR, losing the failure message.

Learn more

The fetch-based EventSource calls the error filter before dispatching its error event. For a status-less failure, handleError currently builds errorInfoFromHttpError(0). The new guard discards the subsequent onerror result, which previously carried errorInfoFromNetworkError(err.message). The orchestrator passes the remaining error kind, message, and status code into reportStatusError, so consumers now see an HTTP response that never occurred.

Example: If fetch() rejects with network unreachable, the stream reports ERROR_RESPONSE, Unexpected status code: 0, and status code 0 instead of NETWORK_ERROR and network unreachable.

Recommended fix: Classify status-less failures as network errors in handleError, using the provided message, while retaining the single-result suppression in onerror. Add an assertion for the error kind and message in the status-less failure test.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

},
"optionalDependencies": {
"@launchdarkly/eventsource": "0.2.1"
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional EventSource pin is unpublished

High Severity

The browser SDK pins optional @launchdarkly/eventsource to 0.2.1, but the workspace package, changelog, and release-please manifest are still at 0.2.0. Installs skip the missing optional package, so importing @launchdarkly/js-client-sdk/fetch-eventsource fails to resolve createEventSource.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4577f29. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is false

},
"dependencies": {
"@launchdarkly/js-client-sdk": "4.13.0"
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Example pins a nonexistent SDK version

Medium Severity

The new example depends on @launchdarkly/js-client-sdk 4.13.0, but the workspace package is still 4.12.0 (as is example/package.json). Yarn will not link the local SDK for an unsatisfied exact range, so the example cannot install or import the new fetch-eventsource entry until that version exists.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 4577f29. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think this is false

Comment thread packages/sdk/browser/__tests__/options.test.ts

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5b13155. Configure here.

// Every branch below queues a result for this failure, so onerror must not
// queue another one for the same event.
filterHandledFailure = true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Network errors misclassified as HTTP errors

Medium Severity

The new filterHandledFailure mark stops onerror from reporting a status-less failure after the error filter already ran, which is the fetch EventSource path. handleError still always builds errorInfoFromHttpError(err.status ?? 0), so a network drop is recorded as ErrorResponse with status 0 ("Unexpected status code: 0") instead of NETWORK_ERROR. Data source status, inspectors, and diagnostics then show the wrong error kind for every offline or connection-drop on the opt-in transport.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 5b13155. Configure here.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant