Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<EventName>(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);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export default class EventSource<E extends string = never> {
private _initialRetryDelayMillis: number = 1000;
private _retryCount: number = 0;
private _logger?: any;
private _urlBuilder?: () => string;

constructor(url: string, options?: EventSourceOptions) {
const opts = {
Expand All @@ -84,6 +85,7 @@ export default class EventSource<E extends string = never> {
this._retryAndHandleError = opts.retryAndHandleError;
this._initialRetryDelayMillis = opts.initialRetryDelayMillis!;
this._logger = opts.logger;
this._urlBuilder = opts.urlBuilder;

this._tryConnect(true);
}
Expand Down Expand Up @@ -116,6 +118,12 @@ export default class EventSource<E extends string = never> {
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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default class PlatformRequests implements Requests {
body: eventSourceInitDict.body,
retryAndHandleError: eventSourceInitDict.errorFilter,
logger: this._logger,
urlBuilder: eventSourceInitDict.urlBuilder,
});
}

Expand Down
Loading