Skip to content

Commit e78dbe4

Browse files
committed
feat(client): add disconnect() to close the SSE connection (#2351)
* feat(client): add disconnect() to close the SSE connection Expose a way to close the EventSource for the current path and stop the reconnection watchdog (e.g. before tearing the page down). The cached wrapper is dropped so a later setOptionsAndConnect() opens a fresh connection. Ref webpack/webpack-hot-middleware#367 * fixup!
1 parent 3fc8f77 commit e78dbe4

4 files changed

Lines changed: 56 additions & 2 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,10 @@ hotClient.useCustomOverlay({
420420
// Connect manually when `autoConnect=false`. Accepts the same option keys as
421421
// the query-string API above.
422422
hotClient.setOptionsAndConnect({ path: "/__hmr" });
423+
424+
// Close the SSE connection and stop reconnecting (e.g. before tearing the
425+
// page down). A later `setOptionsAndConnect` call opens a fresh connection.
426+
hotClient.disconnect();
423427
```
424428

425429
## API

client-src/globals.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ interface ClientReporter {
2020

2121
interface EventSourceWrapper {
2222
addMessageListener(fn: (event: { data: string }) => void): void;
23+
close(): void;
2324
}
2425

2526
interface Window {

client-src/index.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ function setOverrides(overrides) {
7878
*/
7979

8080
/**
81-
* @returns {{ addMessageListener: (fn: MessageListener) => void }} event source wrapper
81+
* @returns {{ addMessageListener: (fn: MessageListener) => void, close: () => void }} event source wrapper
8282
*/
8383
function createEventSourceWrapper() {
8484
/** @type {EventSource} */
@@ -104,9 +104,17 @@ function createEventSourceWrapper() {
104104
}
105105
};
106106

107-
const handleDisconnect = () => {
107+
/**
108+
* Close the connection and stop the activity timer without scheduling a
109+
* reconnection.
110+
*/
111+
const close = () => {
108112
clearInterval(timer);
109113
source.close();
114+
};
115+
116+
const handleDisconnect = () => {
117+
close();
110118
setTimeout(init, /** @type {number} */ (options.timeout));
111119
};
112120

@@ -134,6 +142,7 @@ function createEventSourceWrapper() {
134142
addMessageListener(fn) {
135143
listeners.push(fn);
136144
},
145+
close,
137146
};
138147
}
139148

@@ -179,6 +188,20 @@ export function setOptionsAndConnect(overrides) {
179188
connect();
180189
}
181190

191+
/**
192+
* Close the SSE connection for the current path and stop reconnecting. A
193+
* later `setOptionsAndConnect` call opens a fresh connection.
194+
*/
195+
export function disconnect() {
196+
const path = /** @type {string} */ (options.path);
197+
const wrappers = window[WRAPPER_KEY];
198+
199+
if (wrappers && wrappers[path]) {
200+
wrappers[path].close();
201+
delete wrappers[path];
202+
}
203+
}
204+
182205
// eslint-disable-next-line jsdoc/reject-any-type
183206
/** @typedef {any} EXPECTED_ANY */
184207

test/client.test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,32 @@ describe("client", () => {
597597
jest.advanceTimersByTime(20 * 1000);
598598
expect(EventSourceStub.instances).toHaveLength(2);
599599
});
600+
601+
it("disconnect() closes the connection and does not reconnect", () => {
602+
jest.useFakeTimers({ doNotFake: ["nextTick"] });
603+
delete globalThis.__wdmEventSourceWrapper;
604+
EventSourceStub.instances.length = 0;
605+
client = loadClient();
606+
607+
const [first] = EventSourceStub.instances;
608+
client.disconnect();
609+
610+
expect(first.closed).toBe(true);
611+
// Neither the watchdog nor a reconnect timer may re-open it.
612+
jest.advanceTimersByTime(60 * 1000);
613+
expect(EventSourceStub.instances).toHaveLength(1);
614+
});
615+
616+
it("disconnect() drops the cached wrapper so a new connect starts fresh", () => {
617+
client.disconnect();
618+
expect(
619+
globalThis.__wdmEventSourceWrapper["/__webpack_hmr"],
620+
).toBeUndefined();
621+
622+
client.setOptionsAndConnect({});
623+
expect(EventSourceStub.instances).toHaveLength(2);
624+
expect(EventSourceStub.lastInstance().closed).toBe(false);
625+
});
600626
});
601627

602628
describe("with logging option", () => {

0 commit comments

Comments
 (0)