UNPKG

@colyseus/ws-transport

Version:

```typescript import { Server } from "@colyseus/core"; import { WebSocketTransport } from "@colyseus/ws-transport";

95 lines (94 loc) 3.7 kB
// packages/transport/ws-transport/src/WebSocketTransport.ts import http from "http"; import { URL } from "url"; import { WebSocketServer } from "ws"; import { matchMaker, Protocol, Transport, debugAndPrintError, debugConnection, getBearerToken } from "@colyseus/core"; import { WebSocketClient } from "./WebSocketClient.mjs"; function noop() { } function heartbeat() { this.pingCount = 0; } var WebSocketTransport = class extends Transport { constructor(options = {}) { super(); this._originalSend = null; if (options.maxPayload === void 0) { options.maxPayload = 4 * 1024; } if (options.perMessageDeflate === void 0) { options.perMessageDeflate = false; } this.pingIntervalMS = options.pingInterval !== void 0 ? options.pingInterval : 3e3; this.pingMaxRetries = options.pingMaxRetries !== void 0 ? options.pingMaxRetries : 2; if (!options.server && !options.noServer) { options.server = http.createServer(); } this.wss = new WebSocketServer(options); this.wss.on("connection", this.onConnection); this.wss.on("error", (err) => debugAndPrintError(err)); this.server = options.server; if (this.pingIntervalMS > 0 && this.pingMaxRetries > 0) { this.server.on("listening", () => this.autoTerminateUnresponsiveClients(this.pingIntervalMS, this.pingMaxRetries)); this.server.on("close", () => clearInterval(this.pingInterval)); } } listen(port, hostname, backlog, listeningListener) { this.server.listen(port, hostname, backlog, listeningListener); return this; } shutdown() { this.wss.close(); this.server.close(); } simulateLatency(milliseconds) { if (this._originalSend == null) { this._originalSend = WebSocketClient.prototype.raw; } const originalSend = this._originalSend; WebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalSend : function(...args) { let [buf, ...rest] = args; buf = Array.from(buf); setTimeout(() => originalSend.apply(this, [buf, ...rest]), milliseconds); }; } autoTerminateUnresponsiveClients(pingInterval, pingMaxRetries) { this.pingInterval = setInterval(() => { this.wss.clients.forEach((client) => { if (client.pingCount >= pingMaxRetries) { debugConnection(`terminating unresponsive client`); return client.terminate(); } client.pingCount++; client.ping(noop); }); }, pingInterval); } async onConnection(rawClient, req) { rawClient.on("error", (err) => debugAndPrintError(err.message + "\n" + err.stack)); rawClient.on("pong", heartbeat); const parsedURL = new URL(`ws://server/${req.url}`); const sessionId = parsedURL.searchParams.get("sessionId"); const processAndRoomId = parsedURL.pathname.match(/\/[a-zA-Z0-9_\-]+\/([a-zA-Z0-9_\-]+)$/); const roomId = processAndRoomId && processAndRoomId[1]; const room = matchMaker.getLocalRoomById(roomId); rawClient.pingCount = 0; const client = new WebSocketClient(sessionId, rawClient); try { if (!room || !room.hasReservedSeat(sessionId, parsedURL.searchParams.get("reconnectionToken"))) { throw new Error("seat reservation expired."); } await room._onJoin(client, { headers: req.headers, token: parsedURL.searchParams.get("_authToken") ?? getBearerToken(req.headers.authorization), ip: req.headers["x-real-ip"] ?? req.headers["x-forwarded-for"] ?? req.socket.remoteAddress }); } catch (e) { debugAndPrintError(e); client.error(e.code, e.message, () => rawClient.close(Protocol.WS_CLOSE_WITH_ERROR)); } } }; export { WebSocketTransport };