@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
JavaScript
;
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
});