[DevTools] Shut down standalone Bridge on socket close (#37076)

Ensures the standalone DevTools Bridge fully shuts down when its
WebSocket closes, with re-entrancy protection. Adds tests confirming
event-only shutdown leaves the Bridge active while socket closure shuts
it down.
This commit is contained in:
Ruslan Lesiutin
2026-07-23 10:39:15 +01:00
committed by GitHub
parent 561425c9c9
commit b2ce8a5aa1
2 changed files with 70 additions and 8 deletions
+12 -8
View File
@@ -132,6 +132,16 @@ export function connectToDevTools(options: ?ConnectOptions) {
let bridge: BackendBridge | null = null;
function shutdownBridge(): void {
const bridgeToShutdown = bridge;
if (bridgeToShutdown !== null) {
// Clear the active reference before shutdown flushes its final message
// through a potentially closed socket.
bridge = null;
bridgeToShutdown.shutdown();
}
}
const messageListeners = [];
const uri = protocol + '://' + host + ':' + port + prefixedPath;
@@ -170,10 +180,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
);
}
if (bridge !== null) {
bridge.shutdown();
}
shutdownBridge();
scheduleRetry();
}
},
@@ -273,10 +280,7 @@ export function connectToDevTools(options: ?ConnectOptions) {
debug('WebSocket.onclose');
}
if (bridge !== null) {
bridge.emit('shutdown');
}
shutdownBridge();
scheduleRetry();
}
@@ -0,0 +1,58 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
describe('connectToDevTools', () => {
let connectToDevTools;
let hook;
beforeEach(() => {
jest.resetModules();
delete window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
const backend = require('react-devtools-core/src/backend');
backend.initialize();
connectToDevTools = backend.connectToDevTools;
hook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
});
afterEach(() => {
jest.clearAllTimers();
delete window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
});
function createWebSocket(): WebSocket {
return {
CLOSED: 3,
OPEN: 1,
readyState: 1,
send: jest.fn(),
} as any as WebSocket;
}
it('shuts down cleanly when the WebSocket closes', () => {
const websocket = createWebSocket();
const onShutdown = jest.fn();
const unsubscribe = hook.sub('shutdown', onShutdown);
try {
connectToDevTools({websocket});
websocket.onopen();
websocket.readyState = websocket.CLOSED;
websocket.onclose();
jest.runAllTimers();
expect(onShutdown).toHaveBeenCalledTimes(1);
expect(global.consoleWarnMock).not.toHaveBeenCalledWith(
'Bridge was already shutdown.',
);
} finally {
unsubscribe();
}
});
});