{
  "version": 3,
  "sources": ["../src/BunWebSockets.ts"],
  "sourcesContent": ["// <reference types=\"bun-types\" />\n\n// \"bun-types\" is currently conflicting with \"ws\" types.\n// @ts-ignore\nimport { Server, ServerWebSocket, WebSocketHandler } from 'bun';\n\nimport { matchMaker, Protocol, Transport, debugAndPrintError, getBearerToken, CloseCode, connectClientToRoom, spliceOne, isDevMode, type Router } from '@colyseus/core';\nimport { WebSocketClient, WebSocketWrapper } from './WebSocketClient.ts';\n\nimport type { Application } from \"express\";\nimport bunExpress, { IncomingMessage, ServerResponse } from 'bun-serve-express';\n\n// Bun global is available at runtime\ndeclare const Bun: any;\n\nexport type TransportOptions = Partial<Omit<WebSocketHandler<WebSocketData>, \"message\" | \"open\" | \"drain\" | \"close\" | \"ping\" | \"pong\">>;\n\ninterface WebSocketData {\n  url: string;\n  searchParams: URLSearchParams;\n  headers: Headers;\n  remoteAddress: string;\n}\n\nexport class BunWebSockets extends Transport {\n  protected clients: ServerWebSocket<WebSocketData>[] = [];\n  protected clientWrappers = new WeakMap<ServerWebSocket<WebSocketData>, WebSocketWrapper>();\n\n  private _server: Server<WebSocketData> | undefined;\n  private _expressApp: Application | undefined;\n  private _router: Router | undefined;\n  private _originalRawSend: typeof WebSocketClient.prototype.raw | null = null;\n  private options: TransportOptions = {};\n\n  constructor(options: TransportOptions = {}) {\n    super();\n\n    if (options.maxPayloadLength === undefined) {\n      options.maxPayloadLength = 4 * 1024;\n    }\n\n    this.options = options;\n  }\n\n  public getExpressApp(): Application {\n    if (!this._expressApp) {\n      // @ts-ignore\n      this._expressApp = bunExpress({});\n    }\n    return this._expressApp;\n  }\n\n  public bindRouter(router: Router) {\n    this._router = router;\n  }\n\n  public listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void) {\n    const self = this;\n\n    this._server = Bun.serve({\n      port,\n      hostname,\n\n      async fetch(req, server) {\n        const url = new URL(req.url);\n\n        // Try to upgrade to WebSocket\n        if (server.upgrade(req, {\n          data: {\n            url: url.pathname,\n            searchParams: url.searchParams,\n            headers: req.headers as Headers,\n            remoteAddress: server.requestIP(req)?.address || 'unknown',\n          }\n        })) {\n          return; // WebSocket upgrade successful\n        }\n\n        // Handle HTTP requests through router\n        // Write CORS headers\n        const corsHeaders = {\n          ...matchMaker.controller.DEFAULT_CORS_HEADERS,\n          ...matchMaker.controller.getCorsHeaders(req.headers)\n        };\n\n        // Handle OPTIONS requests\n        if (req.method === \"OPTIONS\") {\n          return new Response(null, {\n            status: 204,\n            headers: corsHeaders\n          });\n        }\n\n        if (self._router?.findRoute(req.method, url.pathname) !== undefined) {\n          const response = await self._router.handler(req);\n\n          // Add CORS headers to response\n          const headers = new Headers(response.headers);\n          Object.entries(corsHeaders).forEach(([key, value]) => {\n            if (!headers.has(key)) {\n              headers.set(key, value.toString());\n            }\n          });\n\n          return new Response(response.body, {\n            status: response.status,\n            statusText: response.statusText,\n            headers\n          });\n\n        } else if (self._expressApp) {\n          // Fallback to express routes\n          const ereq = new IncomingMessage(req, url, self._expressApp);\n          const eres = new ServerResponse(ereq, self._expressApp);\n\n          // Apply CORS headers through the Express response wrapper\n          Object.entries(corsHeaders).forEach(([key, value]) => {\n            eres.setHeader(key, value.toString());\n          });\n\n          // Read the request body before passing to express\n          if (req.method !== \"GET\" && req.method !== \"HEAD\") {\n            await (ereq as any).readBody();\n          }\n\n          (ereq as any).complete = true;\n\n          self._expressApp['handle'](ereq, eres);\n\n          return await eres.getBunResponse();\n        }\n\n        return new Response(\"Not Found\", { status: 404 });\n      },\n\n      websocket: {\n        ...this.options,\n\n        async open(ws) {\n          await self.onConnection(ws);\n        },\n\n        message(ws, message) {\n          self.clientWrappers.get(ws)?.emit('message', Buffer.from(message));\n        },\n\n        close(ws, code, reason) {\n          // remove from client list\n          spliceOne(self.clients, self.clients.indexOf(ws));\n\n          const clientWrapper = self.clientWrappers.get(ws);\n          if (clientWrapper) {\n            self.clientWrappers.delete(ws);\n\n            // emit 'close' on wrapper\n            clientWrapper.emit('close', code);\n          }\n        },\n      }\n    });\n\n    listeningListener?.();\n\n    return this;\n  }\n\n  public shutdown() {\n    if (this._server) {\n      this._server.stop(true);\n    }\n  }\n\n  public simulateLatency(milliseconds: number) {\n    if (this._originalRawSend == null) {\n      this._originalRawSend = WebSocketClient.prototype.raw;\n    }\n\n    const originalRawSend = this._originalRawSend;\n    WebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalRawSend : function (...args: any[]) {\n      let [buf, ...rest] = args;\n      buf = Buffer.from(buf);\n      // @ts-ignore\n      setTimeout(() => originalRawSend.apply(this, [buf, ...rest]), milliseconds);\n    };\n  }\n\n  protected async onConnection(rawClient: ServerWebSocket<WebSocketData>) {\n    const wrapper = new WebSocketWrapper(rawClient);\n    // keep reference to client and its wrapper\n    this.clients.push(rawClient);\n    this.clientWrappers.set(rawClient, wrapper);\n\n    const url = rawClient.data.url;\n    const searchParams = rawClient.data.searchParams;\n\n    const sessionId = searchParams.get(\"sessionId\");\n    const processAndRoomId = url.match(/\\/[a-zA-Z0-9_\\-]+\\/([a-zA-Z0-9_\\-]+)$/);\n    const roomId = processAndRoomId && processAndRoomId[1];\n\n    // If sessionId is not provided, allow ping-pong utility.\n    if (!sessionId && !roomId) {\n      // Disconnect automatically after 1 second if no message is received.\n      const timeout = setTimeout(() => rawClient.close(CloseCode.NORMAL_CLOSURE), 1000);\n      wrapper.on('message', (_) => rawClient.send(new Uint8Array([Protocol.PING])));\n      wrapper.on('close', () => clearTimeout(timeout));\n      return;\n    }\n\n    const room = matchMaker.getLocalRoomById(roomId);\n    const client = new WebSocketClient(sessionId, wrapper);\n    const reconnectionToken = searchParams.get(\"reconnectionToken\");\n    const skipHandshake = searchParams.has(\"skipHandshake\");\n\n    try {\n      await connectClientToRoom(room, client, {\n        token: searchParams.get(\"_authToken\") ?? getBearerToken(rawClient.data.headers.get('authorization')),\n        headers: rawClient.data.headers,\n        ip: rawClient.data.headers.get('x-real-ip') ?? rawClient.data.headers.get('x-forwarded-for') ?? rawClient.data.remoteAddress,\n      }, {\n        reconnectionToken,\n        skipHandshake\n      });\n\n    } catch (e: any) {\n      debugAndPrintError(e);\n\n      // send error code to client then terminate.\n      // Use MAY_TRY_RECONNECT in devMode so the SDK retries \u2014 the seat\n      // may not be reserved yet during HMR reload.\n      client.error(e.code, e.message, () =>\n        rawClient.close(reconnectionToken\n          ? (isDevMode)\n            ? CloseCode.MAY_TRY_RECONNECT\n            : CloseCode.FAILED_TO_RECONNECT\n          : CloseCode.WITH_ERROR));\n    }\n  }\n\n}\n"],
  "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,iBAA0D;AAE1D,kBAAuJ;AACvJ,6BAAkD;AAGlD,+BAA4D;AAcrD,IAAM,gBAAN,cAA4B,sBAAU;AAAA,EAU3C,YAAY,UAA4B,CAAC,GAAG;AAC1C,UAAM;AAVR,SAAU,UAA4C,CAAC;AACvD,SAAU,iBAAiB,oBAAI,QAA0D;AAKzF,SAAQ,mBAAgE;AACxE,SAAQ,UAA4B,CAAC;AAKnC,QAAI,QAAQ,qBAAqB,QAAW;AAC1C,cAAQ,mBAAmB,IAAI;AAAA,IACjC;AAEA,SAAK,UAAU;AAAA,EACjB;AAAA,EAEO,gBAA6B;AAClC,QAAI,CAAC,KAAK,aAAa;AAErB,WAAK,kBAAc,yBAAAA,SAAW,CAAC,CAAC;AAAA,IAClC;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEO,WAAW,QAAgB;AAChC,SAAK,UAAU;AAAA,EACjB;AAAA,EAEO,OAAO,MAAc,UAAmB,SAAkB,mBAAgC;AAC/F,UAAM,OAAO;AAEb,SAAK,UAAU,IAAI,MAAM;AAAA,MACvB;AAAA,MACA;AAAA,MAEA,MAAM,MAAM,KAAK,QAAQ;AACvB,cAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAG3B,YAAI,OAAO,QAAQ,KAAK;AAAA,UACtB,MAAM;AAAA,YACJ,KAAK,IAAI;AAAA,YACT,cAAc,IAAI;AAAA,YAClB,SAAS,IAAI;AAAA,YACb,eAAe,OAAO,UAAU,GAAG,GAAG,WAAW;AAAA,UACnD;AAAA,QACF,CAAC,GAAG;AACF;AAAA,QACF;AAIA,cAAM,cAAc;AAAA,UAClB,GAAG,uBAAW,WAAW;AAAA,UACzB,GAAG,uBAAW,WAAW,eAAe,IAAI,OAAO;AAAA,QACrD;AAGA,YAAI,IAAI,WAAW,WAAW;AAC5B,iBAAO,IAAI,SAAS,MAAM;AAAA,YACxB,QAAQ;AAAA,YACR,SAAS;AAAA,UACX,CAAC;AAAA,QACH;AAEA,YAAI,KAAK,SAAS,UAAU,IAAI,QAAQ,IAAI,QAAQ,MAAM,QAAW;AACnE,gBAAM,WAAW,MAAM,KAAK,QAAQ,QAAQ,GAAG;AAG/C,gBAAM,UAAU,IAAI,QAAQ,SAAS,OAAO;AAC5C,iBAAO,QAAQ,WAAW,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AACpD,gBAAI,CAAC,QAAQ,IAAI,GAAG,GAAG;AACrB,sBAAQ,IAAI,KAAK,MAAM,SAAS,CAAC;AAAA,YACnC;AAAA,UACF,CAAC;AAED,iBAAO,IAAI,SAAS,SAAS,MAAM;AAAA,YACjC,QAAQ,SAAS;AAAA,YACjB,YAAY,SAAS;AAAA,YACrB;AAAA,UACF,CAAC;AAAA,QAEH,WAAW,KAAK,aAAa;AAE3B,gBAAM,OAAO,IAAI,yCAAgB,KAAK,KAAK,KAAK,WAAW;AAC3D,gBAAM,OAAO,IAAI,wCAAe,MAAM,KAAK,WAAW;AAGtD,iBAAO,QAAQ,WAAW,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AACpD,iBAAK,UAAU,KAAK,MAAM,SAAS,CAAC;AAAA,UACtC,CAAC;AAGD,cAAI,IAAI,WAAW,SAAS,IAAI,WAAW,QAAQ;AACjD,kBAAO,KAAa,SAAS;AAAA,UAC/B;AAEA,UAAC,KAAa,WAAW;AAEzB,eAAK,YAAY,QAAQ,EAAE,MAAM,IAAI;AAErC,iBAAO,MAAM,KAAK,eAAe;AAAA,QACnC;AAEA,eAAO,IAAI,SAAS,aAAa,EAAE,QAAQ,IAAI,CAAC;AAAA,MAClD;AAAA,MAEA,WAAW;AAAA,QACT,GAAG,KAAK;AAAA,QAER,MAAM,KAAK,IAAI;AACb,gBAAM,KAAK,aAAa,EAAE;AAAA,QAC5B;AAAA,QAEA,QAAQ,IAAI,SAAS;AACnB,eAAK,eAAe,IAAI,EAAE,GAAG,KAAK,WAAW,OAAO,KAAK,OAAO,CAAC;AAAA,QACnE;AAAA,QAEA,MAAM,IAAI,MAAM,QAAQ;AAEtB,qCAAU,KAAK,SAAS,KAAK,QAAQ,QAAQ,EAAE,CAAC;AAEhD,gBAAM,gBAAgB,KAAK,eAAe,IAAI,EAAE;AAChD,cAAI,eAAe;AACjB,iBAAK,eAAe,OAAO,EAAE;AAG7B,0BAAc,KAAK,SAAS,IAAI;AAAA,UAClC;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAED,wBAAoB;AAEpB,WAAO;AAAA,EACT;AAAA,EAEO,WAAW;AAChB,QAAI,KAAK,SAAS;AAChB,WAAK,QAAQ,KAAK,IAAI;AAAA,IACxB;AAAA,EACF;AAAA,EAEO,gBAAgB,cAAsB;AAC3C,QAAI,KAAK,oBAAoB,MAAM;AACjC,WAAK,mBAAmB,uCAAgB,UAAU;AAAA,IACpD;AAEA,UAAM,kBAAkB,KAAK;AAC7B,2CAAgB,UAAU,MAAM,gBAAgB,OAAO,UAAU,kBAAkB,YAAa,MAAa;AAC3G,UAAI,CAAC,KAAK,GAAG,IAAI,IAAI;AACrB,YAAM,OAAO,KAAK,GAAG;AAErB,iBAAW,MAAM,gBAAgB,MAAM,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,YAAY;AAAA,IAC5E;AAAA,EACF;AAAA,EAEA,MAAgB,aAAa,WAA2C;AACtE,UAAM,UAAU,IAAI,wCAAiB,SAAS;AAE9C,SAAK,QAAQ,KAAK,SAAS;AAC3B,SAAK,eAAe,IAAI,WAAW,OAAO;AAE1C,UAAM,MAAM,UAAU,KAAK;AAC3B,UAAM,eAAe,UAAU,KAAK;AAEpC,UAAM,YAAY,aAAa,IAAI,WAAW;AAC9C,UAAM,mBAAmB,IAAI,MAAM,uCAAuC;AAC1E,UAAM,SAAS,oBAAoB,iBAAiB,CAAC;AAGrD,QAAI,CAAC,aAAa,CAAC,QAAQ;AAEzB,YAAM,UAAU,WAAW,MAAM,UAAU,MAAM,sBAAU,cAAc,GAAG,GAAI;AAChF,cAAQ,GAAG,WAAW,CAAC,MAAM,UAAU,KAAK,IAAI,WAAW,CAAC,qBAAS,IAAI,CAAC,CAAC,CAAC;AAC5E,cAAQ,GAAG,SAAS,MAAM,aAAa,OAAO,CAAC;AAC/C;AAAA,IACF;AAEA,UAAM,OAAO,uBAAW,iBAAiB,MAAM;AAC/C,UAAM,SAAS,IAAI,uCAAgB,WAAW,OAAO;AACrD,UAAM,oBAAoB,aAAa,IAAI,mBAAmB;AAC9D,UAAM,gBAAgB,aAAa,IAAI,eAAe;AAEtD,QAAI;AACF,gBAAM,iCAAoB,MAAM,QAAQ;AAAA,QACtC,OAAO,aAAa,IAAI,YAAY,SAAK,4BAAe,UAAU,KAAK,QAAQ,IAAI,eAAe,CAAC;AAAA,QACnG,SAAS,UAAU,KAAK;AAAA,QACxB,IAAI,UAAU,KAAK,QAAQ,IAAI,WAAW,KAAK,UAAU,KAAK,QAAQ,IAAI,iBAAiB,KAAK,UAAU,KAAK;AAAA,MACjH,GAAG;AAAA,QACD;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IAEH,SAAS,GAAQ;AACf,0CAAmB,CAAC;AAKpB,aAAO,MAAM,EAAE,MAAM,EAAE,SAAS,MAC9B,UAAU,MAAM,oBACX,wBACC,sBAAU,oBACV,sBAAU,sBACZ,sBAAU,UAAU,CAAC;AAAA,IAC7B;AAAA,EACF;AAEF;",
  "names": ["bunExpress"]
}
