{
  "version": 3,
  "sources": ["../src/WebSocketTransport.ts"],
  "sourcesContent": ["import http from 'http';\nimport { URL } from 'url';\nimport WebSocket, { type ServerOptions, WebSocketServer } from 'ws';\n\nimport { matchMaker, Protocol, Transport, debugAndPrintError, debugConnection, getBearerToken } from '@colyseus/core';\nimport { WebSocketClient } from './WebSocketClient.js';\n\nfunction noop() {/* tslint:disable:no-empty */ }\nfunction heartbeat() { this.pingCount = 0; }\n\ntype RawWebSocketClient = WebSocket & { pingCount: number };\n\nexport interface TransportOptions extends ServerOptions {\n  pingInterval?: number;\n  pingMaxRetries?: number;\n}\n\nexport class WebSocketTransport extends Transport {\n  protected wss: WebSocketServer;\n\n  protected pingInterval: NodeJS.Timeout;\n  protected pingIntervalMS: number;\n  protected pingMaxRetries: number;\n\n  private _originalSend: typeof WebSocketClient.prototype.raw | null = null;\n\n  constructor(options: TransportOptions = {}) {\n    super();\n\n    if (options.maxPayload === undefined) {\n      options.maxPayload = 4 * 1024; // 4Kb\n    }\n\n    // disable per-message deflate by default\n    if (options.perMessageDeflate === undefined) {\n      options.perMessageDeflate = false;\n    }\n\n    this.pingIntervalMS = (options.pingInterval !== undefined)\n      ? options.pingInterval\n      : 3000;\n\n    this.pingMaxRetries = (options.pingMaxRetries !== undefined)\n      ? options.pingMaxRetries\n      : 2;\n\n    // create server by default\n    if (!options.server && !options.noServer) {\n      options.server = http.createServer();\n    }\n\n    this.wss = new WebSocketServer(options);\n    this.wss.on('connection', this.onConnection);\n\n    // this is required to allow the ECONNRESET error to trigger on the `server` instance.\n    this.wss.on('error', (err) => debugAndPrintError(err));\n\n    this.server = options.server;\n\n    if (this.pingIntervalMS > 0 && this.pingMaxRetries > 0) {\n      this.server.on('listening', () =>\n        this.autoTerminateUnresponsiveClients(this.pingIntervalMS, this.pingMaxRetries));\n\n      this.server.on('close', () =>\n        clearInterval(this.pingInterval));\n    }\n  }\n\n  public listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void) {\n    this.server.listen(port, hostname, backlog, listeningListener);\n    return this;\n  }\n\n  public shutdown() {\n    this.wss.close();\n    this.server.close();\n  }\n\n  public simulateLatency(milliseconds: number) {\n    if (this._originalSend == null) {\n      this._originalSend = WebSocketClient.prototype.raw;\n    }\n\n    const originalSend = this._originalSend;\n\n    WebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalSend : function (...args: any[]) {\n      // copy buffer\n      let [buf, ...rest] = args;\n      buf = Array.from(buf);\n      setTimeout(() => originalSend.apply(this, [buf, ...rest]), milliseconds);\n    };\n  }\n\n  protected autoTerminateUnresponsiveClients(pingInterval: number, pingMaxRetries: number) {\n    // interval to detect broken connections\n    this.pingInterval = setInterval(() => {\n      this.wss.clients.forEach((client: WebSocket) => {\n        //\n        // if client hasn't responded after the interval, terminate its connection.\n        //\n        if ((client as RawWebSocketClient).pingCount >= pingMaxRetries) {\n          // debugConnection(`terminating unresponsive client ${client.sessionId}`);\n          debugConnection(`terminating unresponsive client`);\n          return client.terminate();\n        }\n\n        (client as RawWebSocketClient).pingCount++;\n        client.ping(noop);\n      });\n    }, pingInterval);\n  }\n\n  protected async onConnection(rawClient: RawWebSocketClient, req: http.IncomingMessage) {\n    // prevent server crashes if a single client had unexpected error\n    rawClient.on('error', (err) => debugAndPrintError(err.message + '\\n' + err.stack));\n    rawClient.on('pong', heartbeat);\n\n    // compatibility with ws / uws\n    const parsedURL = new URL(`ws://server/${req.url}`);\n\n    const sessionId = parsedURL.searchParams.get(\"sessionId\");\n    const processAndRoomId = parsedURL.pathname.match(/\\/[a-zA-Z0-9_\\-]+\\/([a-zA-Z0-9_\\-]+)$/);\n    const roomId = processAndRoomId && processAndRoomId[1];\n\n    const room = matchMaker.getLocalRoomById(roomId);\n\n    // set client id\n    rawClient.pingCount = 0;\n\n    const client = new WebSocketClient(sessionId, rawClient);\n\n    //\n    // TODO: DRY code below with all transports\n    //\n\n    try {\n      if (!room || !room.hasReservedSeat(sessionId, parsedURL.searchParams.get(\"reconnectionToken\"))) {\n        throw new Error('seat reservation expired.');\n      }\n\n      await room._onJoin(client, {\n        headers: req.headers,\n        token: parsedURL.searchParams.get(\"_authToken\") ?? getBearerToken(req.headers.authorization),\n        ip: req.headers['x-real-ip'] ?? req.headers['x-forwarded-for'] ?? req.socket.remoteAddress,\n      });\n\n    } catch (e) {\n      debugAndPrintError(e);\n\n      // send error code to client then terminate\n      client.error(e.code, e.message, () =>\n        rawClient.close(Protocol.WS_CLOSE_WITH_ERROR));\n    }\n  }\n\n}\n"],
  "mappings": "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",
  "names": []
}
