8000 fix: silence One-Way WebSocket error messages in React Strict Mode (#… · coder/coder@ac0cf35 · GitHub
[go: up one dir, main page]

Skip to content

Commit ac0cf35

Browse files
authored
fix: silence One-Way WebSocket error messages in React Strict Mode (#17204)
## Changes made - Updated `OneWayWebSocket` class to prevent errors from being dispatched after a connection has been manually closed. - Renamed one of the class properties for less ambiguity - Made error messages for the class constructor more specific
1 parent c418e86 commit ac0cf35

File tree

1 file changed

+30
-7
lines changed

1 file changed

+30
-7
lines changed

site/src/utils/OneWayWebSocket.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ export class OneWayWebSocket<TData = unknown>
8282
implements OneWayWebSocketApi<TData>
8383
{
8484
readonly #socket: WebSocket;
85-
readonly #messageCallbackWrappers = new Map<
85+
readonly #errorListeners = new Set<(e: Event) => void>();
86+
readonly #messageListenerWrappers = new Map<
8687
OneWayEventCallback<TData, "message">,
8788
WebSocketMessageCallback
8889
>();
@@ -98,7 +99,7 @@ export class OneWayWebSocket<TData = unknown>
9899
} = init;
99100

100101
if (!apiRoute.startsWith("/api/v2/")) {
101-
throw new Error(`API route '${apiRoute}' does not begin with a slash`);
102+
throw new Error(`API route '${apiRoute}' does not begin with '/api/v2/'`);
102103
}
103104

104105
const formattedParams =
@@ -122,6 +123,10 @@ export class OneWayWebSocket<TData = unknown>
122123
event: TEvent,
123124
callback: OneWayEventCallback<TData, TEvent>,
124125
): void {
126+
if (this.#socket.readyState === WebSocket.CLOSED) {
127+
return;
128+
}
129+
125130
// Not happy about all the type assertions, but there are some nasty
126131
// type contravariance issues if you try to resolve the function types
127132
// properly. This is actually the lesser of two evils
@@ -130,11 +135,16 @@ export class OneWayWebSocket<TData = unknown>
130135
WebSocketEventType
131136
>;
132137

133-
if (this.#messageCallbackWrappers.has(looseCallback)) {
138+
// WebSockets automatically handle de-duping callbacks, but we have to
139+
// do a separate check for the wrappers
140+
if (this.#messageListenerWrappers.has(looseCallback)) {
134141
return;
135142
}
136143
if (event !== "message") {
137144
this.#socket.addEventListener(event, looseCallback);
145+
if (event === "error") {
146+
this.#errorListeners.add(looseCallback);
147+
}
138148
return;
139149
}
140150

@@ -161,7 +171,7 @@ export class OneWayWebSocket<TData = unknown>
161171
};
162172

163173
this.#socket.addEventListener(event as "message", wrapped);
164-
this.#messageCallbackWrappers.set(looseCallback, wrapped);
174+
this.#messageListenerWrappers.set(looseCallback, wrapped);
165175
}
166176

167177
removeEventListener<TEvent extends WebSocketEventType>(
@@ -175,24 +185,37 @@ export class OneWayWebSocket<TData = unknown>
175185

176186
if (event !== "message") {
177187
this.#socket.removeEventListener(event, looseCallback);
188+
if (event === "error") {
189+
this.#errorListeners.delete(looseCallback);
190+
}
178191
return;
179192
}
180-
if (!this.#messageCallbackWrappers.has(looseCallback)) {
193+
if (!this.#messageListenerWrappers.has(looseCallback)) {
181194
return;
182195
}
183196

184-
const wrapper = this.#messageCallbackWrappers.get(looseCallback);
197+
const wrapper = this.#messageListenerWrappers.get(looseCallback);
185198
if (wrapper === undefined) {
186199
throw new Error(
187200
`Cannot unregister callback for event ${event}. This is likely an issue with the browser itself.`,
188201
);
189202
}
190203

191204
this.#socket.removeEventListener(event as "message", wrapper);
192-
this.#messageCallbackWrappers.delete(looseCallback);
205+
this.#messageListenerWrappers.delete(looseCallback);
193206
}
194207

195208
close(closeCode?: number, reason?: string): void {
209+
// Eject all error event listeners, mainly for ergonomics in React dev
210+
// mode. React's StrictMode will create additional connections to ensure
211+
// there aren't any render bugs, but manually closing a connection via a
212+
// cleanup function sometimes causes error events to get dispatched for
213+
// a connection that is no longer wired up to the UI
214+
for (const cb of this.#errorListeners) {
215+
this.#socket.removeEventListener("error", cb);
216+
this.#errorListeners.delete(cb);
217+
}
218+
196219
this.#socket.close(closeCode, reason);
197220
}
198221
}

0 commit comments

Comments
 (0)
0