diff --git a/packages/sdk/react-native/__tests__/fromExternal/react-native-sse/EventSource.test.ts b/packages/sdk/react-native/__tests__/fromExternal/react-native-sse/EventSource.test.ts index 2073d1a0fc..84fa7788d6 100644 --- a/packages/sdk/react-native/__tests__/fromExternal/react-native-sse/EventSource.test.ts +++ b/packages/sdk/react-native/__tests__/fromExternal/react-native-sse/EventSource.test.ts @@ -114,4 +114,25 @@ describe('EventSource', () => { expect(mockXhr.open).toHaveBeenCalledTimes(2); expect(eventSource.onclose).toHaveBeenCalledTimes(1); }); + + test('recomputes the connection URL via urlBuilder on each connection', () => { + let basis: string | undefined; + const urlBuilder = jest.fn(() => (basis ? `${uri}?basis=${basis}` : uri)); + const es = new EventSource(uri, { logger, urlBuilder }); + es.onretrying = jest.fn(); + + // Initial connection asks the builder for the URL (no selector known yet). + jest.runAllTimers(); + expect(urlBuilder).toHaveBeenCalled(); + expect(mockXhr.open).toHaveBeenLastCalledWith('GET', uri, true); + + // Once a selector is known, a reconnect must replay it (e.g. FDv2 basis) + // rather than reuse the original URL. + basis = 'initial'; + // @ts-ignore - force a reconnect + es._tryConnect(); + jest.runAllTimers(); + + expect(mockXhr.open).toHaveBeenLastCalledWith('GET', `${uri}?basis=initial`, true); + }); }); diff --git a/packages/sdk/react-native/src/fromExternal/react-native-sse/EventSource.ts b/packages/sdk/react-native/src/fromExternal/react-native-sse/EventSource.ts index 9ba565582a..1d04733303 100644 --- a/packages/sdk/react-native/src/fromExternal/react-native-sse/EventSource.ts +++ b/packages/sdk/react-native/src/fromExternal/react-native-sse/EventSource.ts @@ -68,6 +68,7 @@ export default class EventSource { private _initialRetryDelayMillis: number = 1000; private _retryCount: number = 0; private _logger?: any; + private _urlBuilder?: () => string; constructor(url: string, options?: EventSourceOptions) { const opts = { @@ -84,6 +85,7 @@ export default class EventSource { this._retryAndHandleError = opts.retryAndHandleError; this._initialRetryDelayMillis = opts.initialRetryDelayMillis!; this._logger = opts.logger; + this._urlBuilder = opts.urlBuilder; this._tryConnect(true); } @@ -116,6 +118,12 @@ export default class EventSource { try { this._lastIndexProcessed = 0; this._status = this.CONNECTING; + // Recompute the URL on each (re)connection so that reconnects pick up + // state that changes over the connection's lifetime (for example the + // FDv2 `basis` selector, which must be replayed on reconnect). + if (this._urlBuilder) { + this._url = this._urlBuilder(); + } this._xhr.open(this._method, this._url, true); if (this._withCredentials) { diff --git a/packages/sdk/react-native/src/fromExternal/react-native-sse/types.ts b/packages/sdk/react-native/src/fromExternal/react-native-sse/types.ts index 3c828189b8..465b150fdf 100644 --- a/packages/sdk/react-native/src/fromExternal/react-native-sse/types.ts +++ b/packages/sdk/react-native/src/fromExternal/react-native-sse/types.ts @@ -54,6 +54,13 @@ export interface EventSourceOptions { retryAndHandleError?: (err: any) => boolean; initialRetryDelayMillis?: number; logger?: any; + /** + * Called before each (re)connection to compute the URL to connect to. When + * provided, this takes precedence over the static URL so that reconnections + * pick up state that changes over the connection's lifetime (for example the + * FDv2 `basis` selector, which must be replayed on reconnect). + */ + urlBuilder?: () => string; } type BuiltInEventMap = { diff --git a/packages/sdk/react-native/src/platform/PlatformRequests.ts b/packages/sdk/react-native/src/platform/PlatformRequests.ts index 4cbe4f6da9..c84fee726f 100644 --- a/packages/sdk/react-native/src/platform/PlatformRequests.ts +++ b/packages/sdk/react-native/src/platform/PlatformRequests.ts @@ -21,6 +21,7 @@ export default class PlatformRequests implements Requests { body: eventSourceInitDict.body, retryAndHandleError: eventSourceInitDict.errorFilter, logger: this._logger, + urlBuilder: eventSourceInitDict.urlBuilder, }); }