Skip to content

Commit 35a2306

Browse files
veeceeyclaude
andcommitted
fix(websockets): detect native ws clients before calling emit
The emitMessage() method checked for client.emit first, but native WebSocket clients from the ws package inherit from EventEmitter and also have an emit method that only dispatches events locally. This caused WsException errors to be emitted as local events instead of being sent over the wire, resulting in the e2e test timing out. Added an isNativeWebSocket() check that looks for a numeric readyState property (per the WebSocket spec) to reliably distinguish native WS clients from Socket.IO sockets. Also fixed prettier formatting on the emitMessage generic signature, and updated test mocks to include readyState on the native WS client stub. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6ae8bbd commit 35a2306

File tree

2 files changed

+28
-9
lines changed

2 files changed

+28
-9
lines changed

packages/websockets/exceptions/base-ws-exception-filter.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
type WsExceptionFilter,
66
} from '@nestjs/common';
77
import { WsException } from '../errors/ws-exception.js';
8-
import { isFunction, isObject } from '@nestjs/common/internal';
8+
import { isFunction, isNumber, isObject } from '@nestjs/common/internal';
99
import { MESSAGES } from '@nestjs/core/internal';
1010

1111
export interface ErrorPayload<Cause = { pattern: string; data: unknown }> {
@@ -120,19 +120,38 @@ export class BaseWsExceptionFilter<
120120
/**
121121
* Sends an error message to the client. Supports both Socket.IO clients
122122
* (which use `emit`) and native WebSocket clients (which use `send`).
123+
*
124+
* Native WebSocket clients (e.g. from the `ws` package) inherit from
125+
* EventEmitter and therefore also have an `emit` method, but that method
126+
* only dispatches events locally. To distinguish native WebSocket clients
127+
* from Socket.IO clients, we check for a numeric `readyState` property
128+
* (part of the WebSocket specification) before falling back to `emit`.
123129
*/
124-
protected emitMessage<
125-
TClient extends { emit?: Function; send?: Function },
126-
>(client: TClient, event: string, payload: unknown): void {
127-
if (isFunction(client.emit)) {
128-
client.emit(event, payload);
129-
} else if (isFunction(client.send)) {
130+
protected emitMessage<TClient extends { emit?: Function; send?: Function }>(
131+
client: TClient,
132+
event: string,
133+
payload: unknown,
134+
): void {
135+
if (this.isNativeWebSocket(client)) {
130136
client.send(
131137
JSON.stringify({
132138
event,
133139
data: payload,
134140
}),
135141
);
142+
} else if (isFunction(client.emit)) {
143+
client.emit(event, payload);
136144
}
137145
}
146+
147+
/**
148+
* Determines whether the given client is a native WebSocket (e.g. from the
149+
* `ws` package) as opposed to a Socket.IO socket. Native WebSocket objects
150+
* expose a numeric `readyState` property per the WebSocket specification.
151+
*/
152+
private isNativeWebSocket(
153+
client: Record<string, any>,
154+
): client is { send: Function; readyState: number } {
155+
return isNumber(client.readyState) && isFunction(client.send);
156+
}
138157
}

packages/websockets/test/exceptions/ws-exceptions-handler.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,13 +100,13 @@ describe('WsExceptionsHandler', () => {
100100

101101
describe('when client uses "send" instead of "emit" (native WebSocket)', () => {
102102
let sendStub: sinon.SinonStub;
103-
let wsClient: { send: sinon.SinonStub };
103+
let wsClient: { send: sinon.SinonStub; readyState: number };
104104
let wsExecutionContextHost: ExecutionContextHost;
105105

106106
beforeEach(() => {
107107
handler = new WsExceptionsHandler();
108108
sendStub = sinon.stub();
109-
wsClient = { send: sendStub };
109+
wsClient = { send: sendStub, readyState: 1 };
110110
wsExecutionContextHost = new ExecutionContextHost([
111111
wsClient,
112112
data,

0 commit comments

Comments
 (0)