Skip to content

fix(dev-middleware): don't let one bad socket kill the dev server - #57794

Open
Milad74f wants to merge 1 commit into
react:mainfrom
Milad74f:fix/inspector-proxy-socket-error-handler
Open

fix(dev-middleware): don't let one bad socket kill the dev server#57794
Milad74f wants to merge 1 commit into
react:mainfrom
Milad74f:fix/inspector-proxy-socket-error-handler

Conversation

@Milad74f

@Milad74f Milad74f commented Aug 2, 2026

Copy link
Copy Markdown

Summary

Fixes #57793.

InspectorProxy never attached an "error" listener to the WebSocket connections it accepts. In Node, an 'error' event with no listener throws — so a socket-level failure on one connection takes down the whole dev server rather than just that connection.

Both #createDeviceConnectionWSServer and #createDebuggerConnectionWSServer listened for "message" and "close" only. They now also listen for "error", log it, and terminate that socket.

Two details worth calling out:

  • The listener is attached synchronously, before the first await. These connection handlers are async, so an error arriving while one is suspended would otherwise still have no listener and still be fatal.
  • terminate() rather than close(): close() waits for a closing handshake, and a socket that failed mid-frame may never complete one, which would leak the connection instead.

This does not touch the maxFragments: 16 * 1024 / maxBufferedChunks: 256 * 1024 caps in the vendored ws. Those look like deliberate hardening and I'm not proposing they be relaxed. The RangeError they raise is simply the error this project happened to hit; the bug is that any socket error was fatal to the process.

Changelog

[GENERAL] [FIXED] - Dev server no longer exits when a WebSocket connection to the inspector proxy errors

Test Plan

What I verified. Against a pristine @react-native/dev-middleware@0.81.5 from npm, a ~30-line script that connects to /inspector/device and sends one message in more fragments than the vendored ws allows kills the process:

RangeError: Too many message fragments
    at Receiver.getData (.../ws/lib/receiver.js:359:14)
Emitted 'error' event on WebSocket instance at:
    at Receiver.receiverOnError (.../ws/lib/websocket.js:787:13) {
  [Symbol(status-code)]: 1008
}

With this change applied (as a patch-package patch over the published dist/, which is the same edit as the src/ change here), the same script instead prints PROXY SURVIVED — connection dropped, server still up. The full reproducer is in #57793.

I then ran a real Expo/RN 0.81.5 app against the patched middleware: Metro starts, builds its iOS bundle (HTTP 200, 13.8 MB), and a connected simulator registers two pages on /json/list — i.e. the device socket this wraps still works normally.

What I could not verify, and why. I was unable to run this repo's own test suite locally: yarn install on main fails on this machine with

error react-native@1000.0.0: The engine "node" is incompatible with this module.
Expected version "^22.13.0 || ^24.3.0 || >= 26.0.0". Got "22.12.0"

So CI will be the first run of the added test (InspectorProxySocketErrors-test.js). It follows the conventions of the neighbouring inspector-proxy tests (withServerForEachTest, createDeviceMock, wait-for-expect) and asserts that after one connection is broken at the protocol level, the proxy is still serving and a healthy device is still listed. I'm happy to iterate on it if CI disagrees, or to drop it and land the fix alone if you'd prefer a separate test PR.

InspectorProxy never attached an 'error' listener to the WebSocket
connections it accepts. In Node an 'error' event with no listener
throws, so a socket-level failure on a single connection took down the
whole dev server rather than just that connection.

Both #createDeviceConnectionWSServer and #createDebuggerConnectionWSServer
listened for 'message' and 'close' only. They now also listen for
'error', attached synchronously before the first await — these handlers
are async, so an error arriving while suspended would otherwise still be
unhandled.

Reported in react#57793, where a normally-connected iOS simulator tripped the
maxFragments cap in the vendored ws and exited Metro with
'RangeError: Too many message fragments' (close code 1008). That cap is
deliberate hardening and is untouched here; what is fixed is that
tripping it was fatal to the process instead of to the connection.
@meta-cla

meta-cla Bot commented Aug 2, 2026

Copy link
Copy Markdown

Hi @Milad74f!

Thank you for your pull request and welcome to our community.

Action Required

In order to merge any pull request (code, docs, etc.), we require contributors to sign our Contributor License Agreement, and we don't seem to have one on file for you.

Process

In order for us to review and merge your suggested changes, please sign at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need to sign the corporate CLA.

Once the CLA is signed, our tooling will perform checks and validations. Afterwards, the pull request will be tagged with CLA signed. The tagging process may take up to 1 hour after signing. Please give it that time before contacting us about it.

If you have received this in error or have any questions, please contact us at cla@meta.com. Thanks!

@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown

Caution

Missing Changelog

Please add a Changelog to your PR description. See Changelog format

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dev server exits on any WebSocket error: InspectorProxy never listens for 'error'

1 participant