From 6a268a58d032ff3523328ff674c9eaa744ac6220 Mon Sep 17 00:00:00 2001 From: Edson Henrique Date: Wed, 28 Oct 2026 08:35:39 -0400 Subject: [PATCH 1/2] fix: snapshot caller-owned URL objects --- .changeset/quiet-streams-remain.md | 5 ++++ src/EventSource.ts | 2 +- test/client.test.ts | 39 ++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 .changeset/quiet-streams-remain.md diff --git a/.changeset/quiet-streams-remain.md b/.changeset/quiet-streams-remain.md new file mode 100644 index 0000000..1b113a1 --- /dev/null +++ b/.changeset/quiet-streams-remain.md @@ -0,0 +1,5 @@ +--- +'eventsource': patch +--- + +Copy URL objects during construction so later caller mutations do not change the exposed URL or reconnection target. diff --git a/src/EventSource.ts b/src/EventSource.ts index 75ed50f..0d0e4b1 100644 --- a/src/EventSource.ts +++ b/src/EventSource.ts @@ -297,7 +297,7 @@ class EventSourceImpl extends EventTarget implements EventSource { try { if (url instanceof URL) { - this.#url = url + this.#url = new URL(url.href) } else if (typeof url === 'string') { this.#url = new URL(url, getBaseURL()) } else { diff --git a/test/client.test.ts b/test/client.test.ts index 18895e4..4c1c309 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -51,6 +51,45 @@ const bufferedEventTypes = [ {name: 'notice', eventType: 'notice'}, ] +test('snapshots the caller-owned URL during construction', () => { + const input = new URL(`${serverUrl}/counter?original=yes`) + const original = input.href + const es = new OurEventSource(input, esInit) + try { + input.pathname = '/changed' + input.search = '?changed=yes' + input.hash = '#changed' + expect(es.url).toBe(original) + } finally { + es.close() + } +}) + +test('reconnects to the original URL after the caller mutates its URL object', async () => { + const input = new URL(`${serverUrl}/counter?original=yes`) + const original = input.href + const requested: string[] = [] + const onMessage = getCallCounter({name: 'messages after reconnect'}) + const es = new OurEventSource(input, { + fetch(url, init) { + requested.push(url.toString()) + return request(url, init) + }, + }) + es.addEventListener('open', () => { + input.search = '?changed=yes' + }) + es.addEventListener('counter', onMessage.listener) + try { + await onMessage.waitForCallCount(4) + expect(requested.length).toBeGreaterThanOrEqual(2) + expect(requested.every((url) => url === original)).toBe(true) + expect(es.url).toBe(original) + } finally { + es.close() + } +}) + test.each(bufferedEventTypes)( 'stops dispatching buffered $name events when a listener closes the connection', async ({eventType}) => { From dcbd7d9ee2a3379b6c85752ef31d01ac45f20ac9 Mon Sep 17 00:00:00 2001 From: Espen Hovlandsdal Date: Wed, 28 Oct 2026 21:11:51 +0100 Subject: [PATCH 2/2] refactor: clone URL object directly instead of passing `href` --- src/EventSource.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/EventSource.ts b/src/EventSource.ts index 0d0e4b1..4f6eac6 100644 --- a/src/EventSource.ts +++ b/src/EventSource.ts @@ -297,7 +297,7 @@ class EventSourceImpl extends EventTarget implements EventSource { try { if (url instanceof URL) { - this.#url = new URL(url.href) + this.#url = new URL(url) } else if (typeof url === 'string') { this.#url = new URL(url, getBaseURL()) } else {