UNPKG

@colyseus/uwebsockets-transport

Version:

<div align="center"> <a href="https://github.com/colyseus/colyseus"> <img src="media/logo.svg?raw=true" width="60%" height="300" /> </a> <br> <br> <a href="https://npmjs.com/package/colyseus"> <img src="https://img.shields.io/npm/dm/coly

318 lines (316 loc) 12.1 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // packages/transport/uwebsockets-transport/src/uWebSocketsTransport.ts var uWebSocketsTransport_exports = {}; __export(uWebSocketsTransport_exports, { uWebSocketsTransport: () => uWebSocketsTransport }); module.exports = __toCommonJS(uWebSocketsTransport_exports); var import_querystring = __toESM(require("querystring"), 1); var import_uWebSockets = __toESM(require("uWebSockets.js"), 1); var import_core = require("@colyseus/core"); var import_uWebSocketClient = require("./uWebSocketClient.cjs"); var import_core2 = require("@colyseus/core"); var uWebSocketsExpress = new import_core2.Deferred(); var uWebSocketsExpressModule = void 0; import("uwebsockets-express").then((module2) => uWebSocketsExpress.resolve(module2)).catch((error) => uWebSocketsExpress.reject(error)); var uWebSocketsTransport = class extends import_core.Transport { constructor(options = {}, appOptions = {}) { super(); this.clients = []; this.clientWrappers = /* @__PURE__ */ new WeakMap(); this._originalRawSend = null; this.app = appOptions.cert_file_name && appOptions.key_file_name ? import_uWebSockets.default.SSLApp(appOptions) : import_uWebSockets.default.App(appOptions); if (options.maxBackpressure === void 0) { options.maxBackpressure = 1024 * 1024; } if (options.compression === void 0) { options.compression = import_uWebSockets.default.DISABLED; } if (options.maxPayloadLength === void 0) { options.maxPayloadLength = 4 * 1024; } if (options.sendPingsAutomatically === void 0) { options.sendPingsAutomatically = true; } this.app.ws("/*", { ...options, upgrade: (res, req, context) => { const headers = {}; req.forEach((key, value) => headers[key] = value); const searchParams = import_querystring.default.parse(req.getQuery()); res.upgrade( { url: req.getUrl(), searchParams, context: { token: searchParams._authToken ?? (0, import_core.getBearerToken)(req.getHeader("authorization")), headers, ip: headers["x-real-ip"] ?? headers["x-forwarded-for"] ?? Buffer.from(res.getRemoteAddressAsText()).toString() } }, req.getHeader("sec-websocket-key"), req.getHeader("sec-websocket-protocol"), req.getHeader("sec-websocket-extensions"), context ); }, open: async (ws) => { await this.onConnection(ws); }, // pong: (ws: RawWebSocketClient) => { // ws.pingCount = 0; // }, close: (ws, code, message) => { (0, import_core.spliceOne)(this.clients, this.clients.indexOf(ws)); const clientWrapper = this.clientWrappers.get(ws); if (clientWrapper) { this.clientWrappers.delete(ws); clientWrapper.emit("close", code); } }, message: (ws, message, isBinary) => { this.clientWrappers.get(ws)?.emit("message", Buffer.from(message)); } }); } getExpressApp() { if (!this._expressApp) { return new Promise(async (resolve, reject) => { try { const module2 = await uWebSocketsExpress; uWebSocketsExpressModule = module2; const originalAny = this.app.any; this.app.any = (() => this.app); this._expressApp = module2.default(this.app); this.app.any = originalAny; resolve(this._expressApp); } catch (error) { reject(error); console.warn(""); console.warn("\u274C Error: could not initialize express."); console.warn(""); console.warn(" For Express v5, use:"); console.warn(" \u{1F449} npm install --save uwebsockets-express@^2.0.1"); console.warn(""); console.warn(" For Express v4, use:"); console.warn(" \u{1F449} npm install --save uwebsockets-express@^1.4.1"); console.warn(""); process.exit(); } }); } return this._expressApp; } bindRouter(router) { const getCorsHeaders = (requestHeaders) => { return Object.assign( {}, import_core.matchMaker.controller.DEFAULT_CORS_HEADERS, import_core.matchMaker.controller.getCorsHeaders(requestHeaders) ); }; const writeCorsHeaders = (res, requestHeaders) => { if (res.aborted) { return; } const headers = getCorsHeaders(requestHeaders); for (const header in headers) { res.writeHeader(header, headers[header].toString()); } return true; }; this.app.options("/*", (res, req) => { res.onAborted(() => res.aborted = true); const reqHeaders = new Headers(); req.forEach((key, value) => reqHeaders.set(key, value)); res.cork(() => { res.writeStatus("204 No Content"); writeCorsHeaders(res, reqHeaders); res.end(); }); }); this.app.any("/*", async (res, req) => { const abortController = new AbortController(); res.onAborted(() => { abortController.abort(); res.aborted = true; }); const headers = new Headers(); req.forEach((key, value) => headers.set(key, value)); const method = req.getMethod().toUpperCase(); const url = req.getUrl(); const query = req.getQuery(); const remoteAddress = res.getRemoteAddressAsText(); if (router.findRoute(method, url) !== void 0) { const requestInit = { method, referrer: headers.get("referer") || void 0, keepalive: headers.get("keep-alive") === "true", headers, signal: abortController.signal }; if (method !== "GET" && method !== "HEAD") { let body = void 0; await new Promise((resolve) => { res.onData((ab, isLast) => { const chunk = Buffer.from(ab); if (body === void 0) { body = Buffer.from(chunk); } else { body = Buffer.concat([body, chunk]); } if (isLast) { resolve(); } }); }); requestInit.body = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength); } const fullUrl = `http://${headers.get("host") || "localhost"}${url}${query ? `?${query}` : ""}`; const response = await router.handler(new Request(fullUrl, requestInit)); if (res.aborted) { return; } const responseBody = await response.arrayBuffer(); res.cork(() => { res.writeStatus(`${response.status} ${response.statusText}`); writeCorsHeaders(res, headers); response.headers.forEach((value, key) => { if (key.toLowerCase() !== "content-length") { res.writeHeader(key, value); } }); res.end(responseBody); }); } else if (this._expressApp) { if (res.aborted) { return; } const corsHeaders = getCorsHeaders(headers); const ereq = new uWebSocketsExpressModule.IncomingMessage(req, res, this._expressApp, { headers: Object.fromEntries(headers.entries()), method, url, query, remoteAddress }); const eres = new uWebSocketsExpressModule.ServerResponse(res, req, this._expressApp); abortController.signal.addEventListener("abort", () => { eres.finished = true; eres.writableEnded = true; }); for (const header in corsHeaders) { eres.setHeader(header, corsHeaders[header].toString()); } await ereq._readBody(); if (res.aborted) { return; } this._expressApp["handle"](ereq, eres); } }); } listen(port, hostname, backlog, listeningListener) { const callback = (listeningSocket) => { this._listeningSocket = listeningSocket; listeningListener?.(); }; if (typeof port === "string") { this.app.listen_unix(callback, port); } else { this.app.listen(port, callback); } return this; } shutdown() { if (this._listeningSocket) { import_uWebSockets.default.us_listen_socket_close(this._listeningSocket); } } simulateLatency(milliseconds) { if (this._originalRawSend == null) { this._originalRawSend = import_uWebSocketClient.uWebSocketClient.prototype.raw; } const originalRawSend = this._originalRawSend; import_uWebSocketClient.uWebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalRawSend : function(...args) { let [buf, ...rest] = args; buf = Buffer.from(buf); setTimeout(() => originalRawSend.apply(this, [buf, ...rest]), milliseconds); }; } async onConnection(rawClient) { const wrapper = new import_uWebSocketClient.uWebSocketWrapper(rawClient); this.clients.push(rawClient); this.clientWrappers.set(rawClient, wrapper); const url = rawClient.url; const searchParams = rawClient.searchParams; const sessionId = searchParams.sessionId; const processAndRoomId = url.match(/\/[a-zA-Z0-9_\-]+\/([a-zA-Z0-9_\-]+)$/); const roomId = processAndRoomId && processAndRoomId[1]; if (!sessionId && !roomId) { const timeout = setTimeout(() => { try { rawClient.close(); } catch (e) { } }, 1e3); wrapper.on("message", (_) => { try { rawClient.send(new Uint8Array([import_core.Protocol.PING]), true); } catch (e) { } }); wrapper.on("close", () => clearTimeout(timeout)); return; } const room = import_core.matchMaker.getLocalRoomById(roomId); const client = new import_uWebSocketClient.uWebSocketClient(sessionId, wrapper); const reconnectionToken = searchParams.reconnectionToken; const skipHandshake = searchParams.skipHandshake !== void 0; try { await (0, import_core.connectClientToRoom)(room, client, rawClient.context, { reconnectionToken, skipHandshake }); } catch (e) { (0, import_core.debugAndPrintError)(e); client.error(e.code, e.message, () => { try { rawClient.end(reconnectionToken ? import_core.isDevMode ? import_core.CloseCode.MAY_TRY_RECONNECT : import_core.CloseCode.FAILED_TO_RECONNECT : import_core.CloseCode.WITH_ERROR); } catch (e2) { } }); } } }; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { uWebSocketsTransport });