UNPKG

@nestjs/platform-ws

Version:

Nest - modern, fast, powerful node.js web framework (@platform-ws)

168 lines (167 loc) 7.07 kB
"use strict"; 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;