{
  "version": 3,
  "sources": ["../src/uWebSocketsTransport.ts"],
  "sourcesContent": ["import http, { IncomingHttpHeaders } from 'http';\nimport querystring, { ParsedUrlQuery } from 'querystring';\nimport uWebSockets from 'uWebSockets.js';\nimport expressify, { Application } from \"uwebsockets-express\";\n\nimport { AuthContext, HttpServerMock, ErrorCode, matchMaker, getBearerToken, Transport, debugAndPrintError, spliceOne } from '@colyseus/core';\nimport { uWebSocketClient, uWebSocketWrapper } from './uWebSocketClient.js';\n\nexport type TransportOptions = Omit<uWebSockets.WebSocketBehavior<any>, \"upgrade\" | \"open\" | \"pong\" | \"close\" | \"message\">;\n\ntype RawWebSocketClient = uWebSockets.WebSocket<any> & {\n  url: string,\n  searchParams: ParsedUrlQuery,\n  context: AuthContext,\n};\n\nexport class uWebSocketsTransport extends Transport {\n    public app: uWebSockets.TemplatedApp;\n    public expressApp: Application;\n\n    protected clients: RawWebSocketClient[] = [];\n    protected clientWrappers = new WeakMap<RawWebSocketClient, uWebSocketWrapper>();\n\n    private _listeningSocket: any;\n    private _originalRawSend: typeof uWebSocketClient.prototype.raw | null = null;\n\n    constructor(options: TransportOptions = {}, appOptions: uWebSockets.AppOptions = {}) {\n        super();\n\n        this.app = (appOptions.cert_file_name && appOptions.key_file_name)\n            ? uWebSockets.SSLApp(appOptions)\n            : uWebSockets.App(appOptions);\n\n        this.expressApp = expressify(this.app);\n\n        if (options.maxBackpressure === undefined) {\n            options.maxBackpressure = 1024 * 1024;\n        }\n\n        if (options.compression === undefined) {\n            options.compression = uWebSockets.DISABLED;\n        }\n\n        if (options.maxPayloadLength === undefined) {\n            options.maxPayloadLength = 4 * 1024;\n        }\n\n        if (options.sendPingsAutomatically === undefined) {\n            options.sendPingsAutomatically = true;\n        }\n\n        // https://github.com/colyseus/colyseus/issues/458\n        // Adding a mock object for Transport.server\n        if(!this.server) {\n          // @ts-ignore\n          this.server = new HttpServerMock();\n        }\n\n        this.app.ws('/*', {\n            ...options,\n\n            upgrade: (res, req, context) => {\n                // get all headers\n                const headers: {[id: string]: string} = {};\n                req.forEach((key, value) => headers[key] = value);\n\n                const searchParams = querystring.parse(req.getQuery());\n\n                /* This immediately calls open handler, you must not use res after this call */\n                /* Spell these correctly */\n                res.upgrade(\n                    {\n                        url: req.getUrl(),\n                        searchParams,\n                        context: {\n                          token: searchParams._authToken ?? getBearerToken(req.getHeader('authorization')),\n                          headers,\n                          ip: Buffer.from(res.getRemoteAddressAsText()).toString(),\n                        }\n                    },\n                    req.getHeader('sec-websocket-key'),\n                    req.getHeader('sec-websocket-protocol'),\n                    req.getHeader('sec-websocket-extensions'),\n                    context\n                );\n            },\n\n            open: async (ws: RawWebSocketClient) => {\n                // ws.pingCount = 0;\n                await this.onConnection(ws);\n            },\n\n            // pong: (ws: RawWebSocketClient) => {\n            //     ws.pingCount = 0;\n            // },\n\n            close: (ws: RawWebSocketClient, code: number, message: ArrayBuffer) => {\n                // remove from client list\n                spliceOne(this.clients, this.clients.indexOf(ws));\n\n                const clientWrapper = this.clientWrappers.get(ws);\n                if (clientWrapper) {\n                  this.clientWrappers.delete(ws);\n\n                  // emit 'close' on wrapper\n                  clientWrapper.emit('close', code);\n                }\n            },\n\n            message: (ws: RawWebSocketClient, message: ArrayBuffer, isBinary: boolean) => {\n                // emit 'message' on wrapper\n                this.clientWrappers.get(ws)?.emit('message', Buffer.from(message));\n            },\n\n        });\n\n        this.registerMatchMakeRequest();\n    }\n\n    public listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void) {\n        const callback = (listeningSocket: any) => {\n          this._listeningSocket = listeningSocket;\n          listeningListener?.();\n          // @ts-ignore\n          this.server.emit(\"listening\"); // Mocking Transport.server behaviour, https://github.com/colyseus/colyseus/issues/458\n        };\n\n        if (typeof(port) === \"string\") {\n            // @ts-ignore\n            this.app.listen_unix(callback, port);\n\n        } else {\n            this.app.listen(port, callback);\n\n        }\n        return this;\n    }\n\n    public shutdown() {\n        if (this._listeningSocket) {\n          uWebSockets.us_listen_socket_close(this._listeningSocket);\n          // @ts-ignore\n          this.server.emit(\"close\"); // Mocking Transport.server behaviour, https://github.com/colyseus/colyseus/issues/458\n        }\n    }\n\n    public simulateLatency(milliseconds: number) {\n        if (this._originalRawSend == null) {\n            this._originalRawSend = uWebSocketClient.prototype.raw;\n        }\n\n        const originalRawSend = this._originalRawSend;\n        uWebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalRawSend : function (...args: any[]) {\n            // copy buffer\n            let [buf, ...rest] = args;\n            buf = Buffer.from(buf);\n            setTimeout(() => originalRawSend.apply(this, [buf, ...rest]), milliseconds);\n        };\n    }\n\n    protected async onConnection(rawClient: RawWebSocketClient) {\n        const wrapper = new uWebSocketWrapper(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.url;\n        const searchParams = rawClient.searchParams;\n\n        const sessionId = searchParams.sessionId as string;\n        const processAndRoomId = url.match(/\\/[a-zA-Z0-9_\\-]+\\/([a-zA-Z0-9_\\-]+)$/);\n        const roomId = processAndRoomId && processAndRoomId[1];\n\n        const room = matchMaker.getLocalRoomById(roomId);\n        const client = new uWebSocketClient(sessionId, wrapper);\n\n        //\n        // TODO: DRY code below with all transports\n        //\n\n        try {\n            if (!room || !room.hasReservedSeat(sessionId, searchParams.reconnectionToken as string)) {\n                throw new Error('seat reservation expired.');\n            }\n\n            await room._onJoin(client, rawClient.context);\n\n        } catch (e) {\n            debugAndPrintError(e);\n\n            // send error code to client then terminate\n            client.error(e.code, e.message, () => client.leave());\n        }\n    }\n\n    protected registerMatchMakeRequest() {\n\n        // TODO: DRY with Server.ts\n        const matchmakeRoute = 'matchmake';\n        const allowedRoomNameChars = /([a-zA-Z_\\-0-9]+)/gi;\n\n        const writeHeaders = (req: uWebSockets.HttpRequest, res: uWebSockets.HttpResponse) => {\n            // skip if aborted\n            if (res.aborted) { return; }\n\n            const headers = Object.assign(\n                {},\n                matchMaker.controller.DEFAULT_CORS_HEADERS,\n                matchMaker.controller.getCorsHeaders.call(undefined, req)\n            );\n\n            for (const header in headers) {\n                res.writeHeader(header, headers[header].toString());\n            }\n\n            return true;\n        }\n\n        const writeError = (res: uWebSockets.HttpResponse, error: { code: number, error: string }) => {\n            // skip if aborted\n            if (res.aborted) { return; }\n\n            res.cork(() => {\n              res.writeStatus(\"406 Not Acceptable\");\n              res.end(JSON.stringify(error));\n            });\n        }\n\n        const onAborted = (res: uWebSockets.HttpResponse) => {\n          res.aborted = true;\n        };\n\n        this.app.options(\"/matchmake/*\", (res, req) => {\n            res.onAborted(() => onAborted(res));\n\n            if (writeHeaders(req, res)) {\n              res.writeStatus(\"204 No Content\");\n              res.end();\n            }\n        });\n\n\n        // @ts-ignore\n        this.app.post(\"/matchmake/*\", (res, req) => {\n            res.onAborted(() => onAborted(res));\n\n            // do not accept matchmaking requests if already shutting down\n            if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n              return res.close();\n            }\n\n            writeHeaders(req, res);\n            res.writeHeader('Content-Type', 'application/json');\n\n            const url = req.getUrl();\n            const matchedParams = url.match(allowedRoomNameChars);\n            const matchmakeIndex = matchedParams.indexOf(matchmakeRoute);\n\n            // cache all headers\n            const headers: IncomingHttpHeaders = {};\n            req.forEach((key, value) => headers[key] = value);\n\n            const token = getBearerToken(headers['authorization']);\n\n            // read json body\n            this.readJson(res, async (clientOptions) => {\n                try {\n                    if (clientOptions === undefined) {\n                      throw new Error(\"invalid JSON input\");\n                    }\n\n                    const method = matchedParams[matchmakeIndex + 1];\n                    const roomName = matchedParams[matchmakeIndex + 2] || '';\n\n                    const response = await matchMaker.controller.invokeMethod(\n                      method,\n                      roomName,\n                      clientOptions,\n                      {\n                        token,\n                        headers,\n                        ip: headers['x-real-ip'] ?? Buffer.from(res.getRemoteAddressAsText()).toString()\n                      }\n                    );\n\n                    if (!res.aborted) {\n                      res.cork(() => {\n                        res.writeStatus(\"200 OK\");\n                        res.end(JSON.stringify(response));\n                      });\n                    }\n\n                } catch (e) {\n                    debugAndPrintError(e);\n                    writeError(res, {\n                        code: e.code || ErrorCode.MATCHMAKE_UNHANDLED,\n                        error: e.message\n                    });\n                }\n\n            });\n        });\n    }\n\n    /* Helper function for reading a posted JSON body */\n    /* Extracted from https://github.com/uNetworking/uWebSockets.js/blob/master/examples/JsonPost.js */\n    private readJson(res: uWebSockets.HttpResponse, cb: (json: any) => void) {\n        let buffer: any;\n        /* Register data cb */\n        res.onData((ab, isLast) => {\n            let chunk = Buffer.from(ab);\n            if (isLast) {\n                let json;\n                if (buffer) {\n                    try {\n                        // @ts-ignore\n                        json = JSON.parse(Buffer.concat([buffer, chunk]));\n                    } catch (e) {\n                        /* res.close calls onAborted */\n                        // res.close();\n                        cb(undefined);\n                        return;\n                    }\n                    cb(json);\n                } else {\n                    try {\n                        // @ts-ignore\n                        json = JSON.parse(chunk);\n                    } catch (e) {\n                        /* res.close calls onAborted */\n                        // res.close();\n                        cb(undefined);\n                        return;\n                    }\n                    cb(json);\n                }\n            } else {\n                if (buffer) {\n                    buffer = Buffer.concat([buffer, chunk]);\n                } else {\n                    buffer = Buffer.concat([chunk]);\n                }\n            }\n        });\n    }\n}\n"],
  "mappings": "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",
  "names": []
}
