no-floating-network-connections
Require browser network connection handles to be retained so they can be closed.
Rule catalog ID: R009
Targeted pattern scopeโ
This rule targets browser connection constructors with explicit .close()
lifecycle APIs:
WebSocketEventSourcewindow.WebSocket,self.WebSocket, andglobalThis.WebSocketwindow.EventSource,self.EventSource, andglobalThis.EventSource
The rule reports connection instances that are immediately discarded or chained
directly into a method call other than .close(). In both cases there is no
remaining connection handle available for teardown.
What this rule reportsโ
The rule reports:
- standalone connection construction such as
new WebSocket(url); - voided connection construction such as
void new EventSource(url); - immediate send or listener chains such as
new WebSocket(url).send(message); - immediate
EventSourcelistener registration on a discarded instance
It intentionally does not require same-function close() calls. Ownership can
be transferred to a component instance, connection manager, returned value, or
longer-lived runtime owner.
Why this rule existsโ
WebSocket and EventSource create long-lived network connections. If the
handle is not retained, code cannot reliably call .close(), remove listeners,
or coordinate reconnect and shutdown behavior. Discarding the handle makes the
connection lifecycle implicit and usually leaks work until the page or runtime
exits.
Incorrectโ
new WebSocket("wss://example.com/socket");
void new EventSource("/events");
new WebSocket(url).send("hello");
new EventSource("/events").addEventListener("message", onMessage);
Correctโ
const socket = new WebSocket("wss://example.com/socket");
socket.addEventListener("message", onMessage);
socket.close();
const source = new EventSource("/events");
source.addEventListener("message", onMessage);
source.close();
return new WebSocket(url);
registerConnection(new EventSource(url));
Behavior and migration notesโ
Store the connection handle in the owner that will close it. For UI code, that is usually the component or route lifecycle. For shared libraries, returning the connection or passing it to a connection manager makes the ownership contract explicit.
This rule does not autofix. Introducing a variable without a matching close path would hide the lifecycle problem instead of solving it.
ESLint flat config exampleโ
import runtimeCleanup from "eslint-plugin-runtime-cleanup";
export default [
runtimeCleanup.configs.recommended,
];
When not to use itโ
Do not enable this rule for code that intentionally creates page-lifetime connections and does not need explicit teardown. Prefer a narrow disable comment with a reason when a connection is meant to live for the whole runtime.