@colyseus/bun-websockets 0.16.5 → 0.17.1

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.
@@ -1,8 +1,7 @@
1
- var __create = Object.create;
1
+ "use strict";
2
2
  var __defProp = Object.defineProperty;
3
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __getProtoOf = Object.getPrototypeOf;
6
5
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
6
  var __export = (target, all) => {
8
7
  for (var name in all)
@@ -16,50 +15,24 @@ var __copyProps = (to, from, except, desc) => {
16
15
  }
17
16
  return to;
18
17
  };
19
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
- // If the importer is in node compatibility mode or this is not an ESM
21
- // file that has been converted to a CommonJS file using a Babel-
22
- // compatible transform (i.e. "__esModule" has not been set), then set
23
- // "default" to the CommonJS "module.exports" for node compatibility.
24
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
- mod
26
- ));
27
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
19
  var BunWebSockets_exports = {};
29
20
  __export(BunWebSockets_exports, {
30
21
  BunWebSockets: () => BunWebSockets
31
22
  });
32
23
  module.exports = __toCommonJS(BunWebSockets_exports);
33
- var import_bun_serve_express = __toESM(require("bun-serve-express"));
24
+ var import_bun = require("bun");
34
25
  var import_core = require("@colyseus/core");
35
- var import_WebSocketClient = require("./WebSocketClient.js");
26
+ var import_WebSocketClient = require("./WebSocketClient.ts");
36
27
  class BunWebSockets extends import_core.Transport {
37
28
  constructor(options = {}) {
38
29
  super();
39
- this.options = options;
40
30
  this.clients = [];
41
31
  this.clientWrappers = /* @__PURE__ */ new WeakMap();
42
32
  this._originalRawSend = null;
33
+ this.options = {};
43
34
  const self = this;
44
- this.expressApp = (0, import_bun_serve_express.default)({
45
- websocket: {
46
- ...this.options,
47
- async open(ws) {
48
- await self.onConnection(ws);
49
- },
50
- message(ws, message) {
51
- self.clientWrappers.get(ws)?.emit("message", message);
52
- },
53
- close(ws, code, reason) {
54
- (0, import_core.spliceOne)(self.clients, self.clients.indexOf(ws));
55
- const clientWrapper = self.clientWrappers.get(ws);
56
- if (clientWrapper) {
57
- self.clientWrappers.delete(ws);
58
- clientWrapper.emit("close", code);
59
- }
60
- }
61
- }
62
- });
35
+ this.options = options;
63
36
  if (!this.server) {
64
37
  this.server = new import_core.HttpServerMock();
65
38
  }
@@ -68,7 +41,6 @@ class BunWebSockets extends import_core.Transport {
68
41
  this._listening = this.expressApp.listen(port, listeningListener);
69
42
  this.expressApp.use(`/${import_core.matchMaker.controller.matchmakeRoute}`, async (req, res) => {
70
43
  try {
71
- await this.handleMatchMakeRequest(req, res);
72
44
  } catch (e) {
73
45
  res.status(500).json({
74
46
  code: e.code,
@@ -104,82 +76,30 @@ class BunWebSockets extends import_core.Transport {
104
76
  const sessionId = parsedURL.searchParams.get("sessionId");
105
77
  const processAndRoomId = parsedURL.pathname.match(/\/[a-zA-Z0-9_\-]+\/([a-zA-Z0-9_\-]+)$/);
106
78
  const roomId = processAndRoomId && processAndRoomId[1];
79
+ if (!sessionId && !roomId) {
80
+ const timeout = setTimeout(() => rawClient.close(import_core.CloseCode.NORMAL_CLOSURE), 1e3);
81
+ wrapper.on("message", (_) => rawClient.send(new Uint8Array([import_core.Protocol.PING])));
82
+ wrapper.on("close", () => clearTimeout(timeout));
83
+ return;
84
+ }
107
85
  const room = import_core.matchMaker.getLocalRoomById(roomId);
108
86
  const client = new import_WebSocketClient.WebSocketClient(sessionId, wrapper);
87
+ const reconnectionToken = parsedURL.searchParams.get("reconnectionToken");
88
+ const skipHandshake = parsedURL.searchParams.has("skipHandshake");
109
89
  try {
110
- if (!room || !room.hasReservedSeat(sessionId, parsedURL.searchParams.get("reconnectionToken"))) {
111
- throw new Error("seat reservation expired.");
112
- }
113
- await room._onJoin(client, {
90
+ await (0, import_core.connectClientToRoom)(room, client, {
114
91
  token: parsedURL.searchParams.get("_authToken") ?? (0, import_core.getBearerToken)(rawClient.data.headers["authorization"]),
115
92
  headers: rawClient.data.headers,
116
93
  ip: rawClient.data.headers["x-real-ip"] ?? rawClient.data.headers["x-forwarded-for"] ?? rawClient.remoteAddress
94
+ }, {
95
+ reconnectionToken,
96
+ skipHandshake
117
97
  });
118
98
  } catch (e) {
119
99
  (0, import_core.debugAndPrintError)(e);
120
100
  client.error(e.code, e.message, () => rawClient.close());
121
101
  }
122
102
  }
123
- async handleMatchMakeRequest(req, res) {
124
- const writeHeaders = (req2, res2) => {
125
- if (res2.destroyed) return;
126
- res2.set(Object.assign(
127
- {},
128
- import_core.matchMaker.controller.DEFAULT_CORS_HEADERS,
129
- import_core.matchMaker.controller.getCorsHeaders.call(void 0, req2)
130
- ));
131
- return true;
132
- };
133
- try {
134
- switch (req.method) {
135
- case "OPTIONS": {
136
- writeHeaders(req, res);
137
- res.status(200).end();
138
- break;
139
- }
140
- case "GET": {
141
- writeHeaders(req, res);
142
- res.status(404).end();
143
- break;
144
- }
145
- case "POST": {
146
- if (import_core.matchMaker.state === import_core.matchMaker.MatchMakerState.SHUTTING_DOWN) {
147
- throw new import_core.ServerError(503, "server is shutting down");
148
- }
149
- const matchedParams = req.path.match(import_core.matchMaker.controller.allowedRoomNameChars);
150
- const matchmakeIndex = matchedParams.indexOf(import_core.matchMaker.controller.matchmakeRoute);
151
- let clientOptions = req.body;
152
- if (clientOptions == null) {
153
- throw new import_core.ServerError(500, "invalid JSON input");
154
- }
155
- if (typeof clientOptions === "string" && clientOptions.length > 2) {
156
- clientOptions = JSON.parse(clientOptions);
157
- } else if (typeof clientOptions !== "object") {
158
- clientOptions = {};
159
- }
160
- const method = matchedParams[matchmakeIndex + 1];
161
- const roomName = matchedParams[matchmakeIndex + 2] || "";
162
- writeHeaders(req, res);
163
- res.json(await import_core.matchMaker.controller.invokeMethod(
164
- method,
165
- roomName,
166
- clientOptions,
167
- {
168
- token: (0, import_core.getBearerToken)(req.headers["authorization"]),
169
- headers: req.headers,
170
- ip: req.headers["x-real-ip"] ?? req.headers["x-forwarded-for"] ?? req.ips
171
- }
172
- ));
173
- break;
174
- }
175
- default:
176
- throw new import_core.ServerError(500, "invalid request method");
177
- }
178
- } catch (e) {
179
- writeHeaders(req, res);
180
- res.status(500).json({ code: e.code, error: e.message });
181
- }
182
- }
183
103
  }
184
104
  // Annotate the CommonJS export names for ESM import in node:
185
105
  0 && (module.exports = {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/BunWebSockets.ts"],
4
- "sourcesContent": ["// <reference types=\"bun-types\" />\n\n// \"bun-types\" is currently conflicting with \"ws\" types.\n// @ts-ignore\nimport { ServerWebSocket, WebSocketHandler } from 'bun';\n\nimport type http from 'http';\nimport bunExpress from 'bun-serve-express';\nimport type { Application, Request, Response } from \"express\";\n\nimport { HttpServerMock, matchMaker, Transport, debugAndPrintError, spliceOne, ServerError, getBearerToken } from '@colyseus/core';\nimport { WebSocketClient, WebSocketWrapper } from './WebSocketClient.js';\n\nexport type TransportOptions = Partial<Omit<WebSocketHandler, \"message\" | \"open\" | \"drain\" | \"close\" | \"ping\" | \"pong\">>;\n\ninterface WebSocketData {\n url: URL;\n headers: any;\n}\n\nexport class BunWebSockets extends Transport {\n public expressApp: Application;\n\n protected clients: ServerWebSocket<WebSocketData>[] = [];\n protected clientWrappers = new WeakMap<ServerWebSocket<WebSocketData>, WebSocketWrapper>();\n\n private _listening: any;\n private _originalRawSend: typeof WebSocketClient.prototype.raw | null = null;\n\n constructor(private options: TransportOptions = {}) {\n super();\n\n const self = this;\n\n this.expressApp = bunExpress({\n websocket: {\n ...this.options,\n\n async open(ws) {\n await self.onConnection(ws);\n },\n\n message(ws, message) {\n self.clientWrappers.get(ws)?.emit('message', 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 // Adding a mock object for Transport.server\n if (!this.server) {\n // @ts-ignore\n this.server = new HttpServerMock();\n }\n }\n\n public listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void) {\n this._listening = this.expressApp.listen(port, listeningListener);\n\n this.expressApp.use(`/${matchMaker.controller.matchmakeRoute}`, async (req, res) => {\n try {\n await this.handleMatchMakeRequest(req, res);\n } catch (e) {\n res.status(500).json({\n code: e.code,\n error: e.message\n });\n }\n });\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._listening) {\n this._listening.close();\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 if (this._originalRawSend == null) {\n this._originalRawSend = WebSocketClient.prototype.raw;\n }\n\n const originalRawSend = this._originalRawSend;\n WebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalRawSend : function (...args: any[]) {\n let [buf, ...rest] = args;\n buf = Buffer.from(buf);\n setTimeout(() => originalRawSend.apply(this, [buf, ...rest]), milliseconds);\n };\n }\n\n protected async onConnection(rawClient: ServerWebSocket<WebSocketData>) {\n const wrapper = new WebSocketWrapper(rawClient);\n // keep reference to client and its wrapper\n this.clients.push(rawClient);\n this.clientWrappers.set(rawClient, wrapper);\n\n const 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.getLocalRoomById(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, {\n token: parsedURL.searchParams.get(\"_authToken\") ?? getBearerToken(rawClient.data.headers['authorization']),\n headers: rawClient.data.headers,\n ip: rawClient.data.headers['x-real-ip'] ?? rawClient.data.headers['x-forwarded-for'] ?? rawClient.remoteAddress,\n });\n\n } catch (e) {\n debugAndPrintError(e);\n\n // send error code to client then terminate\n client.error(e.code, e.message, () => rawClient.close());\n }\n }\n\n protected async handleMatchMakeRequest(req: Request, res: Response) {\n const writeHeaders = (req: Request, res: Response) => {\n if (res.destroyed) return;\n\n res.set(Object.assign(\n {},\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders.call(undefined, req)\n ));\n\n return true;\n };\n\n try {\n switch (req.method) {\n case 'OPTIONS': {\n writeHeaders(req, res);\n res.status(200).end();\n break;\n }\n\n case 'GET': {\n writeHeaders(req, res);\n res.status(404).end();\n break;\n }\n\n case 'POST': {\n // do not accept matchmaking requests if already shutting down\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n throw new ServerError(503, \"server is shutting down\");\n }\n\n const matchedParams = req.path.match(matchMaker.controller.allowedRoomNameChars);\n const matchmakeIndex = matchedParams.indexOf(matchMaker.controller.matchmakeRoute);\n let clientOptions = req.body; // Bun.readableStreamToJSON(req.body);\n\n if (clientOptions == null) {\n throw new ServerError(500, \"invalid JSON input\");\n }\n\n if (typeof clientOptions === 'string' && clientOptions.length > 2) {\n clientOptions = JSON.parse(clientOptions);\n } else if (typeof clientOptions !== 'object') {\n clientOptions = {};\n }\n\n const method = matchedParams[matchmakeIndex + 1];\n const roomName = matchedParams[matchmakeIndex + 2] || '';\n\n writeHeaders(req, res);\n res.json(await matchMaker.controller.invokeMethod(\n method,\n roomName,\n clientOptions,\n {\n token: getBearerToken(req.headers['authorization']),\n headers: req.headers,\n ip: req.headers['x-real-ip'] ?? req.headers['x-forwarded-for'] ?? req.ips,\n },\n ));\n break;\n }\n\n default: throw new ServerError(500, \"invalid request method\");\n }\n\n } catch (e) {\n writeHeaders(req, res);\n res.status(500)\n .json({ code: e.code, error: e.message });\n }\n }\n\n}\n"],
5
- "mappings": "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",
6
- "names": ["bunExpress", "req", "res"]
4
+ "sourcesContent": ["// <reference types=\"bun-types\" />\n\n// \"bun-types\" is currently conflicting with \"ws\" types.\n// @ts-ignore\nimport { ServerWebSocket, WebSocketHandler } from 'bun';\n\n// import bunExpress from 'bun-serve-express';\nimport type { Application, Request, Response } from \"express\";\n\nimport { HttpServerMock, matchMaker, Protocol, Transport, debugAndPrintError, getBearerToken, CloseCode, connectClientToRoom } from '@colyseus/core';\nimport { WebSocketClient, WebSocketWrapper } from './WebSocketClient.ts';\n\nexport type TransportOptions = Partial<Omit<WebSocketHandler, \"message\" | \"open\" | \"drain\" | \"close\" | \"ping\" | \"pong\">>;\n\ninterface WebSocketData {\n url: URL;\n headers: any;\n}\n\nexport class BunWebSockets extends Transport {\n public expressApp: Application;\n\n protected clients: ServerWebSocket<WebSocketData>[] = [];\n protected clientWrappers = new WeakMap<ServerWebSocket<WebSocketData>, WebSocketWrapper>();\n\n private _listening: any;\n private _originalRawSend: typeof WebSocketClient.prototype.raw | null = null;\n private options: TransportOptions = {};\n\n constructor(options: TransportOptions = {}) {\n super();\n\n const self = this;\n\n this.options = options;\n\n // this.expressApp = bunExpress({\n // websocket: {\n // ...this.options,\n\n // async open(ws) {\n // await self.onConnection(ws);\n // },\n\n // message(ws, message) {\n // self.clientWrappers.get(ws)?.emit('message', 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 // Adding a mock object for Transport.server\n if (!this.server) {\n // @ts-ignore\n this.server = new HttpServerMock();\n }\n }\n\n public listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void) {\n this._listening = this.expressApp.listen(port, listeningListener);\n\n this.expressApp.use(`/${matchMaker.controller.matchmakeRoute}`, async (req, res) => {\n try {\n // TODO: use shared handler here\n // await this.handleMatchMakeRequest(req, res);\n } catch (e: any) {\n res.status(500).json({\n code: e.code,\n error: e.message\n });\n }\n });\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._listening) {\n this._listening.close();\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 if (this._originalRawSend == null) {\n this._originalRawSend = WebSocketClient.prototype.raw;\n }\n\n const originalRawSend = this._originalRawSend;\n WebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalRawSend : function (...args: any[]) {\n let [buf, ...rest] = args;\n buf = Buffer.from(buf);\n // @ts-ignore\n setTimeout(() => originalRawSend.apply(this, [buf, ...rest]), milliseconds);\n };\n }\n\n protected async onConnection(rawClient: ServerWebSocket<WebSocketData>) {\n const wrapper = new WebSocketWrapper(rawClient);\n // keep reference to client and its wrapper\n this.clients.push(rawClient);\n this.clientWrappers.set(rawClient, wrapper);\n\n const 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 // If sessionId is not provided, allow ping-pong utility.\n if (!sessionId && !roomId) {\n // Disconnect automatically after 1 second if no message is received.\n const timeout = setTimeout(() => rawClient.close(CloseCode.NORMAL_CLOSURE), 1000);\n wrapper.on('message', (_) => rawClient.send(new Uint8Array([Protocol.PING])));\n wrapper.on('close', () => clearTimeout(timeout));\n return;\n }\n\n const room = matchMaker.getLocalRoomById(roomId);\n const client = new WebSocketClient(sessionId, wrapper);\n const reconnectionToken = parsedURL.searchParams.get(\"reconnectionToken\");\n const skipHandshake = (parsedURL.searchParams.has(\"skipHandshake\"));\n\n try {\n await connectClientToRoom(room, client, {\n token: parsedURL.searchParams.get(\"_authToken\") ?? getBearerToken(rawClient.data.headers['authorization']),\n headers: rawClient.data.headers,\n ip: rawClient.data.headers['x-real-ip'] ?? rawClient.data.headers['x-forwarded-for'] ?? rawClient.remoteAddress,\n }, {\n reconnectionToken,\n skipHandshake\n });\n\n } catch (e: any) {\n debugAndPrintError(e);\n\n // send error code to client then terminate\n client.error(e.code, e.message, () => rawClient.close());\n }\n }\n\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
7
  }
@@ -1,34 +1,16 @@
1
1
  // packages/transport/bun-websockets/src/BunWebSockets.ts
2
- import bunExpress from "bun-serve-express";
3
- import { HttpServerMock, matchMaker, Transport, debugAndPrintError, spliceOne, ServerError, getBearerToken } from "@colyseus/core";
2
+ import "bun";
3
+ import { HttpServerMock, matchMaker, Protocol, Transport, debugAndPrintError, getBearerToken, CloseCode, connectClientToRoom } from "@colyseus/core";
4
4
  import { WebSocketClient, WebSocketWrapper } from "./WebSocketClient.mjs";
5
5
  var BunWebSockets = class extends Transport {
6
6
  constructor(options = {}) {
7
7
  super();
8
- this.options = options;
9
8
  this.clients = [];
10
9
  this.clientWrappers = /* @__PURE__ */ new WeakMap();
11
10
  this._originalRawSend = null;
11
+ this.options = {};
12
12
  const self = this;
13
- this.expressApp = bunExpress({
14
- websocket: {
15
- ...this.options,
16
- async open(ws) {
17
- await self.onConnection(ws);
18
- },
19
- message(ws, message) {
20
- self.clientWrappers.get(ws)?.emit("message", message);
21
- },
22
- close(ws, code, reason) {
23
- spliceOne(self.clients, self.clients.indexOf(ws));
24
- const clientWrapper = self.clientWrappers.get(ws);
25
- if (clientWrapper) {
26
- self.clientWrappers.delete(ws);
27
- clientWrapper.emit("close", code);
28
- }
29
- }
30
- }
31
- });
13
+ this.options = options;
32
14
  if (!this.server) {
33
15
  this.server = new HttpServerMock();
34
16
  }
@@ -37,7 +19,6 @@ var BunWebSockets = class extends Transport {
37
19
  this._listening = this.expressApp.listen(port, listeningListener);
38
20
  this.expressApp.use(`/${matchMaker.controller.matchmakeRoute}`, async (req, res) => {
39
21
  try {
40
- await this.handleMatchMakeRequest(req, res);
41
22
  } catch (e) {
42
23
  res.status(500).json({
43
24
  code: e.code,
@@ -73,82 +54,30 @@ var BunWebSockets = class extends Transport {
73
54
  const sessionId = parsedURL.searchParams.get("sessionId");
74
55
  const processAndRoomId = parsedURL.pathname.match(/\/[a-zA-Z0-9_\-]+\/([a-zA-Z0-9_\-]+)$/);
75
56
  const roomId = processAndRoomId && processAndRoomId[1];
57
+ if (!sessionId && !roomId) {
58
+ const timeout = setTimeout(() => rawClient.close(CloseCode.NORMAL_CLOSURE), 1e3);
59
+ wrapper.on("message", (_) => rawClient.send(new Uint8Array([Protocol.PING])));
60
+ wrapper.on("close", () => clearTimeout(timeout));
61
+ return;
62
+ }
76
63
  const room = matchMaker.getLocalRoomById(roomId);
77
64
  const client = new WebSocketClient(sessionId, wrapper);
65
+ const reconnectionToken = parsedURL.searchParams.get("reconnectionToken");
66
+ const skipHandshake = parsedURL.searchParams.has("skipHandshake");
78
67
  try {
79
- if (!room || !room.hasReservedSeat(sessionId, parsedURL.searchParams.get("reconnectionToken"))) {
80
- throw new Error("seat reservation expired.");
81
- }
82
- await room._onJoin(client, {
68
+ await connectClientToRoom(room, client, {
83
69
  token: parsedURL.searchParams.get("_authToken") ?? getBearerToken(rawClient.data.headers["authorization"]),
84
70
  headers: rawClient.data.headers,
85
71
  ip: rawClient.data.headers["x-real-ip"] ?? rawClient.data.headers["x-forwarded-for"] ?? rawClient.remoteAddress
72
+ }, {
73
+ reconnectionToken,
74
+ skipHandshake
86
75
  });
87
76
  } catch (e) {
88
77
  debugAndPrintError(e);
89
78
  client.error(e.code, e.message, () => rawClient.close());
90
79
  }
91
80
  }
92
- async handleMatchMakeRequest(req, res) {
93
- const writeHeaders = (req2, res2) => {
94
- if (res2.destroyed) return;
95
- res2.set(Object.assign(
96
- {},
97
- matchMaker.controller.DEFAULT_CORS_HEADERS,
98
- matchMaker.controller.getCorsHeaders.call(void 0, req2)
99
- ));
100
- return true;
101
- };
102
- try {
103
- switch (req.method) {
104
- case "OPTIONS": {
105
- writeHeaders(req, res);
106
- res.status(200).end();
107
- break;
108
- }
109
- case "GET": {
110
- writeHeaders(req, res);
111
- res.status(404).end();
112
- break;
113
- }
114
- case "POST": {
115
- if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {
116
- throw new ServerError(503, "server is shutting down");
117
- }
118
- const matchedParams = req.path.match(matchMaker.controller.allowedRoomNameChars);
119
- const matchmakeIndex = matchedParams.indexOf(matchMaker.controller.matchmakeRoute);
120
- let clientOptions = req.body;
121
- if (clientOptions == null) {
122
- throw new ServerError(500, "invalid JSON input");
123
- }
124
- if (typeof clientOptions === "string" && clientOptions.length > 2) {
125
- clientOptions = JSON.parse(clientOptions);
126
- } else if (typeof clientOptions !== "object") {
127
- clientOptions = {};
128
- }
129
- const method = matchedParams[matchmakeIndex + 1];
130
- const roomName = matchedParams[matchmakeIndex + 2] || "";
131
- writeHeaders(req, res);
132
- res.json(await matchMaker.controller.invokeMethod(
133
- method,
134
- roomName,
135
- clientOptions,
136
- {
137
- token: getBearerToken(req.headers["authorization"]),
138
- headers: req.headers,
139
- ip: req.headers["x-real-ip"] ?? req.headers["x-forwarded-for"] ?? req.ips
140
- }
141
- ));
142
- break;
143
- }
144
- default:
145
- throw new ServerError(500, "invalid request method");
146
- }
147
- } catch (e) {
148
- writeHeaders(req, res);
149
- res.status(500).json({ code: e.code, error: e.message });
150
- }
151
- }
152
81
  };
153
82
  export {
154
83
  BunWebSockets
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/BunWebSockets.ts"],
4
- "sourcesContent": ["// <reference types=\"bun-types\" />\n\n// \"bun-types\" is currently conflicting with \"ws\" types.\n// @ts-ignore\nimport { ServerWebSocket, WebSocketHandler } from 'bun';\n\nimport type http from 'http';\nimport bunExpress from 'bun-serve-express';\nimport type { Application, Request, Response } from \"express\";\n\nimport { HttpServerMock, matchMaker, Transport, debugAndPrintError, spliceOne, ServerError, getBearerToken } from '@colyseus/core';\nimport { WebSocketClient, WebSocketWrapper } from './WebSocketClient.js';\n\nexport type TransportOptions = Partial<Omit<WebSocketHandler, \"message\" | \"open\" | \"drain\" | \"close\" | \"ping\" | \"pong\">>;\n\ninterface WebSocketData {\n url: URL;\n headers: any;\n}\n\nexport class BunWebSockets extends Transport {\n public expressApp: Application;\n\n protected clients: ServerWebSocket<WebSocketData>[] = [];\n protected clientWrappers = new WeakMap<ServerWebSocket<WebSocketData>, WebSocketWrapper>();\n\n private _listening: any;\n private _originalRawSend: typeof WebSocketClient.prototype.raw | null = null;\n\n constructor(private options: TransportOptions = {}) {\n super();\n\n const self = this;\n\n this.expressApp = bunExpress({\n websocket: {\n ...this.options,\n\n async open(ws) {\n await self.onConnection(ws);\n },\n\n message(ws, message) {\n self.clientWrappers.get(ws)?.emit('message', 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 // Adding a mock object for Transport.server\n if (!this.server) {\n // @ts-ignore\n this.server = new HttpServerMock();\n }\n }\n\n public listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void) {\n this._listening = this.expressApp.listen(port, listeningListener);\n\n this.expressApp.use(`/${matchMaker.controller.matchmakeRoute}`, async (req, res) => {\n try {\n await this.handleMatchMakeRequest(req, res);\n } catch (e) {\n res.status(500).json({\n code: e.code,\n error: e.message\n });\n }\n });\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._listening) {\n this._listening.close();\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 if (this._originalRawSend == null) {\n this._originalRawSend = WebSocketClient.prototype.raw;\n }\n\n const originalRawSend = this._originalRawSend;\n WebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalRawSend : function (...args: any[]) {\n let [buf, ...rest] = args;\n buf = Buffer.from(buf);\n setTimeout(() => originalRawSend.apply(this, [buf, ...rest]), milliseconds);\n };\n }\n\n protected async onConnection(rawClient: ServerWebSocket<WebSocketData>) {\n const wrapper = new WebSocketWrapper(rawClient);\n // keep reference to client and its wrapper\n this.clients.push(rawClient);\n this.clientWrappers.set(rawClient, wrapper);\n\n const 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.getLocalRoomById(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, {\n token: parsedURL.searchParams.get(\"_authToken\") ?? getBearerToken(rawClient.data.headers['authorization']),\n headers: rawClient.data.headers,\n ip: rawClient.data.headers['x-real-ip'] ?? rawClient.data.headers['x-forwarded-for'] ?? rawClient.remoteAddress,\n });\n\n } catch (e) {\n debugAndPrintError(e);\n\n // send error code to client then terminate\n client.error(e.code, e.message, () => rawClient.close());\n }\n }\n\n protected async handleMatchMakeRequest(req: Request, res: Response) {\n const writeHeaders = (req: Request, res: Response) => {\n if (res.destroyed) return;\n\n res.set(Object.assign(\n {},\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders.call(undefined, req)\n ));\n\n return true;\n };\n\n try {\n switch (req.method) {\n case 'OPTIONS': {\n writeHeaders(req, res);\n res.status(200).end();\n break;\n }\n\n case 'GET': {\n writeHeaders(req, res);\n res.status(404).end();\n break;\n }\n\n case 'POST': {\n // do not accept matchmaking requests if already shutting down\n if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n throw new ServerError(503, \"server is shutting down\");\n }\n\n const matchedParams = req.path.match(matchMaker.controller.allowedRoomNameChars);\n const matchmakeIndex = matchedParams.indexOf(matchMaker.controller.matchmakeRoute);\n let clientOptions = req.body; // Bun.readableStreamToJSON(req.body);\n\n if (clientOptions == null) {\n throw new ServerError(500, \"invalid JSON input\");\n }\n\n if (typeof clientOptions === 'string' && clientOptions.length > 2) {\n clientOptions = JSON.parse(clientOptions);\n } else if (typeof clientOptions !== 'object') {\n clientOptions = {};\n }\n\n const method = matchedParams[matchmakeIndex + 1];\n const roomName = matchedParams[matchmakeIndex + 2] || '';\n\n writeHeaders(req, res);\n res.json(await matchMaker.controller.invokeMethod(\n method,\n roomName,\n clientOptions,\n {\n token: getBearerToken(req.headers['authorization']),\n headers: req.headers,\n ip: req.headers['x-real-ip'] ?? req.headers['x-forwarded-for'] ?? req.ips,\n },\n ));\n break;\n }\n\n default: throw new ServerError(500, \"invalid request method\");\n }\n\n } catch (e) {\n writeHeaders(req, res);\n res.status(500)\n .json({ code: e.code, error: e.message });\n }\n }\n\n}\n"],
5
- "mappings": "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",
6
- "names": ["req", "res"]
4
+ "sourcesContent": ["// <reference types=\"bun-types\" />\n\n// \"bun-types\" is currently conflicting with \"ws\" types.\n// @ts-ignore\nimport { ServerWebSocket, WebSocketHandler } from 'bun';\n\n// import bunExpress from 'bun-serve-express';\nimport type { Application, Request, Response } from \"express\";\n\nimport { HttpServerMock, matchMaker, Protocol, Transport, debugAndPrintError, getBearerToken, CloseCode, connectClientToRoom } from '@colyseus/core';\nimport { WebSocketClient, WebSocketWrapper } from './WebSocketClient.ts';\n\nexport type TransportOptions = Partial<Omit<WebSocketHandler, \"message\" | \"open\" | \"drain\" | \"close\" | \"ping\" | \"pong\">>;\n\ninterface WebSocketData {\n url: URL;\n headers: any;\n}\n\nexport class BunWebSockets extends Transport {\n public expressApp: Application;\n\n protected clients: ServerWebSocket<WebSocketData>[] = [];\n protected clientWrappers = new WeakMap<ServerWebSocket<WebSocketData>, WebSocketWrapper>();\n\n private _listening: any;\n private _originalRawSend: typeof WebSocketClient.prototype.raw | null = null;\n private options: TransportOptions = {};\n\n constructor(options: TransportOptions = {}) {\n super();\n\n const self = this;\n\n this.options = options;\n\n // this.expressApp = bunExpress({\n // websocket: {\n // ...this.options,\n\n // async open(ws) {\n // await self.onConnection(ws);\n // },\n\n // message(ws, message) {\n // self.clientWrappers.get(ws)?.emit('message', 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 // Adding a mock object for Transport.server\n if (!this.server) {\n // @ts-ignore\n this.server = new HttpServerMock();\n }\n }\n\n public listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void) {\n this._listening = this.expressApp.listen(port, listeningListener);\n\n this.expressApp.use(`/${matchMaker.controller.matchmakeRoute}`, async (req, res) => {\n try {\n // TODO: use shared handler here\n // await this.handleMatchMakeRequest(req, res);\n } catch (e: any) {\n res.status(500).json({\n code: e.code,\n error: e.message\n });\n }\n });\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._listening) {\n this._listening.close();\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 if (this._originalRawSend == null) {\n this._originalRawSend = WebSocketClient.prototype.raw;\n }\n\n const originalRawSend = this._originalRawSend;\n WebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalRawSend : function (...args: any[]) {\n let [buf, ...rest] = args;\n buf = Buffer.from(buf);\n // @ts-ignore\n setTimeout(() => originalRawSend.apply(this, [buf, ...rest]), milliseconds);\n };\n }\n\n protected async onConnection(rawClient: ServerWebSocket<WebSocketData>) {\n const wrapper = new WebSocketWrapper(rawClient);\n // keep reference to client and its wrapper\n this.clients.push(rawClient);\n this.clientWrappers.set(rawClient, wrapper);\n\n const 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 // If sessionId is not provided, allow ping-pong utility.\n if (!sessionId && !roomId) {\n // Disconnect automatically after 1 second if no message is received.\n const timeout = setTimeout(() => rawClient.close(CloseCode.NORMAL_CLOSURE), 1000);\n wrapper.on('message', (_) => rawClient.send(new Uint8Array([Protocol.PING])));\n wrapper.on('close', () => clearTimeout(timeout));\n return;\n }\n\n const room = matchMaker.getLocalRoomById(roomId);\n const client = new WebSocketClient(sessionId, wrapper);\n const reconnectionToken = parsedURL.searchParams.get(\"reconnectionToken\");\n const skipHandshake = (parsedURL.searchParams.has(\"skipHandshake\"));\n\n try {\n await connectClientToRoom(room, client, {\n token: parsedURL.searchParams.get(\"_authToken\") ?? getBearerToken(rawClient.data.headers['authorization']),\n headers: rawClient.data.headers,\n ip: rawClient.data.headers['x-real-ip'] ?? rawClient.data.headers['x-forwarded-for'] ?? rawClient.remoteAddress,\n }, {\n reconnectionToken,\n skipHandshake\n });\n\n } catch (e: any) {\n debugAndPrintError(e);\n\n // send error code to client then terminate\n client.error(e.code, e.message, () => rawClient.close());\n }\n }\n\n}\n"],
5
+ "mappings": ";AAIA,OAAkD;AAKlD,SAAS,gBAAgB,YAAY,UAAU,WAAW,oBAAoB,gBAAgB,WAAW,2BAA2B;AACpI,SAAS,iBAAiB,wBAAwB;AAS3C,IAAM,gBAAN,cAA4B,UAAU;AAAA,EAU3C,YAAY,UAA4B,CAAC,GAAG;AAC1C,UAAM;AARR,SAAU,UAA4C,CAAC;AACvD,SAAU,iBAAiB,oBAAI,QAA0D;AAGzF,SAAQ,mBAAgE;AACxE,SAAQ,UAA4B,CAAC;AAKnC,UAAM,OAAO;AAEb,SAAK,UAAU;AA8Bf,QAAI,CAAC,KAAK,QAAQ;AAEhB,WAAK,SAAS,IAAI,eAAe;AAAA,IACnC;AAAA,EACF;AAAA,EAEO,OAAO,MAAc,UAAmB,SAAkB,mBAAgC;AAC/F,SAAK,aAAa,KAAK,WAAW,OAAO,MAAM,iBAAiB;AAEhE,SAAK,WAAW,IAAI,IAAI,WAAW,WAAW,cAAc,IAAI,OAAO,KAAK,QAAQ;AAClF,UAAI;AAAA,MAGJ,SAAS,GAAQ;AACf,YAAI,OAAO,GAAG,EAAE,KAAK;AAAA,UACnB,MAAM,EAAE;AAAA,UACR,OAAO,EAAE;AAAA,QACX,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAID,SAAK,OAAO,KAAK,WAAW;AAE5B,WAAO;AAAA,EACT;AAAA,EAEO,WAAW;AAChB,QAAI,KAAK,YAAY;AACnB,WAAK,WAAW,MAAM;AAGtB,WAAK,OAAO,KAAK,OAAO;AAAA,IAC1B;AAAA,EACF;AAAA,EAEO,gBAAgB,cAAsB;AAC3C,QAAI,KAAK,oBAAoB,MAAM;AACjC,WAAK,mBAAmB,gBAAgB,UAAU;AAAA,IACpD;AAEA,UAAM,kBAAkB,KAAK;AAC7B,oBAAgB,UAAU,MAAM,gBAAgB,OAAO,UAAU,kBAAkB,YAAa,MAAa;AAC3G,UAAI,CAAC,KAAK,GAAG,IAAI,IAAI;AACrB,YAAM,OAAO,KAAK,GAAG;AAErB,iBAAW,MAAM,gBAAgB,MAAM,MAAM,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,YAAY;AAAA,IAC5E;AAAA,EACF;AAAA,EAEA,MAAgB,aAAa,WAA2C;AACtE,UAAM,UAAU,IAAI,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,CAAC;AAGrD,QAAI,CAAC,aAAa,CAAC,QAAQ;AAEzB,YAAM,UAAU,WAAW,MAAM,UAAU,MAAM,UAAU,cAAc,GAAG,GAAI;AAChF,cAAQ,GAAG,WAAW,CAAC,MAAM,UAAU,KAAK,IAAI,WAAW,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC;AAC5E,cAAQ,GAAG,SAAS,MAAM,aAAa,OAAO,CAAC;AAC/C;AAAA,IACF;AAEA,UAAM,OAAO,WAAW,iBAAiB,MAAM;AAC/C,UAAM,SAAS,IAAI,gBAAgB,WAAW,OAAO;AACrD,UAAM,oBAAoB,UAAU,aAAa,IAAI,mBAAmB;AACxE,UAAM,gBAAiB,UAAU,aAAa,IAAI,eAAe;AAEjE,QAAI;AACF,YAAM,oBAAoB,MAAM,QAAQ;AAAA,QACtC,OAAO,UAAU,aAAa,IAAI,YAAY,KAAK,eAAe,UAAU,KAAK,QAAQ,eAAe,CAAC;AAAA,QACzG,SAAS,UAAU,KAAK;AAAA,QACxB,IAAI,UAAU,KAAK,QAAQ,WAAW,KAAK,UAAU,KAAK,QAAQ,iBAAiB,KAAK,UAAU;AAAA,MACpG,GAAG;AAAA,QACD;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IAEH,SAAS,GAAQ;AACf,yBAAmB,CAAC;AAGpB,aAAO,MAAM,EAAE,MAAM,EAAE,SAAS,MAAM,UAAU,MAAM,CAAC;AAAA,IACzD;AAAA,EACF;AAEF;",
6
+ "names": []
7
7
  }
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -31,7 +32,7 @@ __export(WebSocketClient_exports, {
31
32
  WebSocketWrapper: () => WebSocketWrapper
32
33
  });
33
34
  module.exports = __toCommonJS(WebSocketClient_exports);
34
- var import_events = __toESM(require("events"));
35
+ var import_events = __toESM(require("events"), 1);
35
36
  var import_core = require("@colyseus/core");
36
37
  class WebSocketWrapper extends import_events.default {
37
38
  constructor(ws) {
@@ -41,11 +42,10 @@ class WebSocketWrapper extends import_events.default {
41
42
  }
42
43
  class WebSocketClient {
43
44
  constructor(id, ref) {
44
- this.id = id;
45
- this.ref = ref;
46
45
  this.state = import_core.ClientState.JOINING;
47
46
  this._enqueuedMessages = [];
48
- this.sessionId = id;
47
+ this.id = this.sessionId = id;
48
+ this.ref = ref;
49
49
  }
50
50
  sendBytes(type, bytes, options) {
51
51
  (0, import_core.debugMessage)("send bytes(to %s): '%s' -> %j", this.sessionId, type, bytes);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/WebSocketClient.ts"],
4
- "sourcesContent": ["// <reference types=\"bun-types\" />\n\n// \"bun-types\" is currently conflicting with \"ws\" types.\n// @ts-ignore\nimport type { ServerWebSocket } from 'bun';\nimport EventEmitter from 'events';\n\nimport { Protocol, Client, ClientPrivate, ClientState, ISendOptions, getMessageBytes, logger, debugMessage } from '@colyseus/core';\n\nexport class WebSocketWrapper extends EventEmitter {\n constructor(public ws: ServerWebSocket<any>) {\n super();\n }\n}\n\nexport class WebSocketClient implements Client, ClientPrivate {\n public sessionId: string;\n public state: ClientState = ClientState.JOINING;\n public reconnectionToken: string;\n\n public _enqueuedMessages: any[] = [];\n public _afterNextPatchQueue;\n public _reconnectionToken: string;\n public _joinedAt: number;\n\n constructor(\n public id: string,\n public ref: WebSocketWrapper,\n ) {\n this.sessionId = id;\n }\n\n public sendBytes(type: string | number, bytes: Buffer | Uint8Array, options?: ISendOptions) {\n debugMessage(\"send bytes(to %s): '%s' -> %j\", this.sessionId, type, bytes);\n\n this.enqueueRaw(\n getMessageBytes.raw(Protocol.ROOM_DATA_BYTES, type, undefined, bytes),\n options,\n );\n }\n\n public send(messageOrType: any, messageOrOptions?: any | ISendOptions, options?: ISendOptions) {\n debugMessage(\"send(to %s): '%s' -> %j\", this.sessionId, messageOrType, messageOrOptions);\n\n this.enqueueRaw(\n getMessageBytes.raw(Protocol.ROOM_DATA, messageOrType, messageOrOptions),\n options,\n );\n }\n\n public enqueueRaw(data: Uint8Array | Buffer, options?: ISendOptions) {\n // use room's afterNextPatch queue\n if (options?.afterNextPatch) {\n this._afterNextPatchQueue.push([this, [data]]);\n return;\n }\n\n if (this.state === ClientState.JOINING) {\n // sending messages during `onJoin`.\n // - the client-side cannot register \"onMessage\" callbacks at this point.\n // - enqueue the messages to be send after JOIN_ROOM message has been sent\n // - create a new buffer for enqueued messages, as the underlying buffer might be modified\n this._enqueuedMessages.push(data);\n return;\n }\n\n this.raw(data, options);\n }\n\n public raw(data: Uint8Array | Buffer, options?: ISendOptions, cb?: (err?: Error) => void) {\n // skip if client not open\n\n // WebSocket is globally available on Bun runtime\n // @ts-ignore\n if (this.ref.ws.readyState !== WebSocket.OPEN) {\n return;\n }\n\n // FIXME: can we avoid creating a new buffer here?\n this.ref.ws.sendBinary(data);\n }\n\n public error(code: number, message: string = '', cb?: (err?: Error) => void) {\n this.raw(getMessageBytes[Protocol.ERROR](code, message));\n\n if (cb) {\n // (same API as \"ws\" transport)\n setTimeout(cb, 1);\n }\n }\n\n get readyState() {\n return this.ref.ws.readyState;\n }\n\n public leave(code?: number, data?: string) {\n this.ref.ws.close(code, data);\n }\n\n public close(code?: number, data?: string) {\n logger.warn('DEPRECATION WARNING: use client.leave() instead of client.close()');\n try {\n throw new Error();\n } catch (e) {\n logger.info(e.stack);\n }\n this.leave(code, data);\n }\n\n public toJSON() {\n return { sessionId: this.sessionId, readyState: this.readyState };\n }\n}\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,oBAAyB;AAEzB,kBAAkH;AAE3G,MAAM,yBAAyB,cAAAA,QAAa;AAAA,EACjD,YAAmB,IAA0B;AAC3C,UAAM;AADW;AAAA,EAEnB;AACF;AAEO,MAAM,gBAAiD;AAAA,EAU5D,YACS,IACA,KACP;AAFO;AACA;AAVT,SAAO,QAAqB,wBAAY;AAGxC,SAAO,oBAA2B,CAAC;AASjC,SAAK,YAAY;AAAA,EACnB;AAAA,EAEO,UAAU,MAAuB,OAA4B,SAAwB;AAC1F,kCAAa,iCAAiC,KAAK,WAAW,MAAM,KAAK;AAEzE,SAAK;AAAA,MACH,4BAAgB,IAAI,qBAAS,iBAAiB,MAAM,QAAW,KAAK;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AAAA,EAEO,KAAK,eAAoB,kBAAuC,SAAwB;AAC7F,kCAAa,2BAA2B,KAAK,WAAW,eAAe,gBAAgB;AAEvF,SAAK;AAAA,MACH,4BAAgB,IAAI,qBAAS,WAAW,eAAe,gBAAgB;AAAA,MACvE;AAAA,IACF;AAAA,EACF;AAAA,EAEO,WAAW,MAA2B,SAAwB;AAEnE,QAAI,SAAS,gBAAgB;AAC3B,WAAK,qBAAqB,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7C;AAAA,IACF;AAEA,QAAI,KAAK,UAAU,wBAAY,SAAS;AAKtC,WAAK,kBAAkB,KAAK,IAAI;AAChC;AAAA,IACF;AAEA,SAAK,IAAI,MAAM,OAAO;AAAA,EACxB;AAAA,EAEO,IAAI,MAA2B,SAAwB,IAA4B;AAKxF,QAAI,KAAK,IAAI,GAAG,eAAe,UAAU,MAAM;AAC7C;AAAA,IACF;AAGA,SAAK,IAAI,GAAG,WAAW,IAAI;AAAA,EAC7B;AAAA,EAEO,MAAM,MAAc,UAAkB,IAAI,IAA4B;AAC3E,SAAK,IAAI,4BAAgB,qBAAS,KAAK,EAAE,MAAM,OAAO,CAAC;AAEvD,QAAI,IAAI;AAEN,iBAAW,IAAI,CAAC;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,IAAI,aAAa;AACf,WAAO,KAAK,IAAI,GAAG;AAAA,EACrB;AAAA,EAEO,MAAM,MAAe,MAAe;AACzC,SAAK,IAAI,GAAG,MAAM,MAAM,IAAI;AAAA,EAC9B;AAAA,EAEO,MAAM,MAAe,MAAe;AACzC,uBAAO,KAAK,mEAAmE;AAC/E,QAAI;AACF,YAAM,IAAI,MAAM;AAAA,IAClB,SAAS,GAAG;AACV,yBAAO,KAAK,EAAE,KAAK;AAAA,IACrB;AACA,SAAK,MAAM,MAAM,IAAI;AAAA,EACvB;AAAA,EAEO,SAAS;AACd,WAAO,EAAE,WAAW,KAAK,WAAW,YAAY,KAAK,WAAW;AAAA,EAClE;AACF;",
4
+ "sourcesContent": ["// <reference types=\"bun-types\" />\n\n// \"bun-types\" is currently conflicting with \"ws\" types.\n// @ts-ignore\nimport type { ServerWebSocket } from 'bun';\nimport EventEmitter from 'events';\n\nimport { Protocol, type Client, type ClientPrivate, ClientState, type ISendOptions, getMessageBytes, logger, debugMessage } from '@colyseus/core';\n\nexport class WebSocketWrapper extends EventEmitter {\n public ws: ServerWebSocket<any>;\n\n constructor(ws: ServerWebSocket<any>) {\n super();\n this.ws = ws;\n }\n}\n\nexport class WebSocketClient implements Client, ClientPrivate {\n '~messages': any;\n\n public id: string;\n public ref: WebSocketWrapper;\n\n public sessionId: string;\n public state: ClientState = ClientState.JOINING;\n public reconnectionToken: string;\n\n public _enqueuedMessages: any[] = [];\n public _afterNextPatchQueue;\n public _reconnectionToken: string;\n public _joinedAt: number;\n\n constructor(id: string, ref: WebSocketWrapper,) {\n this.id = this.sessionId = id;\n this.ref = ref;\n }\n\n public sendBytes(type: string | number, bytes: Buffer | Uint8Array, options?: ISendOptions) {\n debugMessage(\"send bytes(to %s): '%s' -> %j\", this.sessionId, type, bytes);\n\n this.enqueueRaw(\n getMessageBytes.raw(Protocol.ROOM_DATA_BYTES, type, undefined, bytes),\n options,\n );\n }\n\n public send(messageOrType: any, messageOrOptions?: any | ISendOptions, options?: ISendOptions) {\n debugMessage(\"send(to %s): '%s' -> %j\", this.sessionId, messageOrType, messageOrOptions);\n\n this.enqueueRaw(\n getMessageBytes.raw(Protocol.ROOM_DATA, messageOrType, messageOrOptions),\n options,\n );\n }\n\n public enqueueRaw(data: Uint8Array | Buffer, options?: ISendOptions) {\n // use room's afterNextPatch queue\n if (options?.afterNextPatch) {\n this._afterNextPatchQueue.push([this, [data]]);\n return;\n }\n\n if (this.state === ClientState.JOINING) {\n // sending messages during `onJoin`.\n // - the client-side cannot register \"onMessage\" callbacks at this point.\n // - enqueue the messages to be send after JOIN_ROOM message has been sent\n // - create a new buffer for enqueued messages, as the underlying buffer might be modified\n this._enqueuedMessages.push(data);\n return;\n }\n\n this.raw(data, options);\n }\n\n public raw(data: Uint8Array | Buffer, options?: ISendOptions, cb?: (err?: Error) => void) {\n // skip if client not open\n\n // WebSocket is globally available on Bun runtime\n // @ts-ignore\n if (this.ref.ws.readyState !== WebSocket.OPEN) {\n return;\n }\n\n // FIXME: can we avoid creating a new buffer here?\n this.ref.ws.sendBinary(data);\n }\n\n public error(code: number, message: string = '', cb?: (err?: Error) => void) {\n this.raw(getMessageBytes[Protocol.ERROR](code, message));\n\n if (cb) {\n // (same API as \"ws\" transport)\n setTimeout(cb, 1);\n }\n }\n\n get readyState() {\n return this.ref.ws.readyState;\n }\n\n public leave(code?: number, data?: string) {\n this.ref.ws.close(code, data);\n }\n\n public close(code?: number, data?: string) {\n logger.warn('DEPRECATION WARNING: use client.leave() instead of client.close()');\n try {\n throw new Error();\n } catch (e: any) {\n logger.info(e.stack);\n }\n this.leave(code, data);\n }\n\n public toJSON() {\n return { sessionId: this.sessionId, readyState: this.readyState };\n }\n}\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAKA,oBAAyB;AAEzB,kBAAiI;AAE1H,MAAM,yBAAyB,cAAAA,QAAa;AAAA,EAGjD,YAAY,IAA0B;AACpC,UAAM;AACN,SAAK,KAAK;AAAA,EACZ;AACF;AAEO,MAAM,gBAAiD;AAAA,EAe5D,YAAY,IAAY,KAAwB;AARhD,SAAO,QAAqB,wBAAY;AAGxC,SAAO,oBAA2B,CAAC;AAMjC,SAAK,KAAK,KAAK,YAAY;AAC3B,SAAK,MAAM;AAAA,EACb;AAAA,EAEO,UAAU,MAAuB,OAA4B,SAAwB;AAC1F,kCAAa,iCAAiC,KAAK,WAAW,MAAM,KAAK;AAEzE,SAAK;AAAA,MACH,4BAAgB,IAAI,qBAAS,iBAAiB,MAAM,QAAW,KAAK;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AAAA,EAEO,KAAK,eAAoB,kBAAuC,SAAwB;AAC7F,kCAAa,2BAA2B,KAAK,WAAW,eAAe,gBAAgB;AAEvF,SAAK;AAAA,MACH,4BAAgB,IAAI,qBAAS,WAAW,eAAe,gBAAgB;AAAA,MACvE;AAAA,IACF;AAAA,EACF;AAAA,EAEO,WAAW,MAA2B,SAAwB;AAEnE,QAAI,SAAS,gBAAgB;AAC3B,WAAK,qBAAqB,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7C;AAAA,IACF;AAEA,QAAI,KAAK,UAAU,wBAAY,SAAS;AAKtC,WAAK,kBAAkB,KAAK,IAAI;AAChC;AAAA,IACF;AAEA,SAAK,IAAI,MAAM,OAAO;AAAA,EACxB;AAAA,EAEO,IAAI,MAA2B,SAAwB,IAA4B;AAKxF,QAAI,KAAK,IAAI,GAAG,eAAe,UAAU,MAAM;AAC7C;AAAA,IACF;AAGA,SAAK,IAAI,GAAG,WAAW,IAAI;AAAA,EAC7B;AAAA,EAEO,MAAM,MAAc,UAAkB,IAAI,IAA4B;AAC3E,SAAK,IAAI,4BAAgB,qBAAS,KAAK,EAAE,MAAM,OAAO,CAAC;AAEvD,QAAI,IAAI;AAEN,iBAAW,IAAI,CAAC;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,IAAI,aAAa;AACf,WAAO,KAAK,IAAI,GAAG;AAAA,EACrB;AAAA,EAEO,MAAM,MAAe,MAAe;AACzC,SAAK,IAAI,GAAG,MAAM,MAAM,IAAI;AAAA,EAC9B;AAAA,EAEO,MAAM,MAAe,MAAe;AACzC,uBAAO,KAAK,mEAAmE;AAC/E,QAAI;AACF,YAAM,IAAI,MAAM;AAAA,IAClB,SAAS,GAAQ;AACf,yBAAO,KAAK,EAAE,KAAK;AAAA,IACrB;AACA,SAAK,MAAM,MAAM,IAAI;AAAA,EACvB;AAAA,EAEO,SAAS;AACd,WAAO,EAAE,WAAW,KAAK,WAAW,YAAY,KAAK,WAAW;AAAA,EAClE;AACF;",
6
6
  "names": ["EventEmitter"]
7
7
  }
@@ -9,11 +9,10 @@ var WebSocketWrapper = class extends EventEmitter {
9
9
  };
10
10
  var WebSocketClient = class {
11
11
  constructor(id, ref) {
12
- this.id = id;
13
- this.ref = ref;
14
12
  this.state = ClientState.JOINING;
15
13
  this._enqueuedMessages = [];
16
- this.sessionId = id;
14
+ this.id = this.sessionId = id;
15
+ this.ref = ref;
17
16
  }
18
17
  sendBytes(type, bytes, options) {
19
18
  debugMessage("send bytes(to %s): '%s' -> %j", this.sessionId, type, bytes);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/WebSocketClient.ts"],
4
- "sourcesContent": ["// <reference types=\"bun-types\" />\n\n// \"bun-types\" is currently conflicting with \"ws\" types.\n// @ts-ignore\nimport type { ServerWebSocket } from 'bun';\nimport EventEmitter from 'events';\n\nimport { Protocol, Client, ClientPrivate, ClientState, ISendOptions, getMessageBytes, logger, debugMessage } from '@colyseus/core';\n\nexport class WebSocketWrapper extends EventEmitter {\n constructor(public ws: ServerWebSocket<any>) {\n super();\n }\n}\n\nexport class WebSocketClient implements Client, ClientPrivate {\n public sessionId: string;\n public state: ClientState = ClientState.JOINING;\n public reconnectionToken: string;\n\n public _enqueuedMessages: any[] = [];\n public _afterNextPatchQueue;\n public _reconnectionToken: string;\n public _joinedAt: number;\n\n constructor(\n public id: string,\n public ref: WebSocketWrapper,\n ) {\n this.sessionId = id;\n }\n\n public sendBytes(type: string | number, bytes: Buffer | Uint8Array, options?: ISendOptions) {\n debugMessage(\"send bytes(to %s): '%s' -> %j\", this.sessionId, type, bytes);\n\n this.enqueueRaw(\n getMessageBytes.raw(Protocol.ROOM_DATA_BYTES, type, undefined, bytes),\n options,\n );\n }\n\n public send(messageOrType: any, messageOrOptions?: any | ISendOptions, options?: ISendOptions) {\n debugMessage(\"send(to %s): '%s' -> %j\", this.sessionId, messageOrType, messageOrOptions);\n\n this.enqueueRaw(\n getMessageBytes.raw(Protocol.ROOM_DATA, messageOrType, messageOrOptions),\n options,\n );\n }\n\n public enqueueRaw(data: Uint8Array | Buffer, options?: ISendOptions) {\n // use room's afterNextPatch queue\n if (options?.afterNextPatch) {\n this._afterNextPatchQueue.push([this, [data]]);\n return;\n }\n\n if (this.state === ClientState.JOINING) {\n // sending messages during `onJoin`.\n // - the client-side cannot register \"onMessage\" callbacks at this point.\n // - enqueue the messages to be send after JOIN_ROOM message has been sent\n // - create a new buffer for enqueued messages, as the underlying buffer might be modified\n this._enqueuedMessages.push(data);\n return;\n }\n\n this.raw(data, options);\n }\n\n public raw(data: Uint8Array | Buffer, options?: ISendOptions, cb?: (err?: Error) => void) {\n // skip if client not open\n\n // WebSocket is globally available on Bun runtime\n // @ts-ignore\n if (this.ref.ws.readyState !== WebSocket.OPEN) {\n return;\n }\n\n // FIXME: can we avoid creating a new buffer here?\n this.ref.ws.sendBinary(data);\n }\n\n public error(code: number, message: string = '', cb?: (err?: Error) => void) {\n this.raw(getMessageBytes[Protocol.ERROR](code, message));\n\n if (cb) {\n // (same API as \"ws\" transport)\n setTimeout(cb, 1);\n }\n }\n\n get readyState() {\n return this.ref.ws.readyState;\n }\n\n public leave(code?: number, data?: string) {\n this.ref.ws.close(code, data);\n }\n\n public close(code?: number, data?: string) {\n logger.warn('DEPRECATION WARNING: use client.leave() instead of client.close()');\n try {\n throw new Error();\n } catch (e) {\n logger.info(e.stack);\n }\n this.leave(code, data);\n }\n\n public toJSON() {\n return { sessionId: this.sessionId, readyState: this.readyState };\n }\n}\n"],
5
- "mappings": ";AAKA,OAAO,kBAAkB;AAEzB,SAAS,UAAiC,aAA2B,iBAAiB,QAAQ,oBAAoB;AAE3G,IAAM,mBAAN,cAA+B,aAAa;AAAA,EACjD,YAAmB,IAA0B;AAC3C,UAAM;AADW;AAAA,EAEnB;AACF;AAEO,IAAM,kBAAN,MAAuD;AAAA,EAU5D,YACS,IACA,KACP;AAFO;AACA;AAVT,SAAO,QAAqB,YAAY;AAGxC,SAAO,oBAA2B,CAAC;AASjC,SAAK,YAAY;AAAA,EACnB;AAAA,EAEO,UAAU,MAAuB,OAA4B,SAAwB;AAC1F,iBAAa,iCAAiC,KAAK,WAAW,MAAM,KAAK;AAEzE,SAAK;AAAA,MACH,gBAAgB,IAAI,SAAS,iBAAiB,MAAM,QAAW,KAAK;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AAAA,EAEO,KAAK,eAAoB,kBAAuC,SAAwB;AAC7F,iBAAa,2BAA2B,KAAK,WAAW,eAAe,gBAAgB;AAEvF,SAAK;AAAA,MACH,gBAAgB,IAAI,SAAS,WAAW,eAAe,gBAAgB;AAAA,MACvE;AAAA,IACF;AAAA,EACF;AAAA,EAEO,WAAW,MAA2B,SAAwB;AAEnE,QAAI,SAAS,gBAAgB;AAC3B,WAAK,qBAAqB,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7C;AAAA,IACF;AAEA,QAAI,KAAK,UAAU,YAAY,SAAS;AAKtC,WAAK,kBAAkB,KAAK,IAAI;AAChC;AAAA,IACF;AAEA,SAAK,IAAI,MAAM,OAAO;AAAA,EACxB;AAAA,EAEO,IAAI,MAA2B,SAAwB,IAA4B;AAKxF,QAAI,KAAK,IAAI,GAAG,eAAe,UAAU,MAAM;AAC7C;AAAA,IACF;AAGA,SAAK,IAAI,GAAG,WAAW,IAAI;AAAA,EAC7B;AAAA,EAEO,MAAM,MAAc,UAAkB,IAAI,IAA4B;AAC3E,SAAK,IAAI,gBAAgB,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC;AAEvD,QAAI,IAAI;AAEN,iBAAW,IAAI,CAAC;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,IAAI,aAAa;AACf,WAAO,KAAK,IAAI,GAAG;AAAA,EACrB;AAAA,EAEO,MAAM,MAAe,MAAe;AACzC,SAAK,IAAI,GAAG,MAAM,MAAM,IAAI;AAAA,EAC9B;AAAA,EAEO,MAAM,MAAe,MAAe;AACzC,WAAO,KAAK,mEAAmE;AAC/E,QAAI;AACF,YAAM,IAAI,MAAM;AAAA,IAClB,SAAS,GAAG;AACV,aAAO,KAAK,EAAE,KAAK;AAAA,IACrB;AACA,SAAK,MAAM,MAAM,IAAI;AAAA,EACvB;AAAA,EAEO,SAAS;AACd,WAAO,EAAE,WAAW,KAAK,WAAW,YAAY,KAAK,WAAW;AAAA,EAClE;AACF;",
4
+ "sourcesContent": ["// <reference types=\"bun-types\" />\n\n// \"bun-types\" is currently conflicting with \"ws\" types.\n// @ts-ignore\nimport type { ServerWebSocket } from 'bun';\nimport EventEmitter from 'events';\n\nimport { Protocol, type Client, type ClientPrivate, ClientState, type ISendOptions, getMessageBytes, logger, debugMessage } from '@colyseus/core';\n\nexport class WebSocketWrapper extends EventEmitter {\n public ws: ServerWebSocket<any>;\n\n constructor(ws: ServerWebSocket<any>) {\n super();\n this.ws = ws;\n }\n}\n\nexport class WebSocketClient implements Client, ClientPrivate {\n '~messages': any;\n\n public id: string;\n public ref: WebSocketWrapper;\n\n public sessionId: string;\n public state: ClientState = ClientState.JOINING;\n public reconnectionToken: string;\n\n public _enqueuedMessages: any[] = [];\n public _afterNextPatchQueue;\n public _reconnectionToken: string;\n public _joinedAt: number;\n\n constructor(id: string, ref: WebSocketWrapper,) {\n this.id = this.sessionId = id;\n this.ref = ref;\n }\n\n public sendBytes(type: string | number, bytes: Buffer | Uint8Array, options?: ISendOptions) {\n debugMessage(\"send bytes(to %s): '%s' -> %j\", this.sessionId, type, bytes);\n\n this.enqueueRaw(\n getMessageBytes.raw(Protocol.ROOM_DATA_BYTES, type, undefined, bytes),\n options,\n );\n }\n\n public send(messageOrType: any, messageOrOptions?: any | ISendOptions, options?: ISendOptions) {\n debugMessage(\"send(to %s): '%s' -> %j\", this.sessionId, messageOrType, messageOrOptions);\n\n this.enqueueRaw(\n getMessageBytes.raw(Protocol.ROOM_DATA, messageOrType, messageOrOptions),\n options,\n );\n }\n\n public enqueueRaw(data: Uint8Array | Buffer, options?: ISendOptions) {\n // use room's afterNextPatch queue\n if (options?.afterNextPatch) {\n this._afterNextPatchQueue.push([this, [data]]);\n return;\n }\n\n if (this.state === ClientState.JOINING) {\n // sending messages during `onJoin`.\n // - the client-side cannot register \"onMessage\" callbacks at this point.\n // - enqueue the messages to be send after JOIN_ROOM message has been sent\n // - create a new buffer for enqueued messages, as the underlying buffer might be modified\n this._enqueuedMessages.push(data);\n return;\n }\n\n this.raw(data, options);\n }\n\n public raw(data: Uint8Array | Buffer, options?: ISendOptions, cb?: (err?: Error) => void) {\n // skip if client not open\n\n // WebSocket is globally available on Bun runtime\n // @ts-ignore\n if (this.ref.ws.readyState !== WebSocket.OPEN) {\n return;\n }\n\n // FIXME: can we avoid creating a new buffer here?\n this.ref.ws.sendBinary(data);\n }\n\n public error(code: number, message: string = '', cb?: (err?: Error) => void) {\n this.raw(getMessageBytes[Protocol.ERROR](code, message));\n\n if (cb) {\n // (same API as \"ws\" transport)\n setTimeout(cb, 1);\n }\n }\n\n get readyState() {\n return this.ref.ws.readyState;\n }\n\n public leave(code?: number, data?: string) {\n this.ref.ws.close(code, data);\n }\n\n public close(code?: number, data?: string) {\n logger.warn('DEPRECATION WARNING: use client.leave() instead of client.close()');\n try {\n throw new Error();\n } catch (e: any) {\n logger.info(e.stack);\n }\n this.leave(code, data);\n }\n\n public toJSON() {\n return { sessionId: this.sessionId, readyState: this.readyState };\n }\n}\n"],
5
+ "mappings": ";AAKA,OAAO,kBAAkB;AAEzB,SAAS,UAA2C,aAAgC,iBAAiB,QAAQ,oBAAoB;AAE1H,IAAM,mBAAN,cAA+B,aAAa;AAAA,EAGjD,YAAY,IAA0B;AACpC,UAAM;AACN,SAAK,KAAK;AAAA,EACZ;AACF;AAEO,IAAM,kBAAN,MAAuD;AAAA,EAe5D,YAAY,IAAY,KAAwB;AARhD,SAAO,QAAqB,YAAY;AAGxC,SAAO,oBAA2B,CAAC;AAMjC,SAAK,KAAK,KAAK,YAAY;AAC3B,SAAK,MAAM;AAAA,EACb;AAAA,EAEO,UAAU,MAAuB,OAA4B,SAAwB;AAC1F,iBAAa,iCAAiC,KAAK,WAAW,MAAM,KAAK;AAEzE,SAAK;AAAA,MACH,gBAAgB,IAAI,SAAS,iBAAiB,MAAM,QAAW,KAAK;AAAA,MACpE;AAAA,IACF;AAAA,EACF;AAAA,EAEO,KAAK,eAAoB,kBAAuC,SAAwB;AAC7F,iBAAa,2BAA2B,KAAK,WAAW,eAAe,gBAAgB;AAEvF,SAAK;AAAA,MACH,gBAAgB,IAAI,SAAS,WAAW,eAAe,gBAAgB;AAAA,MACvE;AAAA,IACF;AAAA,EACF;AAAA,EAEO,WAAW,MAA2B,SAAwB;AAEnE,QAAI,SAAS,gBAAgB;AAC3B,WAAK,qBAAqB,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AAC7C;AAAA,IACF;AAEA,QAAI,KAAK,UAAU,YAAY,SAAS;AAKtC,WAAK,kBAAkB,KAAK,IAAI;AAChC;AAAA,IACF;AAEA,SAAK,IAAI,MAAM,OAAO;AAAA,EACxB;AAAA,EAEO,IAAI,MAA2B,SAAwB,IAA4B;AAKxF,QAAI,KAAK,IAAI,GAAG,eAAe,UAAU,MAAM;AAC7C;AAAA,IACF;AAGA,SAAK,IAAI,GAAG,WAAW,IAAI;AAAA,EAC7B;AAAA,EAEO,MAAM,MAAc,UAAkB,IAAI,IAA4B;AAC3E,SAAK,IAAI,gBAAgB,SAAS,KAAK,EAAE,MAAM,OAAO,CAAC;AAEvD,QAAI,IAAI;AAEN,iBAAW,IAAI,CAAC;AAAA,IAClB;AAAA,EACF;AAAA,EAEA,IAAI,aAAa;AACf,WAAO,KAAK,IAAI,GAAG;AAAA,EACrB;AAAA,EAEO,MAAM,MAAe,MAAe;AACzC,SAAK,IAAI,GAAG,MAAM,MAAM,IAAI;AAAA,EAC9B;AAAA,EAEO,MAAM,MAAe,MAAe;AACzC,WAAO,KAAK,mEAAmE;AAC/E,QAAI;AACF,YAAM,IAAI,MAAM;AAAA,IAClB,SAAS,GAAQ;AACf,aAAO,KAAK,EAAE,KAAK;AAAA,IACrB;AACA,SAAK,MAAM,MAAM,IAAI;AAAA,EACvB;AAAA,EAEO,SAAS;AACd,WAAO,EAAE,WAAW,KAAK,WAAW,YAAY,KAAK,WAAW;AAAA,EAClE;AACF;",
6
6
  "names": []
7
7
  }
package/build/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  var __defProp = Object.defineProperty;
2
3
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -15,14 +16,14 @@ var __copyProps = (to, from, except, desc) => {
15
16
  return to;
16
17
  };
17
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
- var src_exports = {};
19
- __export(src_exports, {
19
+ var index_exports = {};
20
+ __export(index_exports, {
20
21
  BunWebSockets: () => import_BunWebSockets.BunWebSockets,
21
22
  WebSocketClient: () => import_WebSocketClient.WebSocketClient
22
23
  });
23
- module.exports = __toCommonJS(src_exports);
24
- var import_WebSocketClient = require("./WebSocketClient.js");
25
- var import_BunWebSockets = require("./BunWebSockets.js");
24
+ module.exports = __toCommonJS(index_exports);
25
+ var import_WebSocketClient = require("./WebSocketClient.ts");
26
+ var import_BunWebSockets = require("./BunWebSockets.ts");
26
27
  // Annotate the CommonJS export names for ESM import in node:
27
28
  0 && (module.exports = {
28
29
  BunWebSockets,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["export { WebSocketClient } from './WebSocketClient.js';\nexport { BunWebSockets, type TransportOptions } from './BunWebSockets.js';"],
5
- "mappings": ";;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAAgC;AAChC,2BAAqD;",
4
+ "sourcesContent": ["export { WebSocketClient } from './WebSocketClient.ts';\nexport { BunWebSockets, type TransportOptions } from './BunWebSockets.ts';"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAAgC;AAChC,2BAAqD;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts"],
4
- "sourcesContent": ["export { WebSocketClient } from './WebSocketClient.js';\nexport { BunWebSockets, type TransportOptions } from './BunWebSockets.js';"],
4
+ "sourcesContent": ["export { WebSocketClient } from './WebSocketClient.ts';\nexport { BunWebSockets, type TransportOptions } from './BunWebSockets.ts';"],
5
5
  "mappings": ";AAAA,SAAS,uBAAuB;AAChC,SAAS,qBAA4C;",
6
6
  "names": []
7
7
  }
@@ -1,24 +1,23 @@
1
1
  import { ServerWebSocket, WebSocketHandler } from 'bun';
2
- import type { Application, Request, Response } from "express";
2
+ import type { Application } from "express";
3
3
  import { Transport } from '@colyseus/core';
4
- import { WebSocketWrapper } from './WebSocketClient.js';
4
+ import { WebSocketWrapper } from './WebSocketClient.ts';
5
5
  export type TransportOptions = Partial<Omit<WebSocketHandler, "message" | "open" | "drain" | "close" | "ping" | "pong">>;
6
6
  interface WebSocketData {
7
7
  url: URL;
8
8
  headers: any;
9
9
  }
10
10
  export declare class BunWebSockets extends Transport {
11
- private options;
12
11
  expressApp: Application;
13
12
  protected clients: ServerWebSocket<WebSocketData>[];
14
13
  protected clientWrappers: WeakMap<ServerWebSocket<WebSocketData>, WebSocketWrapper>;
15
14
  private _listening;
16
15
  private _originalRawSend;
16
+ private options;
17
17
  constructor(options?: TransportOptions);
18
18
  listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void): this;
19
19
  shutdown(): void;
20
20
  simulateLatency(milliseconds: number): void;
21
21
  protected onConnection(rawClient: ServerWebSocket<WebSocketData>): Promise<void>;
22
- protected handleMatchMakeRequest(req: Request, res: Response): Promise<void>;
23
22
  }
24
23
  export {};
@@ -1,11 +1,12 @@
1
1
  import type { ServerWebSocket } from 'bun';
2
2
  import EventEmitter from 'events';
3
- import { Client, ClientPrivate, ClientState, ISendOptions } from '@colyseus/core';
3
+ import { type Client, type ClientPrivate, ClientState, type ISendOptions } from '@colyseus/core';
4
4
  export declare class WebSocketWrapper extends EventEmitter {
5
5
  ws: ServerWebSocket<any>;
6
6
  constructor(ws: ServerWebSocket<any>);
7
7
  }
8
8
  export declare class WebSocketClient implements Client, ClientPrivate {
9
+ '~messages': any;
9
10
  id: string;
10
11
  ref: WebSocketWrapper;
11
12
  sessionId: string;
@@ -0,0 +1,2 @@
1
+ export { WebSocketClient } from './WebSocketClient.ts';
2
+ export { BunWebSockets, type TransportOptions } from './BunWebSockets.ts';
package/package.json CHANGED
@@ -1,24 +1,29 @@
1
1
  {
2
2
  "name": "@colyseus/bun-websockets",
3
- "version": "0.16.5",
3
+ "version": "0.17.1",
4
+ "type": "module",
4
5
  "input": "./src/index.ts",
5
6
  "main": "./build/index.js",
6
- "module": "./build/index.mjs",
7
+ "module": "./src/index.ts",
7
8
  "typings": "./build/index.d.ts",
8
9
  "exports": {
9
10
  ".": {
10
11
  "types": "./build/index.d.ts",
11
- "import": "./build/index.mjs",
12
+ "import": "./src/index.ts",
12
13
  "require": "./build/index.js"
14
+ },
15
+ "./*": {
16
+ "types": "./build/*.d.ts",
17
+ "import": "./src/*.ts",
18
+ "require": "./build/*.js"
13
19
  }
14
20
  },
15
21
  "dependencies": {
16
- "bun-serve-express": "^1.0.5",
17
- "express": ">=4.16.0",
18
- "@colyseus/core": "^0.16.21"
22
+ "@colyseus/core": "^0.17.1"
19
23
  },
20
24
  "devDependencies": {
21
- "@colyseus/core": "^0.16.21"
25
+ "bun-types": "^1.2.0",
26
+ "@colyseus/core": "^0.17.1"
22
27
  },
23
28
  "author": "Endel Dreyer",
24
29
  "license": "MIT",
@@ -30,8 +35,7 @@
30
35
  ],
31
36
  "files": [
32
37
  "build",
33
- "LICENSE",
34
- "README.md"
38
+ "src"
35
39
  ],
36
40
  "repository": {
37
41
  "type": "git",
@@ -0,0 +1,160 @@
1
+ // <reference types="bun-types" />
2
+
3
+ // "bun-types" is currently conflicting with "ws" types.
4
+ // @ts-ignore
5
+ import { ServerWebSocket, WebSocketHandler } from 'bun';
6
+
7
+ // import bunExpress from 'bun-serve-express';
8
+ import type { Application, Request, Response } from "express";
9
+
10
+ import { HttpServerMock, matchMaker, Protocol, Transport, debugAndPrintError, getBearerToken, CloseCode, connectClientToRoom } from '@colyseus/core';
11
+ import { WebSocketClient, WebSocketWrapper } from './WebSocketClient.ts';
12
+
13
+ export type TransportOptions = Partial<Omit<WebSocketHandler, "message" | "open" | "drain" | "close" | "ping" | "pong">>;
14
+
15
+ interface WebSocketData {
16
+ url: URL;
17
+ headers: any;
18
+ }
19
+
20
+ export class BunWebSockets extends Transport {
21
+ public expressApp: Application;
22
+
23
+ protected clients: ServerWebSocket<WebSocketData>[] = [];
24
+ protected clientWrappers = new WeakMap<ServerWebSocket<WebSocketData>, WebSocketWrapper>();
25
+
26
+ private _listening: any;
27
+ private _originalRawSend: typeof WebSocketClient.prototype.raw | null = null;
28
+ private options: TransportOptions = {};
29
+
30
+ constructor(options: TransportOptions = {}) {
31
+ super();
32
+
33
+ const self = this;
34
+
35
+ this.options = options;
36
+
37
+ // this.expressApp = bunExpress({
38
+ // websocket: {
39
+ // ...this.options,
40
+
41
+ // async open(ws) {
42
+ // await self.onConnection(ws);
43
+ // },
44
+
45
+ // message(ws, message) {
46
+ // self.clientWrappers.get(ws)?.emit('message', message);
47
+ // },
48
+
49
+ // close(ws, code, reason) {
50
+ // // remove from client list
51
+ // spliceOne(self.clients, self.clients.indexOf(ws));
52
+
53
+ // const clientWrapper = self.clientWrappers.get(ws);
54
+ // if (clientWrapper) {
55
+ // self.clientWrappers.delete(ws);
56
+
57
+ // // emit 'close' on wrapper
58
+ // clientWrapper.emit('close', code);
59
+ // }
60
+ // },
61
+ // }
62
+ // });
63
+
64
+ // Adding a mock object for Transport.server
65
+ if (!this.server) {
66
+ // @ts-ignore
67
+ this.server = new HttpServerMock();
68
+ }
69
+ }
70
+
71
+ public listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void) {
72
+ this._listening = this.expressApp.listen(port, listeningListener);
73
+
74
+ this.expressApp.use(`/${matchMaker.controller.matchmakeRoute}`, async (req, res) => {
75
+ try {
76
+ // TODO: use shared handler here
77
+ // await this.handleMatchMakeRequest(req, res);
78
+ } catch (e: any) {
79
+ res.status(500).json({
80
+ code: e.code,
81
+ error: e.message
82
+ });
83
+ }
84
+ });
85
+
86
+ // Mocking Transport.server behaviour, https://github.com/colyseus/colyseus/issues/458
87
+ // @ts-ignore
88
+ this.server.emit("listening");
89
+
90
+ return this;
91
+ }
92
+
93
+ public shutdown() {
94
+ if (this._listening) {
95
+ this._listening.close();
96
+
97
+ // @ts-ignore
98
+ this.server.emit("close"); // Mocking Transport.server behaviour, https://github.com/colyseus/colyseus/issues/458
99
+ }
100
+ }
101
+
102
+ public simulateLatency(milliseconds: number) {
103
+ if (this._originalRawSend == null) {
104
+ this._originalRawSend = WebSocketClient.prototype.raw;
105
+ }
106
+
107
+ const originalRawSend = this._originalRawSend;
108
+ WebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalRawSend : function (...args: any[]) {
109
+ let [buf, ...rest] = args;
110
+ buf = Buffer.from(buf);
111
+ // @ts-ignore
112
+ setTimeout(() => originalRawSend.apply(this, [buf, ...rest]), milliseconds);
113
+ };
114
+ }
115
+
116
+ protected async onConnection(rawClient: ServerWebSocket<WebSocketData>) {
117
+ const wrapper = new WebSocketWrapper(rawClient);
118
+ // keep reference to client and its wrapper
119
+ this.clients.push(rawClient);
120
+ this.clientWrappers.set(rawClient, wrapper);
121
+
122
+ const parsedURL = new URL(rawClient.data.url);
123
+
124
+ const sessionId = parsedURL.searchParams.get("sessionId");
125
+ const processAndRoomId = parsedURL.pathname.match(/\/[a-zA-Z0-9_\-]+\/([a-zA-Z0-9_\-]+)$/);
126
+ const roomId = processAndRoomId && processAndRoomId[1];
127
+
128
+ // If sessionId is not provided, allow ping-pong utility.
129
+ if (!sessionId && !roomId) {
130
+ // Disconnect automatically after 1 second if no message is received.
131
+ const timeout = setTimeout(() => rawClient.close(CloseCode.NORMAL_CLOSURE), 1000);
132
+ wrapper.on('message', (_) => rawClient.send(new Uint8Array([Protocol.PING])));
133
+ wrapper.on('close', () => clearTimeout(timeout));
134
+ return;
135
+ }
136
+
137
+ const room = matchMaker.getLocalRoomById(roomId);
138
+ const client = new WebSocketClient(sessionId, wrapper);
139
+ const reconnectionToken = parsedURL.searchParams.get("reconnectionToken");
140
+ const skipHandshake = (parsedURL.searchParams.has("skipHandshake"));
141
+
142
+ try {
143
+ await connectClientToRoom(room, client, {
144
+ token: parsedURL.searchParams.get("_authToken") ?? getBearerToken(rawClient.data.headers['authorization']),
145
+ headers: rawClient.data.headers,
146
+ ip: rawClient.data.headers['x-real-ip'] ?? rawClient.data.headers['x-forwarded-for'] ?? rawClient.remoteAddress,
147
+ }, {
148
+ reconnectionToken,
149
+ skipHandshake
150
+ });
151
+
152
+ } catch (e: any) {
153
+ debugAndPrintError(e);
154
+
155
+ // send error code to client then terminate
156
+ client.error(e.code, e.message, () => rawClient.close());
157
+ }
158
+ }
159
+
160
+ }
@@ -0,0 +1,119 @@
1
+ // <reference types="bun-types" />
2
+
3
+ // "bun-types" is currently conflicting with "ws" types.
4
+ // @ts-ignore
5
+ import type { ServerWebSocket } from 'bun';
6
+ import EventEmitter from 'events';
7
+
8
+ import { Protocol, type Client, type ClientPrivate, ClientState, type ISendOptions, getMessageBytes, logger, debugMessage } from '@colyseus/core';
9
+
10
+ export class WebSocketWrapper extends EventEmitter {
11
+ public ws: ServerWebSocket<any>;
12
+
13
+ constructor(ws: ServerWebSocket<any>) {
14
+ super();
15
+ this.ws = ws;
16
+ }
17
+ }
18
+
19
+ export class WebSocketClient implements Client, ClientPrivate {
20
+ '~messages': any;
21
+
22
+ public id: string;
23
+ public ref: WebSocketWrapper;
24
+
25
+ public sessionId: string;
26
+ public state: ClientState = ClientState.JOINING;
27
+ public reconnectionToken: string;
28
+
29
+ public _enqueuedMessages: any[] = [];
30
+ public _afterNextPatchQueue;
31
+ public _reconnectionToken: string;
32
+ public _joinedAt: number;
33
+
34
+ constructor(id: string, ref: WebSocketWrapper,) {
35
+ this.id = this.sessionId = id;
36
+ this.ref = ref;
37
+ }
38
+
39
+ public sendBytes(type: string | number, bytes: Buffer | Uint8Array, options?: ISendOptions) {
40
+ debugMessage("send bytes(to %s): '%s' -> %j", this.sessionId, type, bytes);
41
+
42
+ this.enqueueRaw(
43
+ getMessageBytes.raw(Protocol.ROOM_DATA_BYTES, type, undefined, bytes),
44
+ options,
45
+ );
46
+ }
47
+
48
+ public send(messageOrType: any, messageOrOptions?: any | ISendOptions, options?: ISendOptions) {
49
+ debugMessage("send(to %s): '%s' -> %j", this.sessionId, messageOrType, messageOrOptions);
50
+
51
+ this.enqueueRaw(
52
+ getMessageBytes.raw(Protocol.ROOM_DATA, messageOrType, messageOrOptions),
53
+ options,
54
+ );
55
+ }
56
+
57
+ public enqueueRaw(data: Uint8Array | Buffer, options?: ISendOptions) {
58
+ // use room's afterNextPatch queue
59
+ if (options?.afterNextPatch) {
60
+ this._afterNextPatchQueue.push([this, [data]]);
61
+ return;
62
+ }
63
+
64
+ if (this.state === ClientState.JOINING) {
65
+ // sending messages during `onJoin`.
66
+ // - the client-side cannot register "onMessage" callbacks at this point.
67
+ // - enqueue the messages to be send after JOIN_ROOM message has been sent
68
+ // - create a new buffer for enqueued messages, as the underlying buffer might be modified
69
+ this._enqueuedMessages.push(data);
70
+ return;
71
+ }
72
+
73
+ this.raw(data, options);
74
+ }
75
+
76
+ public raw(data: Uint8Array | Buffer, options?: ISendOptions, cb?: (err?: Error) => void) {
77
+ // skip if client not open
78
+
79
+ // WebSocket is globally available on Bun runtime
80
+ // @ts-ignore
81
+ if (this.ref.ws.readyState !== WebSocket.OPEN) {
82
+ return;
83
+ }
84
+
85
+ // FIXME: can we avoid creating a new buffer here?
86
+ this.ref.ws.sendBinary(data);
87
+ }
88
+
89
+ public error(code: number, message: string = '', cb?: (err?: Error) => void) {
90
+ this.raw(getMessageBytes[Protocol.ERROR](code, message));
91
+
92
+ if (cb) {
93
+ // (same API as "ws" transport)
94
+ setTimeout(cb, 1);
95
+ }
96
+ }
97
+
98
+ get readyState() {
99
+ return this.ref.ws.readyState;
100
+ }
101
+
102
+ public leave(code?: number, data?: string) {
103
+ this.ref.ws.close(code, data);
104
+ }
105
+
106
+ public close(code?: number, data?: string) {
107
+ logger.warn('DEPRECATION WARNING: use client.leave() instead of client.close()');
108
+ try {
109
+ throw new Error();
110
+ } catch (e: any) {
111
+ logger.info(e.stack);
112
+ }
113
+ this.leave(code, data);
114
+ }
115
+
116
+ public toJSON() {
117
+ return { sessionId: this.sessionId, readyState: this.readyState };
118
+ }
119
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { WebSocketClient } from './WebSocketClient.ts';
2
+ export { BunWebSockets, type TransportOptions } from './BunWebSockets.ts';
package/build/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- export { WebSocketClient } from './WebSocketClient.js';
2
- export { BunWebSockets, type TransportOptions } from './BunWebSockets.js';