"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.uServer = void 0; const debug_1 = require("debug"); const server_1 = require("./server"); const transports_uws_1 = require("./transports-uws"); const debug = (0, debug_1.default)("engine:uws"); class uServer extends server_1.BaseServer { init() { } cleanup() { } /** * Prepares a request by processing the query string. * * @api private */ prepare(req, res) { req.method = req.getMethod().toUpperCase(); const params = new URLSearchParams(req.getQuery()); req._query = Object.fromEntries(params.entries()); req.headers = {}; req.forEach((key, value) => { req.headers[key] = value; }); req.connection = { remoteAddress: Buffer.from(res.getRemoteAddressAsText()).toString() }; res.onAborted(() => { debug("response has been aborted"); }); } createTransport(transportName, req) { return new transports_uws_1.default[transportName](req); } /** * Attach the engine to a µWebSockets.js server * @param app * @param options */ attach(app /* : TemplatedApp */, options = {}) { const path = (options.path || "/engine.io").replace(/\/$/, "") + "/"; app .any(path, this.handleRequest.bind(this)) // .ws(path, { compression: options.compression, idleTimeout: options.idleTimeout, maxBackpressure: options.maxBackpressure, maxPayloadLength: this.opts.maxHttpBufferSize, upgrade: this.handleUpgrade.bind(this), open: ws => { ws.transport.socket = ws; ws.transport.writable = true; ws.transport.emit("drain"); }, message: (ws, message, isBinary) => { ws.transport.onData(isBinary ? message : Buffer.from(message).toString()); }, close: (ws, code, message) => { ws.transport.onClose(code, message); } }); } handleRequest(res, req) { debug('handling "%s" http request "%s"', req.getMethod(), req.getUrl()); this.prepare(req, res); req.res = res; const callback = (errorCode, errorContext) => { if (errorCode !== undefined) { this.emit("connection_error", { req, code: errorCode, message: server_1.Server.errorMessages[errorCode], context: errorContext }); this.abortRequest(req.res, errorCode, errorContext); return; } if (req._query.sid) { debug("setting new request for existing client"); this.clients[req._query.sid].transport.onRequest(req); } else { const closeConnection = (errorCode, errorContext) => this.abortRequest(res, errorCode, errorContext); this.handshake(req._query.transport, req, closeConnection); } }; if (this.corsMiddleware) { // needed to buffer headers until the status is computed req.res = new ResponseWrapper(res); this.corsMiddleware.call(null, req, req.res, () => { this.verify(req, false, callback); }); } else { this.verify(req, false, callback); } } handleUpgrade(res, req, context) { debug("on upgrade"); this.prepare(req, res); // @ts-ignore req.res = res; this.verify(req, true, async (errorCode, errorContext) => { if (errorCode) { this.emit("connection_error", { req, code: errorCode, message: server_1.Server.errorMessages[errorCode], context: errorContext }); this.abortRequest(res, errorCode, errorContext); return; } const id = req._query.sid; let transport; if (id) { const client = this.clients[id]; if (!client) { debug("upgrade attempt for closed client"); res.close(); } else if (client.upgrading) { debug("transport has already been trying to upgrade"); res.close(); } else if (client.upgraded) { debug("transport had already been upgraded"); res.close(); } else { debug("upgrading existing transport"); transport = this.createTransport(req._query.transport, req); client.maybeUpgrade(transport); } } else { transport = await this.handshake(req._query.transport, req, (errorCode, errorContext) => this.abortRequest(res, errorCode, errorContext)); if (!transport) { return; } } res.upgrade({ transport }, req.getHeader("sec-websocket-key"), req.getHeader("sec-websocket-protocol"), req.getHeader("sec-websocket-extensions"), context); }); } abortRequest(res, errorCode, errorContext) { const statusCode = errorCode === server_1.Server.errors.FORBIDDEN ? "403 Forbidden" : "400 Bad Request"; const message = errorContext && errorContext.message ? errorContext.message : server_1.Server.errorMessages[errorCode]; res.writeStatus(statusCode); res.writeHeader("Content-Type", "application/json"); res.end(JSON.stringify({ code: errorCode, message })); } } exports.uServer = uServer; class ResponseWrapper { constructor(res) { this.res = res; this.statusWritten = false; this.headers = []; } set statusCode(status) { this.writeStatus(status === 200 ? "200 OK" : "204 No Content"); } setHeader(key, value) { this.writeHeader(key, value); } // needed by vary: https://github.com/jshttp/vary/blob/5d725d059b3871025cf753e9dfa08924d0bcfa8f/index.js#L134 getHeader() { } writeStatus(status) { this.res.writeStatus(status); this.statusWritten = true; this.writeBufferedHeaders(); } writeHeader(key, value) { if (key === "Content-Length") { // the content length is automatically added by uWebSockets.js return; } if (this.statusWritten) { this.res.writeHeader(key, value); } else { this.headers.push([key, value]); } } writeBufferedHeaders() { this.headers.forEach(([key, value]) => { this.res.writeHeader(key, value); }); } end(data) { if (!this.statusWritten) { // status will be inferred as "200 OK" this.writeBufferedHeaders(); } this.res.end(data); } onData(fn) { this.res.onData(fn); } onAborted(fn) { this.res.onAborted(fn); } }