@nestjs/platform-ws 12.0.1 → 12.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters/ws-adapter.d.ts +1 -0
- package/adapters/ws-adapter.js +52 -13
- package/package.json +2 -2
package/adapters/ws-adapter.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare class WsAdapter extends AbstractWsAdapter {
|
|
|
19
19
|
protected readonly logger: Logger;
|
|
20
20
|
protected readonly httpServersRegistry: Map<number, any>;
|
|
21
21
|
protected readonly wsServersRegistry: Map<number, WsServerRegistryEntry>;
|
|
22
|
+
private readonly upgradeListenersRegistry;
|
|
22
23
|
protected messageParser: WsMessageParser;
|
|
23
24
|
constructor(appOrHttpServer?: INestApplicationContext | object, options?: WsAdapterOptions);
|
|
24
25
|
create(port: number, options?: Record<string, any> & {
|
package/adapters/ws-adapter.js
CHANGED
|
@@ -3,7 +3,7 @@ import { AbstractWsAdapter } from '@nestjs/websockets';
|
|
|
3
3
|
import * as http from 'http';
|
|
4
4
|
import { createRequire } from 'module';
|
|
5
5
|
import { EMPTY, fromEvent } from 'rxjs';
|
|
6
|
-
import { filter, first, mergeMap, share, takeUntil } from 'rxjs/operators';
|
|
6
|
+
import { catchError, filter, first, mergeMap, share, takeUntil, } from 'rxjs/operators';
|
|
7
7
|
import { loadPackageSync, isNil, normalizePath } from '@nestjs/common/internal';
|
|
8
8
|
import { CLOSE_EVENT, CONNECTION_EVENT, ERROR_EVENT, } from '@nestjs/websockets/internal';
|
|
9
9
|
let wsPackage = {};
|
|
@@ -15,6 +15,11 @@ var READY_STATE;
|
|
|
15
15
|
READY_STATE[READY_STATE["CLOSED_STATE"] = 3] = "CLOSED_STATE";
|
|
16
16
|
})(READY_STATE || (READY_STATE = {}));
|
|
17
17
|
const UNDERLYING_HTTP_SERVER_PORT = 0;
|
|
18
|
+
// Kept out of the class so that an adapter can be compared against it to tell
|
|
19
|
+
// whether the parser in use is still the built-in one.
|
|
20
|
+
const defaultMessageParser = (data) => {
|
|
21
|
+
return JSON.parse(data.toString());
|
|
22
|
+
};
|
|
18
23
|
/**
|
|
19
24
|
* @publicApi
|
|
20
25
|
*/
|
|
@@ -22,9 +27,8 @@ export class WsAdapter extends AbstractWsAdapter {
|
|
|
22
27
|
logger = new Logger(WsAdapter.name);
|
|
23
28
|
httpServersRegistry = new Map();
|
|
24
29
|
wsServersRegistry = new Map();
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
};
|
|
30
|
+
upgradeListenersRegistry = new Map();
|
|
31
|
+
messageParser = defaultMessageParser;
|
|
28
32
|
constructor(appOrHttpServer, options) {
|
|
29
33
|
super(appOrHttpServer);
|
|
30
34
|
wsPackage = loadPackageSync('ws', 'WsAdapter', () => createRequire(import.meta.url)('ws'));
|
|
@@ -80,7 +84,14 @@ export class WsAdapter extends AbstractWsAdapter {
|
|
|
80
84
|
const handlersMap = new Map();
|
|
81
85
|
handlers.forEach(handler => handlersMap.set(handler.message, handler));
|
|
82
86
|
const close$ = fromEvent(client, CLOSE_EVENT).pipe(share(), first());
|
|
83
|
-
const source$ = fromEvent(client, 'message').pipe(mergeMap(data => this.bindMessageHandler(data, handlersMap, transform).pipe(filter(result => !isNil(result))
|
|
87
|
+
const source$ = fromEvent(client, 'message').pipe(mergeMap(data => this.bindMessageHandler(data, handlersMap, transform).pipe(filter(result => !isNil(result)),
|
|
88
|
+
// a handler that rejects through an error filter that rethrows
|
|
89
|
+
// would error the client's stream and silence every later message;
|
|
90
|
+
// that rethrow is an app bug, so it gets logged, not dropped silently
|
|
91
|
+
catchError(err => {
|
|
92
|
+
this.logger.error(err);
|
|
93
|
+
return EMPTY;
|
|
94
|
+
}))), takeUntil(close$));
|
|
84
95
|
const onMessage = (response) => {
|
|
85
96
|
if (client.readyState !== READY_STATE.OPEN_STATE) {
|
|
86
97
|
return;
|
|
@@ -90,16 +101,37 @@ export class WsAdapter extends AbstractWsAdapter {
|
|
|
90
101
|
source$.subscribe(onMessage);
|
|
91
102
|
}
|
|
92
103
|
bindMessageHandler(buffer, handlersMap, transform) {
|
|
104
|
+
let message;
|
|
93
105
|
try {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
106
|
+
message = this.messageParser(buffer.data);
|
|
107
|
+
}
|
|
108
|
+
catch (err) {
|
|
109
|
+
// A custom parser that throws is an application bug, and swallowing it
|
|
110
|
+
// leaves no trace of it anywhere. The default parser, on the other hand,
|
|
111
|
+
// throws on client-controlled input, so reporting that one would turn a
|
|
112
|
+
// public socket into a log flood target. A custom parser that wants a
|
|
113
|
+
// frame dropped silently can return nothing, which is handled below.
|
|
114
|
+
if (this.messageParser !== defaultMessageParser) {
|
|
115
|
+
this.logger.error(err);
|
|
97
116
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
117
|
+
return EMPTY;
|
|
118
|
+
}
|
|
119
|
+
if (!message) {
|
|
120
|
+
return EMPTY;
|
|
121
|
+
}
|
|
122
|
+
const messageHandler = handlersMap.get(message.event);
|
|
123
|
+
if (!messageHandler) {
|
|
124
|
+
// An unrecognised event is an expected condition on a public socket,
|
|
125
|
+
// not an error. It used to reach the catch below as a TypeError.
|
|
126
|
+
return EMPTY;
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
return transform(messageHandler.callback(message.data, message.event));
|
|
101
130
|
}
|
|
102
131
|
catch {
|
|
132
|
+
// Kept deliberately: a synchronous throw here would otherwise propagate
|
|
133
|
+
// through the mergeMap in bindMessageHandlers and tear down the client's
|
|
134
|
+
// source stream, silencing every subsequent message from that client.
|
|
103
135
|
return EMPTY;
|
|
104
136
|
}
|
|
105
137
|
}
|
|
@@ -128,9 +160,14 @@ export class WsAdapter extends AbstractWsAdapter {
|
|
|
128
160
|
const closeEventSignals = Array.from(this.httpServersRegistry)
|
|
129
161
|
.filter(([port]) => port !== UNDERLYING_HTTP_SERVER_PORT)
|
|
130
162
|
.map(([_, server]) => new Promise(resolve => server.close(resolve)));
|
|
163
|
+
// A server passed in by the caller outlives this adapter, so the listener
|
|
164
|
+
// added to it has to be taken off explicitly. Servers created here are
|
|
165
|
+
// closed above and take their listeners with them.
|
|
166
|
+
this.upgradeListenersRegistry.forEach((listener, port) => this.httpServersRegistry.get(port)?.off('upgrade', listener));
|
|
131
167
|
await Promise.all(closeEventSignals);
|
|
132
168
|
this.httpServersRegistry.clear();
|
|
133
169
|
this.wsServersRegistry.clear();
|
|
170
|
+
this.upgradeListenersRegistry.clear();
|
|
134
171
|
}
|
|
135
172
|
setMessageParser(parser) {
|
|
136
173
|
this.messageParser = parser;
|
|
@@ -140,7 +177,7 @@ export class WsAdapter extends AbstractWsAdapter {
|
|
|
140
177
|
return;
|
|
141
178
|
}
|
|
142
179
|
this.httpServersRegistry.set(port, httpServer);
|
|
143
|
-
|
|
180
|
+
const upgradeListener = (request, socket, head) => {
|
|
144
181
|
try {
|
|
145
182
|
const baseUrl = 'ws://' + request.headers.host + '/';
|
|
146
183
|
const pathname = new URL(request.url, baseUrl).pathname;
|
|
@@ -162,7 +199,9 @@ export class WsAdapter extends AbstractWsAdapter {
|
|
|
162
199
|
catch (err) {
|
|
163
200
|
socket.end(`HTTP/1.1 400\r\n${err.message}`);
|
|
164
201
|
}
|
|
165
|
-
}
|
|
202
|
+
};
|
|
203
|
+
httpServer.on('upgrade', upgradeListener);
|
|
204
|
+
this.upgradeListenersRegistry.set(port, upgradeListener);
|
|
166
205
|
return httpServer;
|
|
167
206
|
}
|
|
168
207
|
addWsServerToRegistry(wsServer, port, path) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nestjs/platform-ws",
|
|
3
|
-
"version": "12.0.
|
|
3
|
+
"version": "12.0.3",
|
|
4
4
|
"description": "Nest - modern, fast, powerful node.js web framework (@platform-ws)",
|
|
5
5
|
"author": "Kamil Mysliwiec",
|
|
6
6
|
"license": "MIT",
|
|
@@ -33,5 +33,5 @@
|
|
|
33
33
|
"@nestjs/websockets": "^12.0.0",
|
|
34
34
|
"rxjs": "^7.1.0"
|
|
35
35
|
},
|
|
36
|
-
"gitHead": "
|
|
36
|
+
"gitHead": "14151790e1abe870abc3bbf00f88786fc3ebe509"
|
|
37
37
|
}
|