@colyseus/ws-transport
Version:
```typescript import { Server } from "@colyseus/core"; import { WebSocketTransport } from "@colyseus/ws-transport";
95 lines (94 loc) • 3.7 kB
JavaScript
// 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
};