Skip to content

Commit 20ff2b4

Browse files
committed
fix(browser-sdk): include credentials on SSE connections
1 parent 7250b9b commit 20ff2b4

9 files changed

Lines changed: 44 additions & 1 deletion

File tree

‎.changeset/brave-pandas-listen.md‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@reflag/browser-sdk": patch
3+
"@reflag/openfeature-browser-provider": patch
4+
"@reflag/react-native-sdk": patch
5+
"@reflag/react-sdk": patch
6+
"@reflag/vue-sdk": patch
7+
---
8+
9+
Pass `credentials: "include"` through to EventSource connections so live updates and feedback SSE can include cookies when proxying through an authenticated backend.

‎packages/browser-sdk/README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ Supply these to the constructor call:
9999
type Configuration = {
100100
logger: console; // by default only logs warn/error, by passing `console` you'll log everything
101101
apiBaseUrl?: "https://front.reflag.com";
102+
credentials?: "include" | "same-origin" | "omit"; // forwarded to fetch requests; "include" also enables credentials for the default EventSource transport
102103
feedback?: undefined; // See FEEDBACK.md
103104
enableTracking?: true; // set to `false` to stop sending track events and user/company updates to Reflag servers. Useful when you're impersonating a user
104105
enableLiveFlagUpdates?: false; // Set to `true` to keep flags up to date over SSE (browser SDK default: false)
@@ -242,6 +243,7 @@ For server-side rendered applications, you can eliminate the initial network req
242243
type Configuration = {
243244
logger: console; // by default only logs warn/error, by passing `console` you'll log everything
244245
apiBaseUrl?: "https://front.reflag.com";
246+
credentials?: "include" | "same-origin" | "omit"; // forwarded to fetch requests; "include" also enables credentials for the default EventSource transport
245247
feedback?: undefined; // See FEEDBACK.md
246248
enableTracking?: true; // set to `false` to stop sending track events and user/company updates to Reflag servers. Useful when you're impersonating a user
247249
offline?: boolean; // Use the SDK in offline mode. Offline mode is useful during testing and local development

‎packages/browser-sdk/src/client.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,7 @@ export class ReflagClient {
480480
private autoFeedbackInit: Promise<void> | undefined;
481481
private readonly enableLiveFlagUpdates: boolean;
482482
private readonly eventSourceFactory: EventSourceFactory | undefined;
483+
private readonly credentials: RequestCredentials | undefined;
483484
private readonly sdkVersion: string;
484485
private pubSubChannel: AblySSEChannel | undefined;
485486
private pubSubInit: Promise<void> | undefined;
@@ -542,6 +543,7 @@ export class ReflagClient {
542543
this.enableLiveFlagUpdates =
543544
requestedEnableLiveFlagUpdates && !bootstrappedWithoutFlagStateVersion;
544545
this.eventSourceFactory = opts?.eventSourceFactory;
546+
this.credentials = opts?.credentials;
545547
this.sdkVersion = opts?.sdkVersion ?? SDK_VERSION;
546548

547549
this.requestFeedbackOptions = {
@@ -627,6 +629,7 @@ export class ReflagClient {
627629
opts?.feedback?.ui?.position,
628630
opts?.feedback?.ui?.translations,
629631
bulkQueue ? (event) => bulkQueue.enqueue(event) : undefined,
632+
this.credentials,
630633
);
631634
}
632635
}
@@ -1387,6 +1390,7 @@ export class ReflagClient {
13871390
sdkVersion: this.sdkVersion,
13881391
path: "sse/client",
13891392
context: this.context,
1393+
credentials: this.credentials,
13901394
});
13911395
}
13921396

‎packages/browser-sdk/src/feedback/feedback.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ export class AutoFeedback {
289289
private position: Position = DEFAULT_POSITION,
290290
private feedbackTranslations: Partial<FeedbackTranslations> = {},
291291
private enqueueBulkEvent?: (event: BulkEvent) => Promise<void>,
292+
private credentials?: RequestCredentials,
292293
) {}
293294

294295
/**
@@ -315,6 +316,7 @@ export class AutoFeedback {
315316
),
316317
logger: this.logger,
317318
sseBaseUrl: this.sseBaseUrl,
319+
credentials: this.credentials,
318320
});
319321
this.logger.debug(`automatic feedback connection established`);
320322
} catch (e) {

‎packages/browser-sdk/src/sse.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export class AblySSEChannel {
6363
private sdkVersion?: string,
6464
private path = "sse",
6565
private context?: ReflagContext,
66+
private credentials?: RequestCredentials,
6667
) {
6768
this.logger = loggerWithPrefix(logger, "[SSE]");
6869

@@ -139,7 +140,9 @@ export class AblySSEChannel {
139140
return;
140141
}
141142

142-
return new EventSource(url);
143+
return this.credentials === "include"
144+
? new EventSource(url, { withCredentials: true })
145+
: new EventSource(url);
143146
}
144147

145148
public async connect() {
@@ -270,6 +273,7 @@ export function openAblySSEChannel({
270273
sdkVersion,
271274
path,
272275
context,
276+
credentials,
273277
}: {
274278
channel?: string;
275279
channels?: string[];
@@ -281,6 +285,7 @@ export function openAblySSEChannel({
281285
sdkVersion?: string;
282286
path?: string;
283287
context?: ReflagContext;
288+
credentials?: RequestCredentials;
284289
}) {
285290
const subscribedChannels = channels ?? (channel ? [channel] : []);
286291
const sse = new AblySSEChannel(
@@ -293,6 +298,7 @@ export function openAblySSEChannel({
293298
sdkVersion,
294299
path,
295300
context,
301+
credentials,
296302
);
297303

298304
sse.open();

‎packages/browser-sdk/test/init.test.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ describe("init", () => {
156156
enableLiveFlagUpdates: true,
157157
feedback: { enableAutoFeedback: false },
158158
sdkVersion: "browser-sdk/test",
159+
credentials: "include",
159160
});
160161
await reflagInstance.initialize();
161162

@@ -169,6 +170,7 @@ describe("init", () => {
169170
company: undefined,
170171
other: {},
171172
});
173+
expect(spy.mock.calls[0][0].credentials).toBe("include");
172174

173175
await reflagInstance.stop();
174176
expect(closeChannel).toHaveBeenCalledTimes(1);

‎packages/browser-sdk/test/sse.test.ts‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,22 @@ describe("connection handling", () => {
115115
);
116116
});
117117

118+
test("enables EventSource credentials when credentials is include", async () => {
119+
openAblySSEChannel({
120+
channels: [],
121+
callback: vi.fn(),
122+
logger: testLogger,
123+
sseBaseUrl: sseHost,
124+
path: "sse/client",
125+
credentials: "include",
126+
});
127+
128+
expect(vi.mocked(window.EventSource)).toHaveBeenCalledWith(
129+
`${sseHost}/sse/client`,
130+
{ withCredentials: true },
131+
);
132+
});
133+
118134
test("passes parsed message envelopes to the callback", async () => {
119135
const callback = vi.fn();
120136
const sse = createSSEChannel(callback);

‎packages/react-sdk/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,7 @@ The `<ReflagProvider>` initializes the Reflag SDK, fetches flags and starts list
552552
- `enableTracking`: Set to `false` to stop sending tracking events and user/company updates to Reflag. Useful when you're impersonating a user (defaults to `true`),
553553
- `enableLiveFlagUpdates`: Enables live flag updates over SSE. Defaults to `true` in the React SDK.
554554
- `apiBaseUrl`: Optional base URL for the Reflag API. This also controls the SSE origin used for live flag updates and automated feedback,
555+
- `credentials`: Optional fetch credentials mode. Set to `"include"` when proxying through your backend and authenticating with cookies; this also enables credentials for live-update SSE connections.
555556
- `appBaseUrl`: Optional base URL for the Reflag application. Use this to override the default app URL,
556557
557558
- `logger`: Optional custom logger implementation (`debug`, `info`, `warn`, `error`) used by the underlying client,

‎packages/vue-sdk/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ The `<ReflagProvider>` initializes the Reflag SDK, fetches flags and starts list
183183

184184
- `enableTracking`: Set to `false` to stop sending tracking events and user/company updates to Reflag. Useful when you're impersonating a user (defaults to `true`),
185185
- `apiBaseUrl`: Optional base URL for the Reflag API. This also controls the SSE origin used for live flag updates and automated feedback,
186+
- `credentials`: Optional fetch credentials mode. Set to `"include"` when proxying through your backend and authenticating with cookies; this also enables credentials for live-update SSE connections.
186187
- `appBaseUrl`: Optional base URL for the Reflag application. Use this to override the default app URL,
187188

188189
- `debug`: Set to `true` to enable debug logging to the console. If both `logger` and `debug` are provided, `logger` takes precedence,

0 commit comments

Comments
 (0)