@nestjs/platform-ws
Version:
Nest - modern, fast, powerful node.js web framework (@platform-ws)
168 lines (167 loc) • 7.07 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.WsAdapter = void 0;
const common_1 = require("@nestjs/common");
const load_package_util_1 = require("@nestjs/common/utils/load-package.util");
const shared_utils_1 = require("@nestjs/common/utils/shared.utils");
const websockets_1 = require("@nestjs/websockets");
const constants_1 = require("@nestjs/websockets/constants");
const http = require("http");
const rxjs_1 = require("rxjs");
const operators_1 = require("rxjs/operators");
let wsPackage = {};
var READY_STATE;
(function (READY_STATE) {
READY_STATE[READY_STATE["CONNECTING_STATE"] = 0] = "CONNECTING_STATE";
READY_STATE[READY_STATE["OPEN_STATE"] = 1] = "OPEN_STATE";
READY_STATE[READY_STATE["CLOSING_STATE"] = 2] = "CLOSING_STATE";
READY_STATE[READY_STATE["CLOSED_STATE"] = 3] = "CLOSED_STATE";
})(READY_STATE || (READY_STATE = {}));
const UNDERLYING_HTTP_SERVER_PORT = 0;
/**
* @publicApi
*/
class WsAdapter extends websockets_1.AbstractWsAdapter {
constructor(appOrHttpServer, options) {
super(appOrHttpServer);
this.logger = new common_1.Logger(WsAdapter.name);
this.httpServersRegistry = new Map();
this.wsServersRegistry = new Map();
this.messageParser = (data) => {
return JSON.parse(data.toString());
};
wsPackage = (0, load_package_util_1.loadPackage)('ws', 'WsAdapter', () => require('ws'));
if (options?.messageParser) {
this.messageParser = options.messageParser;
}
}
create(port, options) {
const { server, path, ...wsOptions } = options;
if (wsOptions?.namespace) {
const error = new Error('"WsAdapter" does not support namespaces. If you need namespaces in your project, consider using the "@nestjs/platform-socket.io" package instead.');
this.logger.error(error);
throw error;
}
if (port === UNDERLYING_HTTP_SERVER_PORT && this.httpServer) {
this.ensureHttpServerExists(port, this.httpServer);
const wsServer = this.bindErrorHandler(new wsPackage.Server({
noServer: true,
...wsOptions,
}));
this.addWsServerToRegistry(wsServer, port, path);
return wsServer;
}
if (server) {
return server;
}
if (path && port !== UNDERLYING_HTTP_SERVER_PORT) {
// Multiple servers with different paths
// sharing a single HTTP/S server running on different port
// than a regular HTTP application
const httpServer = this.ensureHttpServerExists(port);
httpServer?.listen(port);
const wsServer = this.bindErrorHandler(new wsPackage.Server({
noServer: true,
...wsOptions,
}));
this.addWsServerToRegistry(wsServer, port, path);
return wsServer;
}
const wsServer = this.bindErrorHandler(new wsPackage.Server({
port,
path,
...wsOptions,
}));
return wsServer;
}
bindMessageHandlers(client, handlers, transform) {
const handlersMap = new Map();
handlers.forEach(handler => handlersMap.set(handler.message, handler));
const close$ = (0, rxjs_1.fromEvent)(client, constants_1.CLOSE_EVENT).pipe((0, operators_1.share)(), (0, operators_1.first)());
const source$ = (0, rxjs_1.fromEvent)(client, 'message').pipe((0, operators_1.mergeMap)(data => this.bindMessageHandler(data, handlersMap, transform).pipe((0, operators_1.filter)(result => !(0, shared_utils_1.isNil)(result)))), (0, operators_1.takeUntil)(close$));
const onMessage = (response) => {
if (client.readyState !== READY_STATE.OPEN_STATE) {
return;
}
client.send(JSON.stringify(response));
};
source$.subscribe(onMessage);
}
bindMessageHandler(buffer, handlersMap, transform) {
try {
const message = this.messageParser(buffer.data);
if (!message) {
return rxjs_1.EMPTY;
}
const messageHandler = handlersMap.get(message.event);
const { callback } = messageHandler;
return transform(callback(message.data, message.event));
}
catch {
return rxjs_1.EMPTY;
}
}
bindErrorHandler(server) {
server.on(constants_1.CONNECTION_EVENT, (ws) => ws.on(constants_1.ERROR_EVENT, (err) => this.logger.error(err)));
server.on(constants_1.ERROR_EVENT, (err) => this.logger.error(err));
return server;
}
bindClientDisconnect(client, callback) {
client.on(constants_1.CLOSE_EVENT, callback);
}
async close(server) {
const closeEventSignal = new Promise((resolve, reject) => server.close((err) => (err ? reject(err) : resolve(undefined))));
for (const ws of server.clients) {
ws.terminate();
}
await closeEventSignal;
}
async dispose() {
const closeEventSignals = Array.from(this.httpServersRegistry)
.filter(([port]) => port !== UNDERLYING_HTTP_SERVER_PORT)
.map(([_, server]) => new Promise(resolve => server.close(resolve)));
await Promise.all(closeEventSignals);
this.httpServersRegistry.clear();
this.wsServersRegistry.clear();
}
setMessageParser(parser) {
this.messageParser = parser;
}
ensureHttpServerExists(port, httpServer = http.createServer()) {
if (this.httpServersRegistry.has(port)) {
return;
}
this.httpServersRegistry.set(port, httpServer);
httpServer.on('upgrade', (request, socket, head) => {
try {
const baseUrl = 'ws://' + request.headers.host + '/';
const pathname = new URL(request.url, baseUrl).pathname;
const wsServersCollection = this.wsServersRegistry.get(port);
let isRequestDelegated = false;
for (const wsServer of wsServersCollection) {
if (pathname === wsServer.path) {
wsServer.handleUpgrade(request, socket, head, (ws) => {
wsServer.emit('connection', ws, request);
});
isRequestDelegated = true;
break;
}
}
if (!isRequestDelegated) {
socket.destroy();
}
}
catch (err) {
socket.end('HTTP/1.1 400\r\n' + err.message);
}
});
return httpServer;
}
addWsServerToRegistry(wsServer, port, path) {
const entries = this.wsServersRegistry.get(port) ?? [];
entries.push(wsServer);
wsServer.path = (0, shared_utils_1.normalizePath)(path);
this.wsServersRegistry.set(port, entries);
}
}
exports.WsAdapter = WsAdapter;