@colyseus/bun-websockets 0.15.0-alpha.1 → 0.15.0-alpha.2

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.
@@ -79,7 +79,7 @@ class BunWebSockets extends import_core.Transport {
79
79
  await self.onConnection(ws);
80
80
  },
81
81
  message(ws, message) {
82
- this.clientWrappers.get(ws)?.emit("message", message);
82
+ self.clientWrappers.get(ws)?.emit("message", message);
83
83
  },
84
84
  close(ws, code, reason) {
85
85
  (0, import_core.spliceOne)(self.clients, self.clients.indexOf(ws));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/BunWebSockets.ts"],
4
- "sourcesContent": ["/// <reference types=\"bun-types\" />\nimport Bun, { Server, ServerWebSocket, WebSocketHandler } from \"bun\";\n\nimport http from 'http';\n\nimport { DummyServer, ErrorCode, matchMaker, Transport, debugAndPrintError, spliceOne, ServerError } from '@colyseus/core';\nimport { WebSocketClient, WebSocketWrapper } from './WebSocketClient';\n\nexport type TransportOptions = Partial<Omit<WebSocketHandler, \"message\" | \"open\" | \"drain\" | \"close\" | \"ping\" | \"pong\">>;\n\ninterface WebSocketData {\n url: URL;\n // query: string,\n // headers: { [key: string]: string },\n // connection: { remoteAddress: string },\n}\n\nexport class BunWebSockets extends Transport {\n public bunServer: Bun.Server;\n\n protected clients: ServerWebSocket<WebSocketData>[] = [];\n protected clientWrappers = new WeakMap<ServerWebSocket<WebSocketData>, WebSocketWrapper>();\n\n constructor(private options: TransportOptions = {}) {\n super();\n\n // Adding a mock object for Transport.server\n if (!this.server) {\n this.server = new DummyServer();\n }\n }\n\n public listen(port: number | string, hostname?: string, backlog?: number, listeningListener?: () => void) {\n const self = this;\n\n this.bunServer = Bun.serve<WebSocketData>({\n port,\n hostname,\n\n async fetch(req, server) {\n const url = new URL(req.url);\n\n if (url.pathname.startsWith(`/${matchMaker.controller.matchmakeRoute}`)) {\n try {\n const [code, response, headers] = await self.handleMatchMakeRequest(req, server, url);\n //\n // success response\n //\n return new Response(response, {\n status: code,\n headers: Object.assign(\n headers,\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders.call(undefined, req)\n )\n });\n\n } catch (e) {\n //\n // error response\n //\n return new Response(JSON.stringify({ code: e.code, error: e.message }), {\n status: e.code || ErrorCode.MATCHMAKE_UNHANDLED,\n headers: Object.assign(\n { 'Content-Type': 'application/json' },\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders.call(undefined, req)\n )\n });\n }\n\n\n } else {\n // req.headers.get(\"Cookie\");\n server.upgrade(req, { data: { url } });\n\n return undefined;\n }\n },\n\n websocket: {\n ...this.options,\n\n async open(ws) {\n await self.onConnection(ws);\n },\n\n message(ws, message) {\n // this.clientWrappers.get(ws)?.emit('message', Buffer.from(message.slice(0)));\n this.clientWrappers.get(ws)?.emit('message', 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\n listeningListener?.();\n\n // Mocking Transport.server behaviour, https://github.com/colyseus/colyseus/issues/458\n // @ts-ignore\n this.server.emit(\"listening\");\n\n return this;\n }\n\n public shutdown() {\n if (this.bunServer) {\n this.bunServer.stop(true);\n\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 const originalRawSend = WebSocketClient.prototype.raw;\n WebSocketClient.prototype.raw = function () {\n setTimeout(() => originalRawSend.apply(this, arguments), 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 parsedURL = new URL(rawClient.data.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.getRoomById(roomId);\n const client = new WebSocketClient(sessionId, wrapper);\n\n //\n // TODO: DRY code below with all transports\n //\n\n try {\n if (!room || !room.hasReservedSeat(sessionId, parsedURL.searchParams.get(\"reconnectionToken\") as string)) {\n throw new Error('seat reservation expired.');\n }\n\n await room._onJoin(client, rawClient as unknown as http.IncomingMessage);\n\n } catch (e) {\n debugAndPrintError(e);\n\n // send error code to client then terminate\n client.error(e.code, e.message, () => rawClient.close());\n }\n }\n\n protected async handleMatchMakeRequest (req: Request, server: Server, url: URL): Promise<[number, string, { [key: string]: string }]> {\n switch (req.method) {\n case 'OPTIONS': return [200, undefined, {}];\n\n case 'GET': {\n const matchedParams = url.pathname.match(matchMaker.controller.allowedRoomNameChars);\n const roomName = matchedParams.length > 1 ? matchedParams[matchedParams.length - 1] : \"\";\n\n return [\n 200,\n JSON.stringify(await matchMaker.controller.getAvailableRooms(roomName || '')), {\n 'Content-Type': 'application/json'\n }\n ];\n }\n\n case 'POST': {\n // do not accept matchmaking requests if already shutting down\n if (matchMaker.isGracefullyShuttingDown) {\n throw new ServerError(503, \"server is shutting down\");\n }\n\n const matchedParams = url.pathname.match(matchMaker.controller.allowedRoomNameChars);\n const matchmakeIndex = matchedParams.indexOf(matchMaker.controller.matchmakeRoute);\n\n const clientOptions = Bun.readableStreamToJSON(req.body);\n\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 return [\n 200,\n JSON.stringify(await matchMaker.controller.invokeMethod(method, roomName, clientOptions)), {\n 'Content-Type': 'application/json'\n }\n ];\n }\n\n default: return undefined;\n }\n }\n\n}\n"],
4
+ "sourcesContent": ["/// <reference types=\"bun-types\" />\nimport Bun, { Server, ServerWebSocket, WebSocketHandler } from \"bun\";\n\nimport http from 'http';\n\nimport { DummyServer, ErrorCode, matchMaker, Transport, debugAndPrintError, spliceOne, ServerError } from '@colyseus/core';\nimport { WebSocketClient, WebSocketWrapper } from './WebSocketClient';\n\nexport type TransportOptions = Partial<Omit<WebSocketHandler, \"message\" | \"open\" | \"drain\" | \"close\" | \"ping\" | \"pong\">>;\n\ninterface WebSocketData {\n url: URL;\n // query: string,\n // headers: { [key: string]: string },\n // connection: { remoteAddress: string },\n}\n\nexport class BunWebSockets extends Transport {\n public bunServer: Bun.Server;\n\n protected clients: ServerWebSocket<WebSocketData>[] = [];\n protected clientWrappers = new WeakMap<ServerWebSocket<WebSocketData>, WebSocketWrapper>();\n\n constructor(private options: TransportOptions = {}) {\n super();\n\n // Adding a mock object for Transport.server\n if (!this.server) {\n this.server = new DummyServer();\n }\n }\n\n public listen(port: number | string, hostname?: string, backlog?: number, listeningListener?: () => void) {\n const self = this;\n\n this.bunServer = Bun.serve<WebSocketData>({\n port,\n hostname,\n\n async fetch(req, server) {\n const url = new URL(req.url);\n\n if (url.pathname.startsWith(`/${matchMaker.controller.matchmakeRoute}`)) {\n try {\n const [code, response, headers] = await self.handleMatchMakeRequest(req, server, url);\n //\n // success response\n //\n return new Response(response, {\n status: code,\n headers: Object.assign(\n headers,\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders.call(undefined, req)\n )\n });\n\n } catch (e) {\n //\n // error response\n //\n return new Response(JSON.stringify({ code: e.code, error: e.message }), {\n status: e.code || ErrorCode.MATCHMAKE_UNHANDLED,\n headers: Object.assign(\n { 'Content-Type': 'application/json' },\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders.call(undefined, req)\n )\n });\n }\n\n\n } else {\n // req.headers.get(\"Cookie\");\n server.upgrade(req, { data: { url } });\n\n return undefined;\n }\n },\n\n websocket: {\n ...this.options,\n\n async open(ws) {\n await self.onConnection(ws);\n },\n\n message(ws, message) {\n // this.clientWrappers.get(ws)?.emit('message', Buffer.from(message.slice(0)));\n self.clientWrappers.get(ws)?.emit('message', 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\n listeningListener?.();\n\n // Mocking Transport.server behaviour, https://github.com/colyseus/colyseus/issues/458\n // @ts-ignore\n this.server.emit(\"listening\");\n\n return this;\n }\n\n public shutdown() {\n if (this.bunServer) {\n this.bunServer.stop(true);\n\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 const originalRawSend = WebSocketClient.prototype.raw;\n WebSocketClient.prototype.raw = function () {\n setTimeout(() => originalRawSend.apply(this, arguments), 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 parsedURL = new URL(rawClient.data.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.getRoomById(roomId);\n const client = new WebSocketClient(sessionId, wrapper);\n\n //\n // TODO: DRY code below with all transports\n //\n\n try {\n if (!room || !room.hasReservedSeat(sessionId, parsedURL.searchParams.get(\"reconnectionToken\") as string)) {\n throw new Error('seat reservation expired.');\n }\n\n await room._onJoin(client, rawClient as unknown as http.IncomingMessage);\n\n } catch (e) {\n debugAndPrintError(e);\n\n // send error code to client then terminate\n client.error(e.code, e.message, () => rawClient.close());\n }\n }\n\n protected async handleMatchMakeRequest (req: Request, server: Server, url: URL): Promise<[number, string, { [key: string]: string }]> {\n switch (req.method) {\n case 'OPTIONS': return [200, undefined, {}];\n\n case 'GET': {\n const matchedParams = url.pathname.match(matchMaker.controller.allowedRoomNameChars);\n const roomName = matchedParams.length > 1 ? matchedParams[matchedParams.length - 1] : \"\";\n\n return [\n 200,\n JSON.stringify(await matchMaker.controller.getAvailableRooms(roomName || '')), {\n 'Content-Type': 'application/json'\n }\n ];\n }\n\n case 'POST': {\n // do not accept matchmaking requests if already shutting down\n if (matchMaker.isGracefullyShuttingDown) {\n throw new ServerError(503, \"server is shutting down\");\n }\n\n const matchedParams = url.pathname.match(matchMaker.controller.allowedRoomNameChars);\n const matchmakeIndex = matchedParams.indexOf(matchMaker.controller.matchmakeRoute);\n\n const clientOptions = Bun.readableStreamToJSON(req.body);\n\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 return [\n 200,\n JSON.stringify(await matchMaker.controller.invokeMethod(method, roomName, clientOptions)), {\n 'Content-Type': 'application/json'\n }\n ];\n }\n\n default: return undefined;\n }\n }\n\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["Bun"]
7
7
  }
@@ -51,7 +51,7 @@ class BunWebSockets extends Transport {
51
51
  await self.onConnection(ws);
52
52
  },
53
53
  message(ws, message) {
54
- this.clientWrappers.get(ws)?.emit("message", message);
54
+ self.clientWrappers.get(ws)?.emit("message", message);
55
55
  },
56
56
  close(ws, code, reason) {
57
57
  spliceOne(self.clients, self.clients.indexOf(ws));
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/BunWebSockets.ts"],
4
- "sourcesContent": ["/// <reference types=\"bun-types\" />\nimport Bun, { Server, ServerWebSocket, WebSocketHandler } from \"bun\";\n\nimport http from 'http';\n\nimport { DummyServer, ErrorCode, matchMaker, Transport, debugAndPrintError, spliceOne, ServerError } from '@colyseus/core';\nimport { WebSocketClient, WebSocketWrapper } from './WebSocketClient';\n\nexport type TransportOptions = Partial<Omit<WebSocketHandler, \"message\" | \"open\" | \"drain\" | \"close\" | \"ping\" | \"pong\">>;\n\ninterface WebSocketData {\n url: URL;\n // query: string,\n // headers: { [key: string]: string },\n // connection: { remoteAddress: string },\n}\n\nexport class BunWebSockets extends Transport {\n public bunServer: Bun.Server;\n\n protected clients: ServerWebSocket<WebSocketData>[] = [];\n protected clientWrappers = new WeakMap<ServerWebSocket<WebSocketData>, WebSocketWrapper>();\n\n constructor(private options: TransportOptions = {}) {\n super();\n\n // Adding a mock object for Transport.server\n if (!this.server) {\n this.server = new DummyServer();\n }\n }\n\n public listen(port: number | string, hostname?: string, backlog?: number, listeningListener?: () => void) {\n const self = this;\n\n this.bunServer = Bun.serve<WebSocketData>({\n port,\n hostname,\n\n async fetch(req, server) {\n const url = new URL(req.url);\n\n if (url.pathname.startsWith(`/${matchMaker.controller.matchmakeRoute}`)) {\n try {\n const [code, response, headers] = await self.handleMatchMakeRequest(req, server, url);\n //\n // success response\n //\n return new Response(response, {\n status: code,\n headers: Object.assign(\n headers,\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders.call(undefined, req)\n )\n });\n\n } catch (e) {\n //\n // error response\n //\n return new Response(JSON.stringify({ code: e.code, error: e.message }), {\n status: e.code || ErrorCode.MATCHMAKE_UNHANDLED,\n headers: Object.assign(\n { 'Content-Type': 'application/json' },\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders.call(undefined, req)\n )\n });\n }\n\n\n } else {\n // req.headers.get(\"Cookie\");\n server.upgrade(req, { data: { url } });\n\n return undefined;\n }\n },\n\n websocket: {\n ...this.options,\n\n async open(ws) {\n await self.onConnection(ws);\n },\n\n message(ws, message) {\n // this.clientWrappers.get(ws)?.emit('message', Buffer.from(message.slice(0)));\n this.clientWrappers.get(ws)?.emit('message', 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\n listeningListener?.();\n\n // Mocking Transport.server behaviour, https://github.com/colyseus/colyseus/issues/458\n // @ts-ignore\n this.server.emit(\"listening\");\n\n return this;\n }\n\n public shutdown() {\n if (this.bunServer) {\n this.bunServer.stop(true);\n\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 const originalRawSend = WebSocketClient.prototype.raw;\n WebSocketClient.prototype.raw = function () {\n setTimeout(() => originalRawSend.apply(this, arguments), 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 parsedURL = new URL(rawClient.data.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.getRoomById(roomId);\n const client = new WebSocketClient(sessionId, wrapper);\n\n //\n // TODO: DRY code below with all transports\n //\n\n try {\n if (!room || !room.hasReservedSeat(sessionId, parsedURL.searchParams.get(\"reconnectionToken\") as string)) {\n throw new Error('seat reservation expired.');\n }\n\n await room._onJoin(client, rawClient as unknown as http.IncomingMessage);\n\n } catch (e) {\n debugAndPrintError(e);\n\n // send error code to client then terminate\n client.error(e.code, e.message, () => rawClient.close());\n }\n }\n\n protected async handleMatchMakeRequest (req: Request, server: Server, url: URL): Promise<[number, string, { [key: string]: string }]> {\n switch (req.method) {\n case 'OPTIONS': return [200, undefined, {}];\n\n case 'GET': {\n const matchedParams = url.pathname.match(matchMaker.controller.allowedRoomNameChars);\n const roomName = matchedParams.length > 1 ? matchedParams[matchedParams.length - 1] : \"\";\n\n return [\n 200,\n JSON.stringify(await matchMaker.controller.getAvailableRooms(roomName || '')), {\n 'Content-Type': 'application/json'\n }\n ];\n }\n\n case 'POST': {\n // do not accept matchmaking requests if already shutting down\n if (matchMaker.isGracefullyShuttingDown) {\n throw new ServerError(503, \"server is shutting down\");\n }\n\n const matchedParams = url.pathname.match(matchMaker.controller.allowedRoomNameChars);\n const matchmakeIndex = matchedParams.indexOf(matchMaker.controller.matchmakeRoute);\n\n const clientOptions = Bun.readableStreamToJSON(req.body);\n\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 return [\n 200,\n JSON.stringify(await matchMaker.controller.invokeMethod(method, roomName, clientOptions)), {\n 'Content-Type': 'application/json'\n }\n ];\n }\n\n default: return undefined;\n }\n }\n\n}\n"],
4
+ "sourcesContent": ["/// <reference types=\"bun-types\" />\nimport Bun, { Server, ServerWebSocket, WebSocketHandler } from \"bun\";\n\nimport http from 'http';\n\nimport { DummyServer, ErrorCode, matchMaker, Transport, debugAndPrintError, spliceOne, ServerError } from '@colyseus/core';\nimport { WebSocketClient, WebSocketWrapper } from './WebSocketClient';\n\nexport type TransportOptions = Partial<Omit<WebSocketHandler, \"message\" | \"open\" | \"drain\" | \"close\" | \"ping\" | \"pong\">>;\n\ninterface WebSocketData {\n url: URL;\n // query: string,\n // headers: { [key: string]: string },\n // connection: { remoteAddress: string },\n}\n\nexport class BunWebSockets extends Transport {\n public bunServer: Bun.Server;\n\n protected clients: ServerWebSocket<WebSocketData>[] = [];\n protected clientWrappers = new WeakMap<ServerWebSocket<WebSocketData>, WebSocketWrapper>();\n\n constructor(private options: TransportOptions = {}) {\n super();\n\n // Adding a mock object for Transport.server\n if (!this.server) {\n this.server = new DummyServer();\n }\n }\n\n public listen(port: number | string, hostname?: string, backlog?: number, listeningListener?: () => void) {\n const self = this;\n\n this.bunServer = Bun.serve<WebSocketData>({\n port,\n hostname,\n\n async fetch(req, server) {\n const url = new URL(req.url);\n\n if (url.pathname.startsWith(`/${matchMaker.controller.matchmakeRoute}`)) {\n try {\n const [code, response, headers] = await self.handleMatchMakeRequest(req, server, url);\n //\n // success response\n //\n return new Response(response, {\n status: code,\n headers: Object.assign(\n headers,\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders.call(undefined, req)\n )\n });\n\n } catch (e) {\n //\n // error response\n //\n return new Response(JSON.stringify({ code: e.code, error: e.message }), {\n status: e.code || ErrorCode.MATCHMAKE_UNHANDLED,\n headers: Object.assign(\n { 'Content-Type': 'application/json' },\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders.call(undefined, req)\n )\n });\n }\n\n\n } else {\n // req.headers.get(\"Cookie\");\n server.upgrade(req, { data: { url } });\n\n return undefined;\n }\n },\n\n websocket: {\n ...this.options,\n\n async open(ws) {\n await self.onConnection(ws);\n },\n\n message(ws, message) {\n // this.clientWrappers.get(ws)?.emit('message', Buffer.from(message.slice(0)));\n self.clientWrappers.get(ws)?.emit('message', 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\n listeningListener?.();\n\n // Mocking Transport.server behaviour, https://github.com/colyseus/colyseus/issues/458\n // @ts-ignore\n this.server.emit(\"listening\");\n\n return this;\n }\n\n public shutdown() {\n if (this.bunServer) {\n this.bunServer.stop(true);\n\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 const originalRawSend = WebSocketClient.prototype.raw;\n WebSocketClient.prototype.raw = function () {\n setTimeout(() => originalRawSend.apply(this, arguments), 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 parsedURL = new URL(rawClient.data.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.getRoomById(roomId);\n const client = new WebSocketClient(sessionId, wrapper);\n\n //\n // TODO: DRY code below with all transports\n //\n\n try {\n if (!room || !room.hasReservedSeat(sessionId, parsedURL.searchParams.get(\"reconnectionToken\") as string)) {\n throw new Error('seat reservation expired.');\n }\n\n await room._onJoin(client, rawClient as unknown as http.IncomingMessage);\n\n } catch (e) {\n debugAndPrintError(e);\n\n // send error code to client then terminate\n client.error(e.code, e.message, () => rawClient.close());\n }\n }\n\n protected async handleMatchMakeRequest (req: Request, server: Server, url: URL): Promise<[number, string, { [key: string]: string }]> {\n switch (req.method) {\n case 'OPTIONS': return [200, undefined, {}];\n\n case 'GET': {\n const matchedParams = url.pathname.match(matchMaker.controller.allowedRoomNameChars);\n const roomName = matchedParams.length > 1 ? matchedParams[matchedParams.length - 1] : \"\";\n\n return [\n 200,\n JSON.stringify(await matchMaker.controller.getAvailableRooms(roomName || '')), {\n 'Content-Type': 'application/json'\n }\n ];\n }\n\n case 'POST': {\n // do not accept matchmaking requests if already shutting down\n if (matchMaker.isGracefullyShuttingDown) {\n throw new ServerError(503, \"server is shutting down\");\n }\n\n const matchedParams = url.pathname.match(matchMaker.controller.allowedRoomNameChars);\n const matchmakeIndex = matchedParams.indexOf(matchMaker.controller.matchmakeRoute);\n\n const clientOptions = Bun.readableStreamToJSON(req.body);\n\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 return [\n 200,\n JSON.stringify(await matchMaker.controller.invokeMethod(method, roomName, clientOptions)), {\n 'Content-Type': 'application/json'\n }\n ];\n }\n\n default: return undefined;\n }\n }\n\n}\n"],
5
5
  "mappings": "AACA,OAAO,SAAwD;AAI/D,SAAS,aAAa,WAAW,YAAY,WAAW,oBAAoB,WAAW,mBAAmB;AAC1G,SAAS,iBAAiB,wBAAwB;AAW3C,MAAM,sBAAsB,UAAU;AAAA,EAM3C,YAAoB,UAA4B,CAAC,GAAG;AAClD,UAAM;AADY;AAIlB,QAAI,CAAC,KAAK,QAAQ;AAChB,WAAK,SAAS,IAAI,YAAY;AAAA,IAChC;AAAA,EACF;AAAA,EAZO;AAAA,EAEG,UAA4C,CAAC;AAAA,EAC7C,iBAAiB,oBAAI,QAA0D;AAAA,EAWlF,OAAO,MAAuB,UAAmB,SAAkB,mBAAgC;AACxG,UAAM,OAAO;AAEb,SAAK,YAAY,IAAI,MAAqB;AAAA,MACxC;AAAA,MACA;AAAA,MAEA,MAAM,MAAM,KAAK,QAAQ;AACvB,cAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAE3B,YAAI,IAAI,SAAS,WAAW,IAAI,WAAW,WAAW,gBAAgB,GAAG;AACvE,cAAI;AACF,kBAAM,CAAC,MAAM,UAAU,OAAO,IAAI,MAAM,KAAK,uBAAuB,KAAK,QAAQ,GAAG;AAIpF,mBAAO,IAAI,SAAS,UAAU;AAAA,cAC5B,QAAQ;AAAA,cACR,SAAS,OAAO;AAAA,gBACd;AAAA,gBACA,WAAW,WAAW;AAAA,gBACtB,WAAW,WAAW,eAAe,KAAK,QAAW,GAAG;AAAA,cAC1D;AAAA,YACF,CAAC;AAAA,UAEH,SAAS,GAAP;AAIA,mBAAO,IAAI,SAAS,KAAK,UAAU,EAAE,MAAM,EAAE,MAAM,OAAO,EAAE,QAAQ,CAAC,GAAG;AAAA,cACtE,QAAQ,EAAE,QAAQ,UAAU;AAAA,cAC5B,SAAS,OAAO;AAAA,gBACd,EAAE,gBAAgB,mBAAmB;AAAA,gBACrC,WAAW,WAAW;AAAA,gBACtB,WAAW,WAAW,eAAe,KAAK,QAAW,GAAG;AAAA,cAC1D;AAAA,YACF,CAAC;AAAA,UACH;AAAA,QAGF,OAAO;AAEL,iBAAO,QAAQ,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAErC,iBAAO;AAAA,QACT;AAAA,MACF;AAAA,MAEA,WAAW;AAAA,QACT,GAAG,KAAK;AAAA,QAER,MAAM,KAAK,IAAI;AACb,gBAAM,KAAK,aAAa,EAAE;AAAA,QAC5B;AAAA,QAEA,QAAQ,IAAI,SAAS;AAEnB,eAAK,eAAe,IAAI,EAAE,GAAG,KAAK,WAAW,OAAO;AAAA,QACtD;AAAA,QAEA,MAAM,IAAI,MAAM,QAAQ;AAEtB,oBAAU,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,MAEF;AAAA,IACF,CAAC;AAED,wBAAoB;AAIpB,SAAK,OAAO,KAAK,WAAW;AAE5B,WAAO;AAAA,EACT;AAAA,EAEO,WAAW;AAChB,QAAI,KAAK,WAAW;AAClB,WAAK,UAAU,KAAK,IAAI;AAGxB,WAAK,OAAO,KAAK,OAAO;AAAA,IAC1B;AAAA,EACF;AAAA,EAEO,gBAAgB,cAAsB;AAC3C,UAAM,kBAAkB,gBAAgB,UAAU;AAClD,oBAAgB,UAAU,MAAM,WAAY;AAC1C,iBAAW,MAAM,gBAAgB,MAAM,MAAM,SAAS,GAAG,YAAY;AAAA,IACvE;AAAA,EACF;AAAA,EAEA,MAAgB,aAAa,WAA2C;AACtE,UAAM,UAAU,IAAI,iBAAiB,SAAS;AAE9C,SAAK,QAAQ,KAAK,SAAS;AAC3B,SAAK,eAAe,IAAI,WAAW,OAAO;AAE1C,UAAM,YAAY,IAAI,IAAI,UAAU,KAAK,GAAG;AAE5C,UAAM,YAAY,UAAU,aAAa,IAAI,WAAW;AACxD,UAAM,mBAAmB,UAAU,SAAS,MAAM,uCAAuC;AACzF,UAAM,SAAS,oBAAoB,iBAAiB;AAEpD,UAAM,OAAO,WAAW,YAAY,MAAM;AAC1C,UAAM,SAAS,IAAI,gBAAgB,WAAW,OAAO;AAMrD,QAAI;AACF,UAAI,CAAC,QAAQ,CAAC,KAAK,gBAAgB,WAAW,UAAU,aAAa,IAAI,mBAAmB,CAAW,GAAG;AACxG,cAAM,IAAI,MAAM,2BAA2B;AAAA,MAC7C;AAEA,YAAM,KAAK,QAAQ,QAAQ,SAA4C;AAAA,IAEzE,SAAS,GAAP;AACA,yBAAmB,CAAC;AAGpB,aAAO,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,UAAU,MAAM,CAAC;AAAA,IACzD;AAAA,EACF;AAAA,EAEA,MAAgB,uBAAwB,KAAc,QAAgB,KAAgE;AACpI,YAAQ,IAAI;AAAA,WACL;AAAW,eAAO,CAAC,KAAK,QAAW,CAAC,CAAC;AAAA,WAErC,OAAO;AACV,cAAM,gBAAgB,IAAI,SAAS,MAAM,WAAW,WAAW,oBAAoB;AACnF,cAAM,WAAW,cAAc,SAAS,IAAI,cAAc,cAAc,SAAS,KAAK;AAEtF,eAAO;AAAA,UACL;AAAA,UACA,KAAK,UAAU,MAAM,WAAW,WAAW,kBAAkB,YAAY,EAAE,CAAC;AAAA,UAAG;AAAA,YAC7E,gBAAgB;AAAA,UAClB;AAAA,QACF;AAAA,MACF;AAAA,WAEK,QAAQ;AAEX,YAAI,WAAW,0BAA0B;AACvC,gBAAM,IAAI,YAAY,KAAK,yBAAyB;AAAA,QACtD;AAEA,cAAM,gBAAgB,IAAI,SAAS,MAAM,WAAW,WAAW,oBAAoB;AACnF,cAAM,iBAAiB,cAAc,QAAQ,WAAW,WAAW,cAAc;AAEjF,cAAM,gBAAgB,IAAI,qBAAqB,IAAI,IAAI;AAEvD,YAAI,kBAAkB,QAAW;AAC/B,gBAAM,IAAI,MAAM,oBAAoB;AAAA,QACtC;AAEA,cAAM,SAAS,cAAc,iBAAiB;AAC9C,cAAM,WAAW,cAAc,iBAAiB,MAAM;AAEtD,eAAO;AAAA,UACL;AAAA,UACA,KAAK,UAAU,MAAM,WAAW,WAAW,aAAa,QAAQ,UAAU,aAAa,CAAC;AAAA,UAAG;AAAA,YACzF,gBAAgB;AAAA,UAClB;AAAA,QACF;AAAA,MACF;AAAA;AAES,eAAO;AAAA;AAAA,EAEpB;AAEF;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colyseus/bun-websockets",
3
- "version": "0.15.0-alpha.1",
3
+ "version": "0.15.0-alpha.2",
4
4
  "input": "./src/index.ts",
5
5
  "main": "./build/index.js",
6
6
  "module": "./build/index.mjs",
@@ -35,5 +35,5 @@
35
35
  "publishConfig": {
36
36
  "access": "public"
37
37
  },
38
- "gitHead": "a602d77919d9e76d32a6ad3a663161bad7919b6f"
38
+ "gitHead": "fcd166a528546a253be58437249e96896d8ec1bb"
39
39
  }