@colyseus/uwebsockets-transport 0.17.20 → 0.17.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -41,6 +41,8 @@ var import_core2 = require("@colyseus/core");
41
41
  var uWebSocketsExpress = new import_core2.Deferred();
42
42
  var uWebSocketsExpressModule = void 0;
43
43
  import("uwebsockets-express").then((module2) => uWebSocketsExpress.resolve(module2)).catch((error) => uWebSocketsExpress.reject(error));
44
+ var noop = () => {
45
+ };
44
46
  var uWebSocketsTransport = class extends import_core.Transport {
45
47
  constructor(options = {}, appOptions = {}) {
46
48
  super();
@@ -48,20 +50,22 @@ var uWebSocketsTransport = class extends import_core.Transport {
48
50
  this.clientWrappers = /* @__PURE__ */ new WeakMap();
49
51
  this._originalRawSend = null;
50
52
  this.app = appOptions.cert_file_name && appOptions.key_file_name ? import_uWebSockets.default.SSLApp(appOptions) : import_uWebSockets.default.App(appOptions);
51
- if (options.maxBackpressure === void 0) {
52
- options.maxBackpressure = 1024 * 1024;
53
+ const { readBodyMaxTime, ...wsOptions } = options;
54
+ this._readBodyMaxTime = readBodyMaxTime;
55
+ if (wsOptions.maxBackpressure === void 0) {
56
+ wsOptions.maxBackpressure = 1024 * 1024;
53
57
  }
54
- if (options.compression === void 0) {
55
- options.compression = import_uWebSockets.default.DISABLED;
58
+ if (wsOptions.compression === void 0) {
59
+ wsOptions.compression = import_uWebSockets.default.DISABLED;
56
60
  }
57
- if (options.maxPayloadLength === void 0) {
58
- options.maxPayloadLength = 4 * 1024;
61
+ if (wsOptions.maxPayloadLength === void 0) {
62
+ wsOptions.maxPayloadLength = 4 * 1024;
59
63
  }
60
- if (options.sendPingsAutomatically === void 0) {
61
- options.sendPingsAutomatically = true;
64
+ if (wsOptions.sendPingsAutomatically === void 0) {
65
+ wsOptions.sendPingsAutomatically = true;
62
66
  }
63
67
  this.app.ws("/*", {
64
- ...options,
68
+ ...wsOptions,
65
69
  upgrade: (res, req, context) => {
66
70
  const headers = {};
67
71
  req.forEach((key, value) => headers[key] = value);
@@ -109,7 +113,7 @@ var uWebSocketsTransport = class extends import_core.Transport {
109
113
  uWebSocketsExpressModule = module2;
110
114
  const originalAny = this.app.any;
111
115
  this.app.any = (() => this.app);
112
- this._expressApp = module2.default(this.app);
116
+ this._expressApp = module2.default(this.app, { readBodyMaxTime: this._readBodyMaxTime });
113
117
  this.app.any = originalAny;
114
118
  resolve(this._expressApp);
115
119
  } catch (error) {
@@ -230,7 +234,14 @@ var uWebSocketsTransport = class extends import_core.Transport {
230
234
  for (const header in corsHeaders) {
231
235
  eres.setHeader(header, corsHeaders[header].toString());
232
236
  }
233
- await ereq._readBody();
237
+ ereq.on("error", noop);
238
+ try {
239
+ await ereq._readBody();
240
+ } catch (e) {
241
+ eres.statusCode = 408;
242
+ eres.end();
243
+ return;
244
+ }
234
245
  if (res.aborted) {
235
246
  return;
236
247
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/uWebSocketsTransport.ts"],
4
- "sourcesContent": ["import querystring, { type ParsedUrlQuery } from 'querystring';\nimport uWebSockets, { type WebSocket } from 'uWebSockets.js';\nimport type express from 'express';\n\nimport { type AuthContext, Transport, matchMaker, Protocol, getBearerToken, debugAndPrintError, spliceOne, connectClientToRoom, CloseCode, isDevMode, type Router } from '@colyseus/core';\nimport { uWebSocketClient, uWebSocketWrapper } from './uWebSocketClient.ts';\nimport { Deferred } from '@colyseus/core';\n\nconst uWebSocketsExpress = new Deferred<typeof import('uwebsockets-express')>;\nlet uWebSocketsExpressModule: typeof import('uwebsockets-express') | undefined = undefined;\nimport('uwebsockets-express')\n .then((module) => uWebSocketsExpress.resolve(module))\n .catch((error) => uWebSocketsExpress.reject(error));\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\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 private _expressApp?: express.Application;\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 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 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: headers['x-real-ip'] ?? headers['x-forwarded-for'] ?? 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: WebSocket<any>) => {\n // ws.pingCount = 0;\n await this.onConnection(ws as RawWebSocketClient);\n },\n\n // pong: (ws: RawWebSocketClient) => {\n // ws.pingCount = 0;\n // },\n\n close: (ws: WebSocket<any>, code: number, message: ArrayBuffer) => {\n // remove from client list\n spliceOne(this.clients, this.clients.indexOf(ws as RawWebSocketClient));\n\n const clientWrapper = this.clientWrappers.get(ws as RawWebSocketClient);\n if (clientWrapper) {\n this.clientWrappers.delete(ws as RawWebSocketClient);\n\n // emit 'close' on wrapper\n clientWrapper.emit('close', code);\n }\n },\n\n message: (ws: WebSocket<any>, message: ArrayBuffer, isBinary: boolean) => {\n // emit 'message' on wrapper\n this.clientWrappers.get(ws as RawWebSocketClient)?.emit('message', Buffer.from(message));\n },\n\n });\n }\n\n public getExpressApp(): Promise<express.Application> | express.Application {\n if (!this._expressApp) {\n return new Promise(async (resolve, reject) => {\n try {\n const module = await uWebSocketsExpress;\n uWebSocketsExpressModule = module;\n\n // Temporarily stub `app.any` to prevent uwebsockets-express Application.init()\n // from registering its own catch-all handler \u2014 we manage HTTP routing ourselves\n // in bindRouter().\n const originalAny = this.app.any;\n this.app.any = (() => this.app) as any;\n this._expressApp = (module.default(this.app) as unknown) as express.Application;\n this.app.any = originalAny;\n resolve(this._expressApp);\n } catch (error) {\n reject(error);\n console.warn(\"\");\n console.warn(\"\u274C Error: could not initialize express.\");\n console.warn(\"\");\n console.warn(\" For Express v5, use:\");\n console.warn(\" \uD83D\uDC49 npm install --save uwebsockets-express@^2.0.1\");\n console.warn(\"\");\n console.warn(\" For Express v4, use:\");\n console.warn(\" \uD83D\uDC49 npm install --save uwebsockets-express@^1.4.1\");\n console.warn(\"\");\n process.exit();\n }\n });\n }\n return this._expressApp;\n }\n\n public bindRouter(router: Router) {\n const getCorsHeaders = (requestHeaders: Headers) => {\n return Object.assign(\n {},\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders(requestHeaders)\n );\n }\n\n const writeCorsHeaders = (res: uWebSockets.HttpResponse, requestHeaders: Headers) => {\n // skip if aborted\n if (res.aborted) { return; }\n\n const headers = getCorsHeaders(requestHeaders);\n\n for (const header in headers) {\n res.writeHeader(header, headers[header].toString());\n }\n\n return true;\n }\n\n this.app.options(\"/*\", (res, req) => {\n res.onAborted(() => res.aborted = true);\n\n // cache all headers\n const reqHeaders = new Headers();\n req.forEach((key, value) => reqHeaders.set(key, value));\n\n res.cork(() => {\n res.writeStatus(\"204 No Content\");\n writeCorsHeaders(res, reqHeaders);\n res.end();\n });\n });\n\n this.app.any('/*', async (res, req) => {\n const abortController = new AbortController();\n\n res.onAborted(() => {\n abortController.abort();\n res.aborted = true;\n });\n\n // cache all headers and request info synchronously\n // (uWebSockets.js req is only valid in the synchronous callback scope)\n const headers = new Headers();\n req.forEach((key, value) => headers.set(key, value));\n\n const method = req.getMethod().toUpperCase();\n const url = req.getUrl();\n const query = req.getQuery();\n const remoteAddress = res.getRemoteAddressAsText();\n\n // check if the route is defined in the router\n // if so, use the router handler, otherwise fallback to express\n if (router.findRoute(method, url) !== undefined) {\n const requestInit: RequestInit = {\n method,\n referrer: headers.get('referer') || undefined,\n keepalive: headers.get('keep-alive') === 'true',\n headers,\n signal: abortController.signal,\n };\n\n // read request body\n if (method !== \"GET\" && method !== \"HEAD\") {\n let body: Buffer = undefined;\n\n // uWebSockets.js `HttpRequest` does not provide 'getData', must aggregate POST body via HttpResponse\n await new Promise<void>((resolve) => {\n res.onData((ab, isLast) => {\n const chunk = Buffer.from(ab);\n if (body === undefined) {\n body = Buffer.from(chunk);\n } else {\n body = Buffer.concat([body, chunk]);\n }\n if (isLast) {\n resolve();\n }\n });\n });\n\n requestInit.body = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer;\n }\n\n const fullUrl = `http://${headers.get('host') || 'localhost'}${url}${(query ? `?${query}` : '')}`;\n const response = await router.handler(new Request(fullUrl, requestInit));\n\n // skip if aborted\n if (res.aborted) { return; }\n\n // read response body before cork (cork callback must be synchronous)\n const responseBody = await response.arrayBuffer();\n\n // writeStatus() must be called before writeHeader() in uWebSockets.js\n res.cork(() => {\n res.writeStatus(`${response.status} ${response.statusText}`);\n writeCorsHeaders(res, headers);\n response.headers.forEach((value, key) => {\n if (key.toLowerCase() !== 'content-length') {\n res.writeHeader(key, value);\n }\n });\n res.end(responseBody);\n });\n\n } else if (this._expressApp) {\n // skip if already aborted\n if (res.aborted) { return; }\n\n const corsHeaders = getCorsHeaders(headers);\n\n const ereq = new uWebSocketsExpressModule.IncomingMessage(req, res, this._expressApp as any, {\n headers: Object.fromEntries((headers as any).entries()),\n method,\n url,\n query,\n remoteAddress\n });\n const eres = new uWebSocketsExpressModule.ServerResponse(res, req, this._expressApp);\n\n // Propagate uWS abort to the Express response wrapper.\n // When the client disconnects, mark the wrapper as finished\n // so it won't try to write to the already-aborted uWS response.\n // (fixes: \"uWS.HttpResponse must not be accessed after onAborted callback\")\n abortController.signal.addEventListener('abort', () => {\n eres.finished = true;\n // @ts-ignore\n eres.writableEnded = true;\n });\n\n // Apply CORS headers through the Express response wrapper\n for (const header in corsHeaders) {\n eres.setHeader(header, corsHeaders[header].toString());\n }\n\n // Read the request body from uWebSockets before passing to express\n // (uWebSockets requires res.onData() to be called to consume the body)\n await ereq._readBody();\n\n // skip if aborted during body read\n if (res.aborted) { return; }\n\n this._expressApp['handle'](ereq, eres);\n }\n });\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 };\n\n if (typeof (port) === \"string\") {\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 }\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 // @ts-ignore\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 // 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 // uWS throws \"Invalid access of closed uWS.WebSocket\" if the socket closed between the readyState check and the end()/close() call.\n const timeout = setTimeout(() => {\n try { rawClient.close(); } catch (e: any) {}\n }, 1000);\n wrapper.on('message', (_) => {\n try { rawClient.send(new Uint8Array([Protocol.PING]), true); } catch (e: any) {}\n });\n wrapper.on('close', () => clearTimeout(timeout));\n return;\n }\n\n const room = matchMaker.getLocalRoomById(roomId);\n const client = new uWebSocketClient(sessionId, wrapper);\n const reconnectionToken = searchParams.reconnectionToken as string;\n const skipHandshake = (searchParams.skipHandshake !== undefined);\n\n try {\n await connectClientToRoom(room, client, rawClient.context, {\n reconnectionToken,\n skipHandshake\n });\n\n } catch (e: any) {\n debugAndPrintError(e);\n\n // send error code to client then terminate\n client.error(e.code, e.message, () => {\n // uWS throws \"Invalid access of closed uWS.WebSocket\" if the socket closed between the readyState check and the end()/close() call.\n // Use MAY_TRY_RECONNECT in devMode so the SDK retries \u2014 the seat\n // may not be reserved yet during HMR reload.\n try {\n rawClient.end(reconnectionToken\n ? (isDevMode)\n ? CloseCode.MAY_TRY_RECONNECT\n : CloseCode.FAILED_TO_RECONNECT\n : CloseCode.WITH_ERROR);\n } catch (e: any) {}\n });\n }\n }\n\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import querystring, { type ParsedUrlQuery } from 'querystring';\nimport uWebSockets, { type WebSocket } from 'uWebSockets.js';\nimport type express from 'express';\n\nimport { type AuthContext, Transport, matchMaker, Protocol, getBearerToken, debugAndPrintError, spliceOne, connectClientToRoom, CloseCode, isDevMode, type Router } from '@colyseus/core';\nimport { uWebSocketClient, uWebSocketWrapper } from './uWebSocketClient.ts';\nimport { Deferred } from '@colyseus/core';\n\nconst uWebSocketsExpress = new Deferred<typeof import('uwebsockets-express')>;\nlet uWebSocketsExpressModule: typeof import('uwebsockets-express') | undefined = undefined;\nimport('uwebsockets-express')\n .then((module) => uWebSocketsExpress.resolve(module))\n .catch((error) => uWebSocketsExpress.reject(error));\n\nconst noop = () => {};\n\nexport type TransportOptions = Omit<uWebSockets.WebSocketBehavior<any>, \"upgrade\" | \"open\" | \"pong\" | \"close\" | \"message\"> & {\n /**\n * Maximum time (in milliseconds) allowed while reading an HTTP request body\n * before the request is rejected with \"408 Request Timeout\". (default: 500)\n */\n readBodyMaxTime?: number,\n};\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\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 private _expressApp?: express.Application;\n private _readBodyMaxTime?: number;\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 // not a uWS.WebSocketBehavior option \u2014 must not be spread into app.ws()\n const { readBodyMaxTime, ...wsOptions } = options;\n this._readBodyMaxTime = readBodyMaxTime;\n\n if (wsOptions.maxBackpressure === undefined) {\n wsOptions.maxBackpressure = 1024 * 1024;\n }\n\n if (wsOptions.compression === undefined) {\n wsOptions.compression = uWebSockets.DISABLED;\n }\n\n if (wsOptions.maxPayloadLength === undefined) {\n wsOptions.maxPayloadLength = 4 * 1024;\n }\n\n if (wsOptions.sendPingsAutomatically === undefined) {\n wsOptions.sendPingsAutomatically = true;\n }\n\n this.app.ws('/*', {\n ...wsOptions,\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: headers['x-real-ip'] ?? headers['x-forwarded-for'] ?? 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: WebSocket<any>) => {\n // ws.pingCount = 0;\n await this.onConnection(ws as RawWebSocketClient);\n },\n\n // pong: (ws: RawWebSocketClient) => {\n // ws.pingCount = 0;\n // },\n\n close: (ws: WebSocket<any>, code: number, message: ArrayBuffer) => {\n // remove from client list\n spliceOne(this.clients, this.clients.indexOf(ws as RawWebSocketClient));\n\n const clientWrapper = this.clientWrappers.get(ws as RawWebSocketClient);\n if (clientWrapper) {\n this.clientWrappers.delete(ws as RawWebSocketClient);\n\n // emit 'close' on wrapper\n clientWrapper.emit('close', code);\n }\n },\n\n message: (ws: WebSocket<any>, message: ArrayBuffer, isBinary: boolean) => {\n // emit 'message' on wrapper\n this.clientWrappers.get(ws as RawWebSocketClient)?.emit('message', Buffer.from(message));\n },\n\n });\n }\n\n public getExpressApp(): Promise<express.Application> | express.Application {\n if (!this._expressApp) {\n return new Promise(async (resolve, reject) => {\n try {\n const module = await uWebSocketsExpress;\n uWebSocketsExpressModule = module;\n\n // Temporarily stub `app.any` to prevent uwebsockets-express Application.init()\n // from registering its own catch-all handler \u2014 we manage HTTP routing ourselves\n // in bindRouter().\n const originalAny = this.app.any;\n this.app.any = (() => this.app) as any;\n this._expressApp = (module.default(this.app, { readBodyMaxTime: this._readBodyMaxTime }) as unknown) as express.Application;\n this.app.any = originalAny;\n resolve(this._expressApp);\n } catch (error) {\n reject(error);\n console.warn(\"\");\n console.warn(\"\u274C Error: could not initialize express.\");\n console.warn(\"\");\n console.warn(\" For Express v5, use:\");\n console.warn(\" \uD83D\uDC49 npm install --save uwebsockets-express@^2.0.1\");\n console.warn(\"\");\n console.warn(\" For Express v4, use:\");\n console.warn(\" \uD83D\uDC49 npm install --save uwebsockets-express@^1.4.1\");\n console.warn(\"\");\n process.exit();\n }\n });\n }\n return this._expressApp;\n }\n\n public bindRouter(router: Router) {\n const getCorsHeaders = (requestHeaders: Headers) => {\n return Object.assign(\n {},\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders(requestHeaders)\n );\n }\n\n const writeCorsHeaders = (res: uWebSockets.HttpResponse, requestHeaders: Headers) => {\n // skip if aborted\n if (res.aborted) { return; }\n\n const headers = getCorsHeaders(requestHeaders);\n\n for (const header in headers) {\n res.writeHeader(header, headers[header].toString());\n }\n\n return true;\n }\n\n this.app.options(\"/*\", (res, req) => {\n res.onAborted(() => res.aborted = true);\n\n // cache all headers\n const reqHeaders = new Headers();\n req.forEach((key, value) => reqHeaders.set(key, value));\n\n res.cork(() => {\n res.writeStatus(\"204 No Content\");\n writeCorsHeaders(res, reqHeaders);\n res.end();\n });\n });\n\n this.app.any('/*', async (res, req) => {\n const abortController = new AbortController();\n\n res.onAborted(() => {\n abortController.abort();\n res.aborted = true;\n });\n\n // cache all headers and request info synchronously\n // (uWebSockets.js req is only valid in the synchronous callback scope)\n const headers = new Headers();\n req.forEach((key, value) => headers.set(key, value));\n\n const method = req.getMethod().toUpperCase();\n const url = req.getUrl();\n const query = req.getQuery();\n const remoteAddress = res.getRemoteAddressAsText();\n\n // check if the route is defined in the router\n // if so, use the router handler, otherwise fallback to express\n if (router.findRoute(method, url) !== undefined) {\n const requestInit: RequestInit = {\n method,\n referrer: headers.get('referer') || undefined,\n keepalive: headers.get('keep-alive') === 'true',\n headers,\n signal: abortController.signal,\n };\n\n // read request body\n if (method !== \"GET\" && method !== \"HEAD\") {\n let body: Buffer = undefined;\n\n // uWebSockets.js `HttpRequest` does not provide 'getData', must aggregate POST body via HttpResponse\n await new Promise<void>((resolve) => {\n res.onData((ab, isLast) => {\n const chunk = Buffer.from(ab);\n if (body === undefined) {\n body = Buffer.from(chunk);\n } else {\n body = Buffer.concat([body, chunk]);\n }\n if (isLast) {\n resolve();\n }\n });\n });\n\n requestInit.body = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer;\n }\n\n const fullUrl = `http://${headers.get('host') || 'localhost'}${url}${(query ? `?${query}` : '')}`;\n const response = await router.handler(new Request(fullUrl, requestInit));\n\n // skip if aborted\n if (res.aborted) { return; }\n\n // read response body before cork (cork callback must be synchronous)\n const responseBody = await response.arrayBuffer();\n\n // writeStatus() must be called before writeHeader() in uWebSockets.js\n res.cork(() => {\n res.writeStatus(`${response.status} ${response.statusText}`);\n writeCorsHeaders(res, headers);\n response.headers.forEach((value, key) => {\n if (key.toLowerCase() !== 'content-length') {\n res.writeHeader(key, value);\n }\n });\n res.end(responseBody);\n });\n\n } else if (this._expressApp) {\n // skip if already aborted\n if (res.aborted) { return; }\n\n const corsHeaders = getCorsHeaders(headers);\n\n const ereq = new uWebSocketsExpressModule.IncomingMessage(req, res, this._expressApp as any, {\n headers: Object.fromEntries((headers as any).entries()),\n method,\n url,\n query,\n remoteAddress\n });\n const eres = new uWebSocketsExpressModule.ServerResponse(res, req, this._expressApp);\n\n // Propagate uWS abort to the Express response wrapper.\n // When the client disconnects, mark the wrapper as finished\n // so it won't try to write to the already-aborted uWS response.\n // (fixes: \"uWS.HttpResponse must not be accessed after onAborted callback\")\n abortController.signal.addEventListener('abort', () => {\n eres.finished = true;\n // @ts-ignore\n eres.writableEnded = true;\n });\n\n // Apply CORS headers through the Express response wrapper\n for (const header in corsHeaders) {\n eres.setHeader(header, corsHeaders[header].toString());\n }\n\n // uwebsockets-express <= 2.0.2 emits a bare 'error' on body-read timeout,\n // which throws process-wide when unhandled (colyseus/uWebSockets-express#43)\n // TODO: remove once minimum uwebsockets-express is >= 2.0.3\n ereq.on('error', noop);\n\n // Read the request body from uWebSockets before passing to express\n // (uWebSockets requires res.onData() to be called to consume the body)\n try {\n await ereq._readBody();\n } catch (e: any) {\n // every rejection is a body-read timeout (versions <= 2.0.2 reject without\n // an error value); eres.end() is a no-op if the client already aborted\n eres.statusCode = 408;\n eres.end();\n return;\n }\n\n // skip if aborted during body read\n if (res.aborted) { return; }\n\n this._expressApp['handle'](ereq, eres);\n }\n });\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 };\n\n if (typeof (port) === \"string\") {\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 }\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 // @ts-ignore\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 // 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 // uWS throws \"Invalid access of closed uWS.WebSocket\" if the socket closed between the readyState check and the end()/close() call.\n const timeout = setTimeout(() => {\n try { rawClient.close(); } catch (e: any) {}\n }, 1000);\n wrapper.on('message', (_) => {\n try { rawClient.send(new Uint8Array([Protocol.PING]), true); } catch (e: any) {}\n });\n wrapper.on('close', () => clearTimeout(timeout));\n return;\n }\n\n const room = matchMaker.getLocalRoomById(roomId);\n const client = new uWebSocketClient(sessionId, wrapper);\n const reconnectionToken = searchParams.reconnectionToken as string;\n const skipHandshake = (searchParams.skipHandshake !== undefined);\n\n try {\n await connectClientToRoom(room, client, rawClient.context, {\n reconnectionToken,\n skipHandshake\n });\n\n } catch (e: any) {\n debugAndPrintError(e);\n\n // send error code to client then terminate\n client.error(e.code, e.message, () => {\n // uWS throws \"Invalid access of closed uWS.WebSocket\" if the socket closed between the readyState check and the end()/close() call.\n // Use MAY_TRY_RECONNECT in devMode so the SDK retries \u2014 the seat\n // may not be reserved yet during HMR reload.\n try {\n rawClient.end(reconnectionToken\n ? (isDevMode)\n ? CloseCode.MAY_TRY_RECONNECT\n : CloseCode.FAILED_TO_RECONNECT\n : CloseCode.WITH_ERROR);\n } catch (e: any) {}\n });\n }\n }\n\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["import_core", "module", "uWebSockets", "querystring", "e"]
7
7
  }
@@ -3,7 +3,13 @@ import uWebSockets from 'uWebSockets.js';
3
3
  import type express from 'express';
4
4
  import { type AuthContext, Transport, type Router } from '@colyseus/core';
5
5
  import { uWebSocketWrapper } from './uWebSocketClient.ts';
6
- export type TransportOptions = Omit<uWebSockets.WebSocketBehavior<any>, "upgrade" | "open" | "pong" | "close" | "message">;
6
+ export type TransportOptions = Omit<uWebSockets.WebSocketBehavior<any>, "upgrade" | "open" | "pong" | "close" | "message"> & {
7
+ /**
8
+ * Maximum time (in milliseconds) allowed while reading an HTTP request body
9
+ * before the request is rejected with "408 Request Timeout". (default: 500)
10
+ */
11
+ readBodyMaxTime?: number;
12
+ };
7
13
  type RawWebSocketClient = uWebSockets.WebSocket<any> & {
8
14
  url: string;
9
15
  searchParams: ParsedUrlQuery;
@@ -16,6 +22,7 @@ export declare class uWebSocketsTransport extends Transport {
16
22
  private _listeningSocket;
17
23
  private _originalRawSend;
18
24
  private _expressApp?;
25
+ private _readBodyMaxTime?;
19
26
  constructor(options?: TransportOptions, appOptions?: uWebSockets.AppOptions);
20
27
  getExpressApp(): Promise<express.Application> | express.Application;
21
28
  bindRouter(router: Router): void;
@@ -7,6 +7,8 @@ import { Deferred } from "@colyseus/core";
7
7
  var uWebSocketsExpress = new Deferred();
8
8
  var uWebSocketsExpressModule = void 0;
9
9
  import("uwebsockets-express").then((module) => uWebSocketsExpress.resolve(module)).catch((error) => uWebSocketsExpress.reject(error));
10
+ var noop = () => {
11
+ };
10
12
  var uWebSocketsTransport = class extends Transport {
11
13
  constructor(options = {}, appOptions = {}) {
12
14
  super();
@@ -14,20 +16,22 @@ var uWebSocketsTransport = class extends Transport {
14
16
  this.clientWrappers = /* @__PURE__ */ new WeakMap();
15
17
  this._originalRawSend = null;
16
18
  this.app = appOptions.cert_file_name && appOptions.key_file_name ? uWebSockets.SSLApp(appOptions) : uWebSockets.App(appOptions);
17
- if (options.maxBackpressure === void 0) {
18
- options.maxBackpressure = 1024 * 1024;
19
+ const { readBodyMaxTime, ...wsOptions } = options;
20
+ this._readBodyMaxTime = readBodyMaxTime;
21
+ if (wsOptions.maxBackpressure === void 0) {
22
+ wsOptions.maxBackpressure = 1024 * 1024;
19
23
  }
20
- if (options.compression === void 0) {
21
- options.compression = uWebSockets.DISABLED;
24
+ if (wsOptions.compression === void 0) {
25
+ wsOptions.compression = uWebSockets.DISABLED;
22
26
  }
23
- if (options.maxPayloadLength === void 0) {
24
- options.maxPayloadLength = 4 * 1024;
27
+ if (wsOptions.maxPayloadLength === void 0) {
28
+ wsOptions.maxPayloadLength = 4 * 1024;
25
29
  }
26
- if (options.sendPingsAutomatically === void 0) {
27
- options.sendPingsAutomatically = true;
30
+ if (wsOptions.sendPingsAutomatically === void 0) {
31
+ wsOptions.sendPingsAutomatically = true;
28
32
  }
29
33
  this.app.ws("/*", {
30
- ...options,
34
+ ...wsOptions,
31
35
  upgrade: (res, req, context) => {
32
36
  const headers = {};
33
37
  req.forEach((key, value) => headers[key] = value);
@@ -75,7 +79,7 @@ var uWebSocketsTransport = class extends Transport {
75
79
  uWebSocketsExpressModule = module;
76
80
  const originalAny = this.app.any;
77
81
  this.app.any = (() => this.app);
78
- this._expressApp = module.default(this.app);
82
+ this._expressApp = module.default(this.app, { readBodyMaxTime: this._readBodyMaxTime });
79
83
  this.app.any = originalAny;
80
84
  resolve(this._expressApp);
81
85
  } catch (error) {
@@ -196,7 +200,14 @@ var uWebSocketsTransport = class extends Transport {
196
200
  for (const header in corsHeaders) {
197
201
  eres.setHeader(header, corsHeaders[header].toString());
198
202
  }
199
- await ereq._readBody();
203
+ ereq.on("error", noop);
204
+ try {
205
+ await ereq._readBody();
206
+ } catch (e) {
207
+ eres.statusCode = 408;
208
+ eres.end();
209
+ return;
210
+ }
200
211
  if (res.aborted) {
201
212
  return;
202
213
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/uWebSocketsTransport.ts"],
4
- "sourcesContent": ["import querystring, { type ParsedUrlQuery } from 'querystring';\nimport uWebSockets, { type WebSocket } from 'uWebSockets.js';\nimport type express from 'express';\n\nimport { type AuthContext, Transport, matchMaker, Protocol, getBearerToken, debugAndPrintError, spliceOne, connectClientToRoom, CloseCode, isDevMode, type Router } from '@colyseus/core';\nimport { uWebSocketClient, uWebSocketWrapper } from './uWebSocketClient.ts';\nimport { Deferred } from '@colyseus/core';\n\nconst uWebSocketsExpress = new Deferred<typeof import('uwebsockets-express')>;\nlet uWebSocketsExpressModule: typeof import('uwebsockets-express') | undefined = undefined;\nimport('uwebsockets-express')\n .then((module) => uWebSocketsExpress.resolve(module))\n .catch((error) => uWebSocketsExpress.reject(error));\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\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 private _expressApp?: express.Application;\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 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 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: headers['x-real-ip'] ?? headers['x-forwarded-for'] ?? 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: WebSocket<any>) => {\n // ws.pingCount = 0;\n await this.onConnection(ws as RawWebSocketClient);\n },\n\n // pong: (ws: RawWebSocketClient) => {\n // ws.pingCount = 0;\n // },\n\n close: (ws: WebSocket<any>, code: number, message: ArrayBuffer) => {\n // remove from client list\n spliceOne(this.clients, this.clients.indexOf(ws as RawWebSocketClient));\n\n const clientWrapper = this.clientWrappers.get(ws as RawWebSocketClient);\n if (clientWrapper) {\n this.clientWrappers.delete(ws as RawWebSocketClient);\n\n // emit 'close' on wrapper\n clientWrapper.emit('close', code);\n }\n },\n\n message: (ws: WebSocket<any>, message: ArrayBuffer, isBinary: boolean) => {\n // emit 'message' on wrapper\n this.clientWrappers.get(ws as RawWebSocketClient)?.emit('message', Buffer.from(message));\n },\n\n });\n }\n\n public getExpressApp(): Promise<express.Application> | express.Application {\n if (!this._expressApp) {\n return new Promise(async (resolve, reject) => {\n try {\n const module = await uWebSocketsExpress;\n uWebSocketsExpressModule = module;\n\n // Temporarily stub `app.any` to prevent uwebsockets-express Application.init()\n // from registering its own catch-all handler \u2014 we manage HTTP routing ourselves\n // in bindRouter().\n const originalAny = this.app.any;\n this.app.any = (() => this.app) as any;\n this._expressApp = (module.default(this.app) as unknown) as express.Application;\n this.app.any = originalAny;\n resolve(this._expressApp);\n } catch (error) {\n reject(error);\n console.warn(\"\");\n console.warn(\"\u274C Error: could not initialize express.\");\n console.warn(\"\");\n console.warn(\" For Express v5, use:\");\n console.warn(\" \uD83D\uDC49 npm install --save uwebsockets-express@^2.0.1\");\n console.warn(\"\");\n console.warn(\" For Express v4, use:\");\n console.warn(\" \uD83D\uDC49 npm install --save uwebsockets-express@^1.4.1\");\n console.warn(\"\");\n process.exit();\n }\n });\n }\n return this._expressApp;\n }\n\n public bindRouter(router: Router) {\n const getCorsHeaders = (requestHeaders: Headers) => {\n return Object.assign(\n {},\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders(requestHeaders)\n );\n }\n\n const writeCorsHeaders = (res: uWebSockets.HttpResponse, requestHeaders: Headers) => {\n // skip if aborted\n if (res.aborted) { return; }\n\n const headers = getCorsHeaders(requestHeaders);\n\n for (const header in headers) {\n res.writeHeader(header, headers[header].toString());\n }\n\n return true;\n }\n\n this.app.options(\"/*\", (res, req) => {\n res.onAborted(() => res.aborted = true);\n\n // cache all headers\n const reqHeaders = new Headers();\n req.forEach((key, value) => reqHeaders.set(key, value));\n\n res.cork(() => {\n res.writeStatus(\"204 No Content\");\n writeCorsHeaders(res, reqHeaders);\n res.end();\n });\n });\n\n this.app.any('/*', async (res, req) => {\n const abortController = new AbortController();\n\n res.onAborted(() => {\n abortController.abort();\n res.aborted = true;\n });\n\n // cache all headers and request info synchronously\n // (uWebSockets.js req is only valid in the synchronous callback scope)\n const headers = new Headers();\n req.forEach((key, value) => headers.set(key, value));\n\n const method = req.getMethod().toUpperCase();\n const url = req.getUrl();\n const query = req.getQuery();\n const remoteAddress = res.getRemoteAddressAsText();\n\n // check if the route is defined in the router\n // if so, use the router handler, otherwise fallback to express\n if (router.findRoute(method, url) !== undefined) {\n const requestInit: RequestInit = {\n method,\n referrer: headers.get('referer') || undefined,\n keepalive: headers.get('keep-alive') === 'true',\n headers,\n signal: abortController.signal,\n };\n\n // read request body\n if (method !== \"GET\" && method !== \"HEAD\") {\n let body: Buffer = undefined;\n\n // uWebSockets.js `HttpRequest` does not provide 'getData', must aggregate POST body via HttpResponse\n await new Promise<void>((resolve) => {\n res.onData((ab, isLast) => {\n const chunk = Buffer.from(ab);\n if (body === undefined) {\n body = Buffer.from(chunk);\n } else {\n body = Buffer.concat([body, chunk]);\n }\n if (isLast) {\n resolve();\n }\n });\n });\n\n requestInit.body = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer;\n }\n\n const fullUrl = `http://${headers.get('host') || 'localhost'}${url}${(query ? `?${query}` : '')}`;\n const response = await router.handler(new Request(fullUrl, requestInit));\n\n // skip if aborted\n if (res.aborted) { return; }\n\n // read response body before cork (cork callback must be synchronous)\n const responseBody = await response.arrayBuffer();\n\n // writeStatus() must be called before writeHeader() in uWebSockets.js\n res.cork(() => {\n res.writeStatus(`${response.status} ${response.statusText}`);\n writeCorsHeaders(res, headers);\n response.headers.forEach((value, key) => {\n if (key.toLowerCase() !== 'content-length') {\n res.writeHeader(key, value);\n }\n });\n res.end(responseBody);\n });\n\n } else if (this._expressApp) {\n // skip if already aborted\n if (res.aborted) { return; }\n\n const corsHeaders = getCorsHeaders(headers);\n\n const ereq = new uWebSocketsExpressModule.IncomingMessage(req, res, this._expressApp as any, {\n headers: Object.fromEntries((headers as any).entries()),\n method,\n url,\n query,\n remoteAddress\n });\n const eres = new uWebSocketsExpressModule.ServerResponse(res, req, this._expressApp);\n\n // Propagate uWS abort to the Express response wrapper.\n // When the client disconnects, mark the wrapper as finished\n // so it won't try to write to the already-aborted uWS response.\n // (fixes: \"uWS.HttpResponse must not be accessed after onAborted callback\")\n abortController.signal.addEventListener('abort', () => {\n eres.finished = true;\n // @ts-ignore\n eres.writableEnded = true;\n });\n\n // Apply CORS headers through the Express response wrapper\n for (const header in corsHeaders) {\n eres.setHeader(header, corsHeaders[header].toString());\n }\n\n // Read the request body from uWebSockets before passing to express\n // (uWebSockets requires res.onData() to be called to consume the body)\n await ereq._readBody();\n\n // skip if aborted during body read\n if (res.aborted) { return; }\n\n this._expressApp['handle'](ereq, eres);\n }\n });\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 };\n\n if (typeof (port) === \"string\") {\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 }\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 // @ts-ignore\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 // 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 // uWS throws \"Invalid access of closed uWS.WebSocket\" if the socket closed between the readyState check and the end()/close() call.\n const timeout = setTimeout(() => {\n try { rawClient.close(); } catch (e: any) {}\n }, 1000);\n wrapper.on('message', (_) => {\n try { rawClient.send(new Uint8Array([Protocol.PING]), true); } catch (e: any) {}\n });\n wrapper.on('close', () => clearTimeout(timeout));\n return;\n }\n\n const room = matchMaker.getLocalRoomById(roomId);\n const client = new uWebSocketClient(sessionId, wrapper);\n const reconnectionToken = searchParams.reconnectionToken as string;\n const skipHandshake = (searchParams.skipHandshake !== undefined);\n\n try {\n await connectClientToRoom(room, client, rawClient.context, {\n reconnectionToken,\n skipHandshake\n });\n\n } catch (e: any) {\n debugAndPrintError(e);\n\n // send error code to client then terminate\n client.error(e.code, e.message, () => {\n // uWS throws \"Invalid access of closed uWS.WebSocket\" if the socket closed between the readyState check and the end()/close() call.\n // Use MAY_TRY_RECONNECT in devMode so the SDK retries \u2014 the seat\n // may not be reserved yet during HMR reload.\n try {\n rawClient.end(reconnectionToken\n ? (isDevMode)\n ? CloseCode.MAY_TRY_RECONNECT\n : CloseCode.FAILED_TO_RECONNECT\n : CloseCode.WITH_ERROR);\n } catch (e: any) {}\n });\n }\n }\n\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import querystring, { type ParsedUrlQuery } from 'querystring';\nimport uWebSockets, { type WebSocket } from 'uWebSockets.js';\nimport type express from 'express';\n\nimport { type AuthContext, Transport, matchMaker, Protocol, getBearerToken, debugAndPrintError, spliceOne, connectClientToRoom, CloseCode, isDevMode, type Router } from '@colyseus/core';\nimport { uWebSocketClient, uWebSocketWrapper } from './uWebSocketClient.ts';\nimport { Deferred } from '@colyseus/core';\n\nconst uWebSocketsExpress = new Deferred<typeof import('uwebsockets-express')>;\nlet uWebSocketsExpressModule: typeof import('uwebsockets-express') | undefined = undefined;\nimport('uwebsockets-express')\n .then((module) => uWebSocketsExpress.resolve(module))\n .catch((error) => uWebSocketsExpress.reject(error));\n\nconst noop = () => {};\n\nexport type TransportOptions = Omit<uWebSockets.WebSocketBehavior<any>, \"upgrade\" | \"open\" | \"pong\" | \"close\" | \"message\"> & {\n /**\n * Maximum time (in milliseconds) allowed while reading an HTTP request body\n * before the request is rejected with \"408 Request Timeout\". (default: 500)\n */\n readBodyMaxTime?: number,\n};\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\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 private _expressApp?: express.Application;\n private _readBodyMaxTime?: number;\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 // not a uWS.WebSocketBehavior option \u2014 must not be spread into app.ws()\n const { readBodyMaxTime, ...wsOptions } = options;\n this._readBodyMaxTime = readBodyMaxTime;\n\n if (wsOptions.maxBackpressure === undefined) {\n wsOptions.maxBackpressure = 1024 * 1024;\n }\n\n if (wsOptions.compression === undefined) {\n wsOptions.compression = uWebSockets.DISABLED;\n }\n\n if (wsOptions.maxPayloadLength === undefined) {\n wsOptions.maxPayloadLength = 4 * 1024;\n }\n\n if (wsOptions.sendPingsAutomatically === undefined) {\n wsOptions.sendPingsAutomatically = true;\n }\n\n this.app.ws('/*', {\n ...wsOptions,\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: headers['x-real-ip'] ?? headers['x-forwarded-for'] ?? 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: WebSocket<any>) => {\n // ws.pingCount = 0;\n await this.onConnection(ws as RawWebSocketClient);\n },\n\n // pong: (ws: RawWebSocketClient) => {\n // ws.pingCount = 0;\n // },\n\n close: (ws: WebSocket<any>, code: number, message: ArrayBuffer) => {\n // remove from client list\n spliceOne(this.clients, this.clients.indexOf(ws as RawWebSocketClient));\n\n const clientWrapper = this.clientWrappers.get(ws as RawWebSocketClient);\n if (clientWrapper) {\n this.clientWrappers.delete(ws as RawWebSocketClient);\n\n // emit 'close' on wrapper\n clientWrapper.emit('close', code);\n }\n },\n\n message: (ws: WebSocket<any>, message: ArrayBuffer, isBinary: boolean) => {\n // emit 'message' on wrapper\n this.clientWrappers.get(ws as RawWebSocketClient)?.emit('message', Buffer.from(message));\n },\n\n });\n }\n\n public getExpressApp(): Promise<express.Application> | express.Application {\n if (!this._expressApp) {\n return new Promise(async (resolve, reject) => {\n try {\n const module = await uWebSocketsExpress;\n uWebSocketsExpressModule = module;\n\n // Temporarily stub `app.any` to prevent uwebsockets-express Application.init()\n // from registering its own catch-all handler \u2014 we manage HTTP routing ourselves\n // in bindRouter().\n const originalAny = this.app.any;\n this.app.any = (() => this.app) as any;\n this._expressApp = (module.default(this.app, { readBodyMaxTime: this._readBodyMaxTime }) as unknown) as express.Application;\n this.app.any = originalAny;\n resolve(this._expressApp);\n } catch (error) {\n reject(error);\n console.warn(\"\");\n console.warn(\"\u274C Error: could not initialize express.\");\n console.warn(\"\");\n console.warn(\" For Express v5, use:\");\n console.warn(\" \uD83D\uDC49 npm install --save uwebsockets-express@^2.0.1\");\n console.warn(\"\");\n console.warn(\" For Express v4, use:\");\n console.warn(\" \uD83D\uDC49 npm install --save uwebsockets-express@^1.4.1\");\n console.warn(\"\");\n process.exit();\n }\n });\n }\n return this._expressApp;\n }\n\n public bindRouter(router: Router) {\n const getCorsHeaders = (requestHeaders: Headers) => {\n return Object.assign(\n {},\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders(requestHeaders)\n );\n }\n\n const writeCorsHeaders = (res: uWebSockets.HttpResponse, requestHeaders: Headers) => {\n // skip if aborted\n if (res.aborted) { return; }\n\n const headers = getCorsHeaders(requestHeaders);\n\n for (const header in headers) {\n res.writeHeader(header, headers[header].toString());\n }\n\n return true;\n }\n\n this.app.options(\"/*\", (res, req) => {\n res.onAborted(() => res.aborted = true);\n\n // cache all headers\n const reqHeaders = new Headers();\n req.forEach((key, value) => reqHeaders.set(key, value));\n\n res.cork(() => {\n res.writeStatus(\"204 No Content\");\n writeCorsHeaders(res, reqHeaders);\n res.end();\n });\n });\n\n this.app.any('/*', async (res, req) => {\n const abortController = new AbortController();\n\n res.onAborted(() => {\n abortController.abort();\n res.aborted = true;\n });\n\n // cache all headers and request info synchronously\n // (uWebSockets.js req is only valid in the synchronous callback scope)\n const headers = new Headers();\n req.forEach((key, value) => headers.set(key, value));\n\n const method = req.getMethod().toUpperCase();\n const url = req.getUrl();\n const query = req.getQuery();\n const remoteAddress = res.getRemoteAddressAsText();\n\n // check if the route is defined in the router\n // if so, use the router handler, otherwise fallback to express\n if (router.findRoute(method, url) !== undefined) {\n const requestInit: RequestInit = {\n method,\n referrer: headers.get('referer') || undefined,\n keepalive: headers.get('keep-alive') === 'true',\n headers,\n signal: abortController.signal,\n };\n\n // read request body\n if (method !== \"GET\" && method !== \"HEAD\") {\n let body: Buffer = undefined;\n\n // uWebSockets.js `HttpRequest` does not provide 'getData', must aggregate POST body via HttpResponse\n await new Promise<void>((resolve) => {\n res.onData((ab, isLast) => {\n const chunk = Buffer.from(ab);\n if (body === undefined) {\n body = Buffer.from(chunk);\n } else {\n body = Buffer.concat([body, chunk]);\n }\n if (isLast) {\n resolve();\n }\n });\n });\n\n requestInit.body = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer;\n }\n\n const fullUrl = `http://${headers.get('host') || 'localhost'}${url}${(query ? `?${query}` : '')}`;\n const response = await router.handler(new Request(fullUrl, requestInit));\n\n // skip if aborted\n if (res.aborted) { return; }\n\n // read response body before cork (cork callback must be synchronous)\n const responseBody = await response.arrayBuffer();\n\n // writeStatus() must be called before writeHeader() in uWebSockets.js\n res.cork(() => {\n res.writeStatus(`${response.status} ${response.statusText}`);\n writeCorsHeaders(res, headers);\n response.headers.forEach((value, key) => {\n if (key.toLowerCase() !== 'content-length') {\n res.writeHeader(key, value);\n }\n });\n res.end(responseBody);\n });\n\n } else if (this._expressApp) {\n // skip if already aborted\n if (res.aborted) { return; }\n\n const corsHeaders = getCorsHeaders(headers);\n\n const ereq = new uWebSocketsExpressModule.IncomingMessage(req, res, this._expressApp as any, {\n headers: Object.fromEntries((headers as any).entries()),\n method,\n url,\n query,\n remoteAddress\n });\n const eres = new uWebSocketsExpressModule.ServerResponse(res, req, this._expressApp);\n\n // Propagate uWS abort to the Express response wrapper.\n // When the client disconnects, mark the wrapper as finished\n // so it won't try to write to the already-aborted uWS response.\n // (fixes: \"uWS.HttpResponse must not be accessed after onAborted callback\")\n abortController.signal.addEventListener('abort', () => {\n eres.finished = true;\n // @ts-ignore\n eres.writableEnded = true;\n });\n\n // Apply CORS headers through the Express response wrapper\n for (const header in corsHeaders) {\n eres.setHeader(header, corsHeaders[header].toString());\n }\n\n // uwebsockets-express <= 2.0.2 emits a bare 'error' on body-read timeout,\n // which throws process-wide when unhandled (colyseus/uWebSockets-express#43)\n // TODO: remove once minimum uwebsockets-express is >= 2.0.3\n ereq.on('error', noop);\n\n // Read the request body from uWebSockets before passing to express\n // (uWebSockets requires res.onData() to be called to consume the body)\n try {\n await ereq._readBody();\n } catch (e: any) {\n // every rejection is a body-read timeout (versions <= 2.0.2 reject without\n // an error value); eres.end() is a no-op if the client already aborted\n eres.statusCode = 408;\n eres.end();\n return;\n }\n\n // skip if aborted during body read\n if (res.aborted) { return; }\n\n this._expressApp['handle'](ereq, eres);\n }\n });\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 };\n\n if (typeof (port) === \"string\") {\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 }\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 // @ts-ignore\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 // 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 // uWS throws \"Invalid access of closed uWS.WebSocket\" if the socket closed between the readyState check and the end()/close() call.\n const timeout = setTimeout(() => {\n try { rawClient.close(); } catch (e: any) {}\n }, 1000);\n wrapper.on('message', (_) => {\n try { rawClient.send(new Uint8Array([Protocol.PING]), true); } catch (e: any) {}\n });\n wrapper.on('close', () => clearTimeout(timeout));\n return;\n }\n\n const room = matchMaker.getLocalRoomById(roomId);\n const client = new uWebSocketClient(sessionId, wrapper);\n const reconnectionToken = searchParams.reconnectionToken as string;\n const skipHandshake = (searchParams.skipHandshake !== undefined);\n\n try {\n await connectClientToRoom(room, client, rawClient.context, {\n reconnectionToken,\n skipHandshake\n });\n\n } catch (e: any) {\n debugAndPrintError(e);\n\n // send error code to client then terminate\n client.error(e.code, e.message, () => {\n // uWS throws \"Invalid access of closed uWS.WebSocket\" if the socket closed between the readyState check and the end()/close() call.\n // Use MAY_TRY_RECONNECT in devMode so the SDK retries \u2014 the seat\n // may not be reserved yet during HMR reload.\n try {\n rawClient.end(reconnectionToken\n ? (isDevMode)\n ? CloseCode.MAY_TRY_RECONNECT\n : CloseCode.FAILED_TO_RECONNECT\n : CloseCode.WITH_ERROR);\n } catch (e: any) {}\n });\n }\n }\n\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["e"]
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colyseus/uwebsockets-transport",
3
- "version": "0.17.20",
3
+ "version": "0.17.21",
4
4
  "type": "module",
5
5
  "input": "./src/index.ts",
6
6
  "main": "./build/index.cjs",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "devDependencies": {
27
27
  "uwebsockets-express": "^2.0.1",
28
- "@colyseus/core": "^0.17.41"
28
+ "@colyseus/core": "^0.17.44"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "@colyseus/core": "0.17.x",
@@ -12,7 +12,15 @@ import('uwebsockets-express')
12
12
  .then((module) => uWebSocketsExpress.resolve(module))
13
13
  .catch((error) => uWebSocketsExpress.reject(error));
14
14
 
15
- export type TransportOptions = Omit<uWebSockets.WebSocketBehavior<any>, "upgrade" | "open" | "pong" | "close" | "message">;
15
+ const noop = () => {};
16
+
17
+ export type TransportOptions = Omit<uWebSockets.WebSocketBehavior<any>, "upgrade" | "open" | "pong" | "close" | "message"> & {
18
+ /**
19
+ * Maximum time (in milliseconds) allowed while reading an HTTP request body
20
+ * before the request is rejected with "408 Request Timeout". (default: 500)
21
+ */
22
+ readBodyMaxTime?: number,
23
+ };
16
24
 
17
25
  type RawWebSocketClient = uWebSockets.WebSocket<any> & {
18
26
  url: string,
@@ -29,6 +37,7 @@ export class uWebSocketsTransport extends Transport {
29
37
  private _listeningSocket: any;
30
38
  private _originalRawSend: typeof uWebSocketClient.prototype.raw | null = null;
31
39
  private _expressApp?: express.Application;
40
+ private _readBodyMaxTime?: number;
32
41
 
33
42
  constructor(options: TransportOptions = {}, appOptions: uWebSockets.AppOptions = {}) {
34
43
  super();
@@ -37,24 +46,28 @@ export class uWebSocketsTransport extends Transport {
37
46
  ? uWebSockets.SSLApp(appOptions)
38
47
  : uWebSockets.App(appOptions);
39
48
 
40
- if (options.maxBackpressure === undefined) {
41
- options.maxBackpressure = 1024 * 1024;
49
+ // not a uWS.WebSocketBehavior option — must not be spread into app.ws()
50
+ const { readBodyMaxTime, ...wsOptions } = options;
51
+ this._readBodyMaxTime = readBodyMaxTime;
52
+
53
+ if (wsOptions.maxBackpressure === undefined) {
54
+ wsOptions.maxBackpressure = 1024 * 1024;
42
55
  }
43
56
 
44
- if (options.compression === undefined) {
45
- options.compression = uWebSockets.DISABLED;
57
+ if (wsOptions.compression === undefined) {
58
+ wsOptions.compression = uWebSockets.DISABLED;
46
59
  }
47
60
 
48
- if (options.maxPayloadLength === undefined) {
49
- options.maxPayloadLength = 4 * 1024;
61
+ if (wsOptions.maxPayloadLength === undefined) {
62
+ wsOptions.maxPayloadLength = 4 * 1024;
50
63
  }
51
64
 
52
- if (options.sendPingsAutomatically === undefined) {
53
- options.sendPingsAutomatically = true;
65
+ if (wsOptions.sendPingsAutomatically === undefined) {
66
+ wsOptions.sendPingsAutomatically = true;
54
67
  }
55
68
 
56
69
  this.app.ws('/*', {
57
- ...options,
70
+ ...wsOptions,
58
71
 
59
72
  upgrade: (res, req, context) => {
60
73
  // get all headers
@@ -124,7 +137,7 @@ export class uWebSocketsTransport extends Transport {
124
137
  // in bindRouter().
125
138
  const originalAny = this.app.any;
126
139
  this.app.any = (() => this.app) as any;
127
- this._expressApp = (module.default(this.app) as unknown) as express.Application;
140
+ this._expressApp = (module.default(this.app, { readBodyMaxTime: this._readBodyMaxTime }) as unknown) as express.Application;
128
141
  this.app.any = originalAny;
129
142
  resolve(this._expressApp);
130
143
  } catch (error) {
@@ -283,9 +296,22 @@ export class uWebSocketsTransport extends Transport {
283
296
  eres.setHeader(header, corsHeaders[header].toString());
284
297
  }
285
298
 
299
+ // uwebsockets-express <= 2.0.2 emits a bare 'error' on body-read timeout,
300
+ // which throws process-wide when unhandled (colyseus/uWebSockets-express#43)
301
+ // TODO: remove once minimum uwebsockets-express is >= 2.0.3
302
+ ereq.on('error', noop);
303
+
286
304
  // Read the request body from uWebSockets before passing to express
287
305
  // (uWebSockets requires res.onData() to be called to consume the body)
288
- await ereq._readBody();
306
+ try {
307
+ await ereq._readBody();
308
+ } catch (e: any) {
309
+ // every rejection is a body-read timeout (versions <= 2.0.2 reject without
310
+ // an error value); eres.end() is a no-op if the client already aborted
311
+ eres.statusCode = 408;
312
+ eres.end();
313
+ return;
314
+ }
289
315
 
290
316
  // skip if aborted during body read
291
317
  if (res.aborted) { return; }