@colyseus/bun-websockets 0.17.6 → 0.17.8

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,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // packages/transport/bun-websockets/src/BunWebSockets.ts
@@ -26,6 +36,7 @@ module.exports = __toCommonJS(BunWebSockets_exports);
26
36
  var import_bun = require("bun");
27
37
  var import_core = require("@colyseus/core");
28
38
  var import_WebSocketClient = require("./WebSocketClient.cjs");
39
+ var import_bun_serve_express = __toESM(require("bun-serve-express"), 1);
29
40
  var BunWebSockets = class extends import_core.Transport {
30
41
  constructor(options = {}) {
31
42
  super();
@@ -33,14 +44,14 @@ var BunWebSockets = class extends import_core.Transport {
33
44
  this.clientWrappers = /* @__PURE__ */ new WeakMap();
34
45
  this._originalRawSend = null;
35
46
  this.options = {};
47
+ if (options.maxPayloadLength === void 0) {
48
+ options.maxPayloadLength = 4 * 1024;
49
+ }
36
50
  this.options = options;
37
51
  }
38
52
  getExpressApp() {
39
53
  if (!this._expressApp) {
40
- console.warn("");
41
- console.warn("\u274C Error: Express integration not yet implemented for BunWebSockets");
42
- console.warn(" Please use bindRouter() instead or consider using uWebSocketsTransport");
43
- console.warn("");
54
+ this._expressApp = (0, import_bun_serve_express.default)({});
44
55
  }
45
56
  return this._expressApp;
46
57
  }
@@ -56,7 +67,7 @@ var BunWebSockets = class extends import_core.Transport {
56
67
  const url = new URL(req.url);
57
68
  if (server.upgrade(req, {
58
69
  data: {
59
- url: url.pathname + url.search,
70
+ url: url.pathname,
60
71
  searchParams: url.searchParams,
61
72
  headers: req.headers,
62
73
  remoteAddress: server.requestIP(req)?.address || "unknown"
@@ -64,43 +75,41 @@ var BunWebSockets = class extends import_core.Transport {
64
75
  })) {
65
76
  return;
66
77
  }
67
- if (self._router) {
68
- try {
69
- const corsHeaders = {
70
- ...import_core.matchMaker.controller.DEFAULT_CORS_HEADERS,
71
- ...import_core.matchMaker.controller.getCorsHeaders(req.headers)
72
- };
73
- if (req.method === "OPTIONS") {
74
- return new Response(null, {
75
- status: 204,
76
- headers: corsHeaders
77
- });
78
+ const corsHeaders = {
79
+ ...import_core.matchMaker.controller.DEFAULT_CORS_HEADERS,
80
+ ...import_core.matchMaker.controller.getCorsHeaders(req.headers)
81
+ };
82
+ if (req.method === "OPTIONS") {
83
+ return new Response(null, {
84
+ status: 204,
85
+ headers: corsHeaders
86
+ });
87
+ }
88
+ if (self._router?.findRoute(req.method, url.pathname) !== void 0) {
89
+ const response = await self._router.handler(req);
90
+ const headers = new Headers(response.headers);
91
+ Object.entries(corsHeaders).forEach(([key, value]) => {
92
+ if (!headers.has(key)) {
93
+ headers.set(key, value.toString());
78
94
  }
79
- const response = await self._router.handler(req);
80
- const headers = new Headers(response.headers);
81
- Object.entries(corsHeaders).forEach(([key, value]) => {
82
- if (!headers.has(key)) {
83
- headers.set(key, value.toString());
84
- }
85
- });
86
- return new Response(response.body, {
87
- status: response.status,
88
- statusText: response.statusText,
89
- headers
90
- });
91
- } catch (e) {
92
- (0, import_core.debugAndPrintError)(e);
93
- return new Response(JSON.stringify({
94
- code: e.code,
95
- error: e.message
96
- }), {
97
- status: 500,
98
- headers: { "Content-Type": "application/json" }
99
- });
95
+ });
96
+ return new Response(response.body, {
97
+ status: response.status,
98
+ statusText: response.statusText,
99
+ headers
100
+ });
101
+ } else if (self._expressApp) {
102
+ const ereq = new import_bun_serve_express.IncomingMessage(req, url, self._expressApp);
103
+ const eres = new import_bun_serve_express.ServerResponse(ereq, self._expressApp);
104
+ Object.entries(corsHeaders).forEach(([key, value]) => {
105
+ eres.setHeader(key, value.toString());
106
+ });
107
+ if (req.method !== "GET" && req.method !== "HEAD") {
108
+ await ereq.readBody();
100
109
  }
101
- }
102
- if (self._expressApp) {
103
- console.warn("Express integration not yet implemented for BunWebSockets");
110
+ ereq.complete = true;
111
+ self._expressApp["handle"](ereq, eres);
112
+ return await eres.getBunResponse();
104
113
  }
105
114
  return new Response("Not Found", { status: 404 });
106
115
  },
@@ -110,7 +119,7 @@ var BunWebSockets = class extends import_core.Transport {
110
119
  await self.onConnection(ws);
111
120
  },
112
121
  message(ws, message) {
113
- self.clientWrappers.get(ws)?.emit("message", message);
122
+ self.clientWrappers.get(ws)?.emit("message", Buffer.from(message));
114
123
  },
115
124
  close(ws, code, reason) {
116
125
  (0, import_core.spliceOne)(self.clients, self.clients.indexOf(ws));
@@ -162,16 +171,16 @@ var BunWebSockets = class extends import_core.Transport {
162
171
  const skipHandshake = searchParams.has("skipHandshake");
163
172
  try {
164
173
  await (0, import_core.connectClientToRoom)(room, client, {
165
- token: searchParams.get("_authToken") ?? (0, import_core.getBearerToken)(rawClient.data.headers["authorization"]),
174
+ token: searchParams.get("_authToken") ?? (0, import_core.getBearerToken)(rawClient.data.headers.get("authorization")),
166
175
  headers: rawClient.data.headers,
167
- ip: rawClient.data.headers["x-real-ip"] ?? rawClient.data.headers["x-forwarded-for"] ?? rawClient.data.remoteAddress
176
+ ip: rawClient.data.headers.get("x-real-ip") ?? rawClient.data.headers.get("x-forwarded-for") ?? rawClient.data.remoteAddress
168
177
  }, {
169
178
  reconnectionToken,
170
179
  skipHandshake
171
180
  });
172
181
  } catch (e) {
173
182
  (0, import_core.debugAndPrintError)(e);
174
- client.error(e.code, e.message, () => rawClient.close());
183
+ client.error(e.code, e.message, () => rawClient.close(reconnectionToken ? import_core.CloseCode.FAILED_TO_RECONNECT : import_core.CloseCode.WITH_ERROR));
175
184
  }
176
185
  }
177
186
  };
@@ -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 { Server, ServerWebSocket, WebSocketHandler } from 'bun';\nimport type { Application } from \"express\";\nimport type { Router } from '@colyseus/core';\n\nimport { matchMaker, Protocol, Transport, debugAndPrintError, getBearerToken, CloseCode, connectClientToRoom, spliceOne } from '@colyseus/core';\nimport { WebSocketClient, WebSocketWrapper } from './WebSocketClient.ts';\n\n// Bun global is available at runtime\ndeclare const Bun: any;\n\nexport type TransportOptions = Partial<Omit<WebSocketHandler<WebSocketData>, \"message\" | \"open\" | \"drain\" | \"close\" | \"ping\" | \"pong\">>;\n\ninterface WebSocketData {\n url: string;\n searchParams: URLSearchParams;\n headers: Headers;\n remoteAddress: string;\n}\n\nexport class BunWebSockets extends Transport {\n protected clients: ServerWebSocket<WebSocketData>[] = [];\n protected clientWrappers = new WeakMap<ServerWebSocket<WebSocketData>, WebSocketWrapper>();\n\n private _server: Server<WebSocketData> | undefined;\n private _expressApp: Application | undefined;\n private _router: Router | undefined;\n private _originalRawSend: typeof WebSocketClient.prototype.raw | null = null;\n private options: TransportOptions = {};\n\n constructor(options: TransportOptions = {}) {\n super();\n this.options = options;\n }\n\n public getExpressApp(): Application {\n if (!this._expressApp) {\n console.warn(\"\");\n console.warn(\"\u274C Error: Express integration not yet implemented for BunWebSockets\");\n console.warn(\" Please use bindRouter() instead or consider using uWebSocketsTransport\");\n console.warn(\"\");\n }\n return this._expressApp;\n }\n\n public bindRouter(router: Router) {\n this._router = router;\n }\n\n public listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void) {\n const self = this;\n\n this._server = Bun.serve({\n port,\n hostname,\n\n async fetch(req, server) {\n const url = new URL(req.url);\n\n // Try to upgrade to WebSocket\n if (server.upgrade(req, {\n data: {\n url: url.pathname + url.search,\n searchParams: url.searchParams,\n headers: req.headers as Headers,\n remoteAddress: server.requestIP(req)?.address || 'unknown',\n }\n })) {\n return; // WebSocket upgrade successful\n }\n\n // Handle HTTP requests through router\n if (self._router) {\n try {\n // Write CORS headers\n const corsHeaders = {\n ...matchMaker.controller.DEFAULT_CORS_HEADERS,\n ...matchMaker.controller.getCorsHeaders(req.headers)\n };\n\n // Handle OPTIONS requests\n if (req.method === \"OPTIONS\") {\n return new Response(null, {\n status: 204,\n headers: corsHeaders\n });\n }\n\n const response = await self._router.handler(req);\n\n // Add CORS headers to response\n const headers = new Headers(response.headers);\n Object.entries(corsHeaders).forEach(([key, value]) => {\n if (!headers.has(key)) {\n headers.set(key, value.toString());\n }\n });\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers\n });\n\n } catch (e: any) {\n debugAndPrintError(e);\n return new Response(JSON.stringify({\n code: e.code,\n error: e.message\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n }\n\n // Fallback to express app if available\n if (self._expressApp) {\n // TODO: Implement express integration for Bun\n console.warn(\"Express integration not yet implemented for BunWebSockets\");\n }\n\n return new Response(\"Not Found\", { status: 404 });\n },\n\n websocket: {\n ...this.options,\n\n async open(ws) {\n await self.onConnection(ws);\n },\n\n message(ws, message) {\n self.clientWrappers.get(ws)?.emit('message', message);\n },\n\n close(ws, code, reason) {\n // remove from client list\n spliceOne(self.clients, self.clients.indexOf(ws));\n\n const clientWrapper = self.clientWrappers.get(ws);\n if (clientWrapper) {\n self.clientWrappers.delete(ws);\n\n // emit 'close' on wrapper\n clientWrapper.emit('close', code);\n }\n },\n }\n });\n\n listeningListener?.();\n\n return this;\n }\n\n public shutdown() {\n if (this._server) {\n this._server.stop();\n }\n }\n\n public simulateLatency(milliseconds: number) {\n if (this._originalRawSend == null) {\n this._originalRawSend = WebSocketClient.prototype.raw;\n }\n\n const originalRawSend = this._originalRawSend;\n WebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalRawSend : function (...args: any[]) {\n let [buf, ...rest] = args;\n buf = Buffer.from(buf);\n // @ts-ignore\n setTimeout(() => originalRawSend.apply(this, [buf, ...rest]), milliseconds);\n };\n }\n\n protected async onConnection(rawClient: ServerWebSocket<WebSocketData>) {\n const wrapper = new WebSocketWrapper(rawClient);\n // keep reference to client and its wrapper\n this.clients.push(rawClient);\n this.clientWrappers.set(rawClient, wrapper);\n\n const url = rawClient.data.url;\n const searchParams = rawClient.data.searchParams;\n\n const sessionId = searchParams.get(\"sessionId\");\n const processAndRoomId = url.match(/\\/[a-zA-Z0-9_\\-]+\\/([a-zA-Z0-9_\\-]+)$/);\n const roomId = processAndRoomId && processAndRoomId[1];\n\n // If sessionId is not provided, allow ping-pong utility.\n if (!sessionId && !roomId) {\n // Disconnect automatically after 1 second if no message is received.\n const timeout = setTimeout(() => rawClient.close(CloseCode.NORMAL_CLOSURE), 1000);\n wrapper.on('message', (_) => rawClient.send(new Uint8Array([Protocol.PING])));\n wrapper.on('close', () => clearTimeout(timeout));\n return;\n }\n\n const room = matchMaker.getLocalRoomById(roomId);\n const client = new WebSocketClient(sessionId, wrapper);\n const reconnectionToken = searchParams.get(\"reconnectionToken\");\n const skipHandshake = searchParams.has(\"skipHandshake\");\n\n try {\n await connectClientToRoom(room, client, {\n token: searchParams.get(\"_authToken\") ?? getBearerToken(rawClient.data.headers['authorization']),\n headers: rawClient.data.headers,\n ip: rawClient.data.headers['x-real-ip'] ?? rawClient.data.headers['x-forwarded-for'] ?? rawClient.data.remoteAddress,\n }, {\n reconnectionToken,\n skipHandshake\n });\n\n } catch (e: any) {\n debugAndPrintError(e);\n\n // send error code to client then terminate\n client.error(e.code, e.message, () => rawClient.close());\n }\n }\n\n}\n"],
5
- "mappings": "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",
6
- "names": []
4
+ "sourcesContent": ["// <reference types=\"bun-types\" />\n\n// \"bun-types\" is currently conflicting with \"ws\" types.\n// @ts-ignore\nimport { Server, ServerWebSocket, WebSocketHandler } from 'bun';\n\nimport { matchMaker, Protocol, Transport, debugAndPrintError, getBearerToken, CloseCode, connectClientToRoom, spliceOne, type Router } from '@colyseus/core';\nimport { WebSocketClient, WebSocketWrapper } from './WebSocketClient.ts';\n\nimport type { Application } from \"express\";\nimport bunExpress, { IncomingMessage, ServerResponse } from 'bun-serve-express';\n\n// Bun global is available at runtime\ndeclare const Bun: any;\n\nexport type TransportOptions = Partial<Omit<WebSocketHandler<WebSocketData>, \"message\" | \"open\" | \"drain\" | \"close\" | \"ping\" | \"pong\">>;\n\ninterface WebSocketData {\n url: string;\n searchParams: URLSearchParams;\n headers: Headers;\n remoteAddress: string;\n}\n\nexport class BunWebSockets extends Transport {\n protected clients: ServerWebSocket<WebSocketData>[] = [];\n protected clientWrappers = new WeakMap<ServerWebSocket<WebSocketData>, WebSocketWrapper>();\n\n private _server: Server<WebSocketData> | undefined;\n private _expressApp: Application | undefined;\n private _router: Router | undefined;\n private _originalRawSend: typeof WebSocketClient.prototype.raw | null = null;\n private options: TransportOptions = {};\n\n constructor(options: TransportOptions = {}) {\n super();\n\n if (options.maxPayloadLength === undefined) {\n options.maxPayloadLength = 4 * 1024;\n }\n\n this.options = options;\n }\n\n public getExpressApp(): Application {\n if (!this._expressApp) {\n // @ts-ignore\n this._expressApp = bunExpress({});\n }\n return this._expressApp;\n }\n\n public bindRouter(router: Router) {\n this._router = router;\n }\n\n public listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void) {\n const self = this;\n\n this._server = Bun.serve({\n port,\n hostname,\n\n async fetch(req, server) {\n const url = new URL(req.url);\n\n // Try to upgrade to WebSocket\n if (server.upgrade(req, {\n data: {\n url: url.pathname,\n searchParams: url.searchParams,\n headers: req.headers as Headers,\n remoteAddress: server.requestIP(req)?.address || 'unknown',\n }\n })) {\n return; // WebSocket upgrade successful\n }\n\n // Handle HTTP requests through router\n // Write CORS headers\n const corsHeaders = {\n ...matchMaker.controller.DEFAULT_CORS_HEADERS,\n ...matchMaker.controller.getCorsHeaders(req.headers)\n };\n\n // Handle OPTIONS requests\n if (req.method === \"OPTIONS\") {\n return new Response(null, {\n status: 204,\n headers: corsHeaders\n });\n }\n\n if (self._router?.findRoute(req.method, url.pathname) !== undefined) {\n const response = await self._router.handler(req);\n\n // Add CORS headers to response\n const headers = new Headers(response.headers);\n Object.entries(corsHeaders).forEach(([key, value]) => {\n if (!headers.has(key)) {\n headers.set(key, value.toString());\n }\n });\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers\n });\n\n } else if (self._expressApp) {\n // Fallback to express routes\n const ereq = new IncomingMessage(req, url, self._expressApp);\n const eres = new ServerResponse(ereq, self._expressApp);\n\n // Apply CORS headers through the Express response wrapper\n Object.entries(corsHeaders).forEach(([key, value]) => {\n eres.setHeader(key, value.toString());\n });\n\n // Read the request body before passing to express\n if (req.method !== \"GET\" && req.method !== \"HEAD\") {\n await (ereq as any).readBody();\n }\n\n (ereq as any).complete = true;\n\n self._expressApp['handle'](ereq, eres);\n\n return await eres.getBunResponse();\n }\n\n return new Response(\"Not Found\", { status: 404 });\n },\n\n websocket: {\n ...this.options,\n\n async open(ws) {\n await self.onConnection(ws);\n },\n\n message(ws, message) {\n self.clientWrappers.get(ws)?.emit('message', Buffer.from(message));\n },\n\n close(ws, code, reason) {\n // remove from client list\n spliceOne(self.clients, self.clients.indexOf(ws));\n\n const clientWrapper = self.clientWrappers.get(ws);\n if (clientWrapper) {\n self.clientWrappers.delete(ws);\n\n // emit 'close' on wrapper\n clientWrapper.emit('close', code);\n }\n },\n }\n });\n\n listeningListener?.();\n\n return this;\n }\n\n public shutdown() {\n if (this._server) {\n this._server.stop();\n }\n }\n\n public simulateLatency(milliseconds: number) {\n if (this._originalRawSend == null) {\n this._originalRawSend = WebSocketClient.prototype.raw;\n }\n\n const originalRawSend = this._originalRawSend;\n WebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalRawSend : function (...args: any[]) {\n let [buf, ...rest] = args;\n buf = Buffer.from(buf);\n // @ts-ignore\n setTimeout(() => originalRawSend.apply(this, [buf, ...rest]), milliseconds);\n };\n }\n\n protected async onConnection(rawClient: ServerWebSocket<WebSocketData>) {\n const wrapper = new WebSocketWrapper(rawClient);\n // keep reference to client and its wrapper\n this.clients.push(rawClient);\n this.clientWrappers.set(rawClient, wrapper);\n\n const url = rawClient.data.url;\n const searchParams = rawClient.data.searchParams;\n\n const sessionId = searchParams.get(\"sessionId\");\n const processAndRoomId = url.match(/\\/[a-zA-Z0-9_\\-]+\\/([a-zA-Z0-9_\\-]+)$/);\n const roomId = processAndRoomId && processAndRoomId[1];\n\n // If sessionId is not provided, allow ping-pong utility.\n if (!sessionId && !roomId) {\n // Disconnect automatically after 1 second if no message is received.\n const timeout = setTimeout(() => rawClient.close(CloseCode.NORMAL_CLOSURE), 1000);\n wrapper.on('message', (_) => rawClient.send(new Uint8Array([Protocol.PING])));\n wrapper.on('close', () => clearTimeout(timeout));\n return;\n }\n\n const room = matchMaker.getLocalRoomById(roomId);\n const client = new WebSocketClient(sessionId, wrapper);\n const reconnectionToken = searchParams.get(\"reconnectionToken\");\n const skipHandshake = searchParams.has(\"skipHandshake\");\n\n try {\n await connectClientToRoom(room, client, {\n token: searchParams.get(\"_authToken\") ?? getBearerToken(rawClient.data.headers.get('authorization')),\n headers: rawClient.data.headers,\n ip: rawClient.data.headers.get('x-real-ip') ?? rawClient.data.headers.get('x-forwarded-for') ?? rawClient.data.remoteAddress,\n }, {\n reconnectionToken,\n skipHandshake\n });\n\n } catch (e: any) {\n debugAndPrintError(e);\n\n // send error code to client then terminate\n client.error(e.code, e.message, () =>\n rawClient.close(reconnectionToken\n ? CloseCode.FAILED_TO_RECONNECT\n : CloseCode.WITH_ERROR));\n }\n }\n\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["bunExpress"]
7
7
  }
@@ -1,8 +1,7 @@
1
1
  import { ServerWebSocket, WebSocketHandler } from 'bun';
2
- import type { Application } from "express";
3
- import type { Router } from '@colyseus/core';
4
- import { Transport } from '@colyseus/core';
2
+ import { Transport, type Router } from '@colyseus/core';
5
3
  import { WebSocketWrapper } from './WebSocketClient.ts';
4
+ import type { Application } from "express";
6
5
  export type TransportOptions = Partial<Omit<WebSocketHandler<WebSocketData>, "message" | "open" | "drain" | "close" | "ping" | "pong">>;
7
6
  interface WebSocketData {
8
7
  url: string;
@@ -2,6 +2,7 @@
2
2
  import "bun";
3
3
  import { matchMaker, Protocol, Transport, debugAndPrintError, getBearerToken, CloseCode, connectClientToRoom, spliceOne } from "@colyseus/core";
4
4
  import { WebSocketClient, WebSocketWrapper } from "./WebSocketClient.mjs";
5
+ import bunExpress, { IncomingMessage, ServerResponse } from "bun-serve-express";
5
6
  var BunWebSockets = class extends Transport {
6
7
  constructor(options = {}) {
7
8
  super();
@@ -9,14 +10,14 @@ var BunWebSockets = class extends Transport {
9
10
  this.clientWrappers = /* @__PURE__ */ new WeakMap();
10
11
  this._originalRawSend = null;
11
12
  this.options = {};
13
+ if (options.maxPayloadLength === void 0) {
14
+ options.maxPayloadLength = 4 * 1024;
15
+ }
12
16
  this.options = options;
13
17
  }
14
18
  getExpressApp() {
15
19
  if (!this._expressApp) {
16
- console.warn("");
17
- console.warn("\u274C Error: Express integration not yet implemented for BunWebSockets");
18
- console.warn(" Please use bindRouter() instead or consider using uWebSocketsTransport");
19
- console.warn("");
20
+ this._expressApp = bunExpress({});
20
21
  }
21
22
  return this._expressApp;
22
23
  }
@@ -32,7 +33,7 @@ var BunWebSockets = class extends Transport {
32
33
  const url = new URL(req.url);
33
34
  if (server.upgrade(req, {
34
35
  data: {
35
- url: url.pathname + url.search,
36
+ url: url.pathname,
36
37
  searchParams: url.searchParams,
37
38
  headers: req.headers,
38
39
  remoteAddress: server.requestIP(req)?.address || "unknown"
@@ -40,43 +41,41 @@ var BunWebSockets = class extends Transport {
40
41
  })) {
41
42
  return;
42
43
  }
43
- if (self._router) {
44
- try {
45
- const corsHeaders = {
46
- ...matchMaker.controller.DEFAULT_CORS_HEADERS,
47
- ...matchMaker.controller.getCorsHeaders(req.headers)
48
- };
49
- if (req.method === "OPTIONS") {
50
- return new Response(null, {
51
- status: 204,
52
- headers: corsHeaders
53
- });
44
+ const corsHeaders = {
45
+ ...matchMaker.controller.DEFAULT_CORS_HEADERS,
46
+ ...matchMaker.controller.getCorsHeaders(req.headers)
47
+ };
48
+ if (req.method === "OPTIONS") {
49
+ return new Response(null, {
50
+ status: 204,
51
+ headers: corsHeaders
52
+ });
53
+ }
54
+ if (self._router?.findRoute(req.method, url.pathname) !== void 0) {
55
+ const response = await self._router.handler(req);
56
+ const headers = new Headers(response.headers);
57
+ Object.entries(corsHeaders).forEach(([key, value]) => {
58
+ if (!headers.has(key)) {
59
+ headers.set(key, value.toString());
54
60
  }
55
- const response = await self._router.handler(req);
56
- const headers = new Headers(response.headers);
57
- Object.entries(corsHeaders).forEach(([key, value]) => {
58
- if (!headers.has(key)) {
59
- headers.set(key, value.toString());
60
- }
61
- });
62
- return new Response(response.body, {
63
- status: response.status,
64
- statusText: response.statusText,
65
- headers
66
- });
67
- } catch (e) {
68
- debugAndPrintError(e);
69
- return new Response(JSON.stringify({
70
- code: e.code,
71
- error: e.message
72
- }), {
73
- status: 500,
74
- headers: { "Content-Type": "application/json" }
75
- });
61
+ });
62
+ return new Response(response.body, {
63
+ status: response.status,
64
+ statusText: response.statusText,
65
+ headers
66
+ });
67
+ } else if (self._expressApp) {
68
+ const ereq = new IncomingMessage(req, url, self._expressApp);
69
+ const eres = new ServerResponse(ereq, self._expressApp);
70
+ Object.entries(corsHeaders).forEach(([key, value]) => {
71
+ eres.setHeader(key, value.toString());
72
+ });
73
+ if (req.method !== "GET" && req.method !== "HEAD") {
74
+ await ereq.readBody();
76
75
  }
77
- }
78
- if (self._expressApp) {
79
- console.warn("Express integration not yet implemented for BunWebSockets");
76
+ ereq.complete = true;
77
+ self._expressApp["handle"](ereq, eres);
78
+ return await eres.getBunResponse();
80
79
  }
81
80
  return new Response("Not Found", { status: 404 });
82
81
  },
@@ -86,7 +85,7 @@ var BunWebSockets = class extends Transport {
86
85
  await self.onConnection(ws);
87
86
  },
88
87
  message(ws, message) {
89
- self.clientWrappers.get(ws)?.emit("message", message);
88
+ self.clientWrappers.get(ws)?.emit("message", Buffer.from(message));
90
89
  },
91
90
  close(ws, code, reason) {
92
91
  spliceOne(self.clients, self.clients.indexOf(ws));
@@ -138,16 +137,16 @@ var BunWebSockets = class extends Transport {
138
137
  const skipHandshake = searchParams.has("skipHandshake");
139
138
  try {
140
139
  await connectClientToRoom(room, client, {
141
- token: searchParams.get("_authToken") ?? getBearerToken(rawClient.data.headers["authorization"]),
140
+ token: searchParams.get("_authToken") ?? getBearerToken(rawClient.data.headers.get("authorization")),
142
141
  headers: rawClient.data.headers,
143
- ip: rawClient.data.headers["x-real-ip"] ?? rawClient.data.headers["x-forwarded-for"] ?? rawClient.data.remoteAddress
142
+ ip: rawClient.data.headers.get("x-real-ip") ?? rawClient.data.headers.get("x-forwarded-for") ?? rawClient.data.remoteAddress
144
143
  }, {
145
144
  reconnectionToken,
146
145
  skipHandshake
147
146
  });
148
147
  } catch (e) {
149
148
  debugAndPrintError(e);
150
- client.error(e.code, e.message, () => rawClient.close());
149
+ client.error(e.code, e.message, () => rawClient.close(reconnectionToken ? CloseCode.FAILED_TO_RECONNECT : CloseCode.WITH_ERROR));
151
150
  }
152
151
  }
153
152
  };
@@ -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 { Server, ServerWebSocket, WebSocketHandler } from 'bun';\nimport type { Application } from \"express\";\nimport type { Router } from '@colyseus/core';\n\nimport { matchMaker, Protocol, Transport, debugAndPrintError, getBearerToken, CloseCode, connectClientToRoom, spliceOne } from '@colyseus/core';\nimport { WebSocketClient, WebSocketWrapper } from './WebSocketClient.ts';\n\n// Bun global is available at runtime\ndeclare const Bun: any;\n\nexport type TransportOptions = Partial<Omit<WebSocketHandler<WebSocketData>, \"message\" | \"open\" | \"drain\" | \"close\" | \"ping\" | \"pong\">>;\n\ninterface WebSocketData {\n url: string;\n searchParams: URLSearchParams;\n headers: Headers;\n remoteAddress: string;\n}\n\nexport class BunWebSockets extends Transport {\n protected clients: ServerWebSocket<WebSocketData>[] = [];\n protected clientWrappers = new WeakMap<ServerWebSocket<WebSocketData>, WebSocketWrapper>();\n\n private _server: Server<WebSocketData> | undefined;\n private _expressApp: Application | undefined;\n private _router: Router | undefined;\n private _originalRawSend: typeof WebSocketClient.prototype.raw | null = null;\n private options: TransportOptions = {};\n\n constructor(options: TransportOptions = {}) {\n super();\n this.options = options;\n }\n\n public getExpressApp(): Application {\n if (!this._expressApp) {\n console.warn(\"\");\n console.warn(\"\u274C Error: Express integration not yet implemented for BunWebSockets\");\n console.warn(\" Please use bindRouter() instead or consider using uWebSocketsTransport\");\n console.warn(\"\");\n }\n return this._expressApp;\n }\n\n public bindRouter(router: Router) {\n this._router = router;\n }\n\n public listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void) {\n const self = this;\n\n this._server = Bun.serve({\n port,\n hostname,\n\n async fetch(req, server) {\n const url = new URL(req.url);\n\n // Try to upgrade to WebSocket\n if (server.upgrade(req, {\n data: {\n url: url.pathname + url.search,\n searchParams: url.searchParams,\n headers: req.headers as Headers,\n remoteAddress: server.requestIP(req)?.address || 'unknown',\n }\n })) {\n return; // WebSocket upgrade successful\n }\n\n // Handle HTTP requests through router\n if (self._router) {\n try {\n // Write CORS headers\n const corsHeaders = {\n ...matchMaker.controller.DEFAULT_CORS_HEADERS,\n ...matchMaker.controller.getCorsHeaders(req.headers)\n };\n\n // Handle OPTIONS requests\n if (req.method === \"OPTIONS\") {\n return new Response(null, {\n status: 204,\n headers: corsHeaders\n });\n }\n\n const response = await self._router.handler(req);\n\n // Add CORS headers to response\n const headers = new Headers(response.headers);\n Object.entries(corsHeaders).forEach(([key, value]) => {\n if (!headers.has(key)) {\n headers.set(key, value.toString());\n }\n });\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers\n });\n\n } catch (e: any) {\n debugAndPrintError(e);\n return new Response(JSON.stringify({\n code: e.code,\n error: e.message\n }), {\n status: 500,\n headers: { 'Content-Type': 'application/json' }\n });\n }\n }\n\n // Fallback to express app if available\n if (self._expressApp) {\n // TODO: Implement express integration for Bun\n console.warn(\"Express integration not yet implemented for BunWebSockets\");\n }\n\n return new Response(\"Not Found\", { status: 404 });\n },\n\n websocket: {\n ...this.options,\n\n async open(ws) {\n await self.onConnection(ws);\n },\n\n message(ws, message) {\n self.clientWrappers.get(ws)?.emit('message', message);\n },\n\n close(ws, code, reason) {\n // remove from client list\n spliceOne(self.clients, self.clients.indexOf(ws));\n\n const clientWrapper = self.clientWrappers.get(ws);\n if (clientWrapper) {\n self.clientWrappers.delete(ws);\n\n // emit 'close' on wrapper\n clientWrapper.emit('close', code);\n }\n },\n }\n });\n\n listeningListener?.();\n\n return this;\n }\n\n public shutdown() {\n if (this._server) {\n this._server.stop();\n }\n }\n\n public simulateLatency(milliseconds: number) {\n if (this._originalRawSend == null) {\n this._originalRawSend = WebSocketClient.prototype.raw;\n }\n\n const originalRawSend = this._originalRawSend;\n WebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalRawSend : function (...args: any[]) {\n let [buf, ...rest] = args;\n buf = Buffer.from(buf);\n // @ts-ignore\n setTimeout(() => originalRawSend.apply(this, [buf, ...rest]), milliseconds);\n };\n }\n\n protected async onConnection(rawClient: ServerWebSocket<WebSocketData>) {\n const wrapper = new WebSocketWrapper(rawClient);\n // keep reference to client and its wrapper\n this.clients.push(rawClient);\n this.clientWrappers.set(rawClient, wrapper);\n\n const url = rawClient.data.url;\n const searchParams = rawClient.data.searchParams;\n\n const sessionId = searchParams.get(\"sessionId\");\n const processAndRoomId = url.match(/\\/[a-zA-Z0-9_\\-]+\\/([a-zA-Z0-9_\\-]+)$/);\n const roomId = processAndRoomId && processAndRoomId[1];\n\n // If sessionId is not provided, allow ping-pong utility.\n if (!sessionId && !roomId) {\n // Disconnect automatically after 1 second if no message is received.\n const timeout = setTimeout(() => rawClient.close(CloseCode.NORMAL_CLOSURE), 1000);\n wrapper.on('message', (_) => rawClient.send(new Uint8Array([Protocol.PING])));\n wrapper.on('close', () => clearTimeout(timeout));\n return;\n }\n\n const room = matchMaker.getLocalRoomById(roomId);\n const client = new WebSocketClient(sessionId, wrapper);\n const reconnectionToken = searchParams.get(\"reconnectionToken\");\n const skipHandshake = searchParams.has(\"skipHandshake\");\n\n try {\n await connectClientToRoom(room, client, {\n token: searchParams.get(\"_authToken\") ?? getBearerToken(rawClient.data.headers['authorization']),\n headers: rawClient.data.headers,\n ip: rawClient.data.headers['x-real-ip'] ?? rawClient.data.headers['x-forwarded-for'] ?? rawClient.data.remoteAddress,\n }, {\n reconnectionToken,\n skipHandshake\n });\n\n } catch (e: any) {\n debugAndPrintError(e);\n\n // send error code to client then terminate\n client.error(e.code, e.message, () => rawClient.close());\n }\n }\n\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["// <reference types=\"bun-types\" />\n\n// \"bun-types\" is currently conflicting with \"ws\" types.\n// @ts-ignore\nimport { Server, ServerWebSocket, WebSocketHandler } from 'bun';\n\nimport { matchMaker, Protocol, Transport, debugAndPrintError, getBearerToken, CloseCode, connectClientToRoom, spliceOne, type Router } from '@colyseus/core';\nimport { WebSocketClient, WebSocketWrapper } from './WebSocketClient.ts';\n\nimport type { Application } from \"express\";\nimport bunExpress, { IncomingMessage, ServerResponse } from 'bun-serve-express';\n\n// Bun global is available at runtime\ndeclare const Bun: any;\n\nexport type TransportOptions = Partial<Omit<WebSocketHandler<WebSocketData>, \"message\" | \"open\" | \"drain\" | \"close\" | \"ping\" | \"pong\">>;\n\ninterface WebSocketData {\n url: string;\n searchParams: URLSearchParams;\n headers: Headers;\n remoteAddress: string;\n}\n\nexport class BunWebSockets extends Transport {\n protected clients: ServerWebSocket<WebSocketData>[] = [];\n protected clientWrappers = new WeakMap<ServerWebSocket<WebSocketData>, WebSocketWrapper>();\n\n private _server: Server<WebSocketData> | undefined;\n private _expressApp: Application | undefined;\n private _router: Router | undefined;\n private _originalRawSend: typeof WebSocketClient.prototype.raw | null = null;\n private options: TransportOptions = {};\n\n constructor(options: TransportOptions = {}) {\n super();\n\n if (options.maxPayloadLength === undefined) {\n options.maxPayloadLength = 4 * 1024;\n }\n\n this.options = options;\n }\n\n public getExpressApp(): Application {\n if (!this._expressApp) {\n // @ts-ignore\n this._expressApp = bunExpress({});\n }\n return this._expressApp;\n }\n\n public bindRouter(router: Router) {\n this._router = router;\n }\n\n public listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void) {\n const self = this;\n\n this._server = Bun.serve({\n port,\n hostname,\n\n async fetch(req, server) {\n const url = new URL(req.url);\n\n // Try to upgrade to WebSocket\n if (server.upgrade(req, {\n data: {\n url: url.pathname,\n searchParams: url.searchParams,\n headers: req.headers as Headers,\n remoteAddress: server.requestIP(req)?.address || 'unknown',\n }\n })) {\n return; // WebSocket upgrade successful\n }\n\n // Handle HTTP requests through router\n // Write CORS headers\n const corsHeaders = {\n ...matchMaker.controller.DEFAULT_CORS_HEADERS,\n ...matchMaker.controller.getCorsHeaders(req.headers)\n };\n\n // Handle OPTIONS requests\n if (req.method === \"OPTIONS\") {\n return new Response(null, {\n status: 204,\n headers: corsHeaders\n });\n }\n\n if (self._router?.findRoute(req.method, url.pathname) !== undefined) {\n const response = await self._router.handler(req);\n\n // Add CORS headers to response\n const headers = new Headers(response.headers);\n Object.entries(corsHeaders).forEach(([key, value]) => {\n if (!headers.has(key)) {\n headers.set(key, value.toString());\n }\n });\n\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers\n });\n\n } else if (self._expressApp) {\n // Fallback to express routes\n const ereq = new IncomingMessage(req, url, self._expressApp);\n const eres = new ServerResponse(ereq, self._expressApp);\n\n // Apply CORS headers through the Express response wrapper\n Object.entries(corsHeaders).forEach(([key, value]) => {\n eres.setHeader(key, value.toString());\n });\n\n // Read the request body before passing to express\n if (req.method !== \"GET\" && req.method !== \"HEAD\") {\n await (ereq as any).readBody();\n }\n\n (ereq as any).complete = true;\n\n self._expressApp['handle'](ereq, eres);\n\n return await eres.getBunResponse();\n }\n\n return new Response(\"Not Found\", { status: 404 });\n },\n\n websocket: {\n ...this.options,\n\n async open(ws) {\n await self.onConnection(ws);\n },\n\n message(ws, message) {\n self.clientWrappers.get(ws)?.emit('message', Buffer.from(message));\n },\n\n close(ws, code, reason) {\n // remove from client list\n spliceOne(self.clients, self.clients.indexOf(ws));\n\n const clientWrapper = self.clientWrappers.get(ws);\n if (clientWrapper) {\n self.clientWrappers.delete(ws);\n\n // emit 'close' on wrapper\n clientWrapper.emit('close', code);\n }\n },\n }\n });\n\n listeningListener?.();\n\n return this;\n }\n\n public shutdown() {\n if (this._server) {\n this._server.stop();\n }\n }\n\n public simulateLatency(milliseconds: number) {\n if (this._originalRawSend == null) {\n this._originalRawSend = WebSocketClient.prototype.raw;\n }\n\n const originalRawSend = this._originalRawSend;\n WebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalRawSend : function (...args: any[]) {\n let [buf, ...rest] = args;\n buf = Buffer.from(buf);\n // @ts-ignore\n setTimeout(() => originalRawSend.apply(this, [buf, ...rest]), milliseconds);\n };\n }\n\n protected async onConnection(rawClient: ServerWebSocket<WebSocketData>) {\n const wrapper = new WebSocketWrapper(rawClient);\n // keep reference to client and its wrapper\n this.clients.push(rawClient);\n this.clientWrappers.set(rawClient, wrapper);\n\n const url = rawClient.data.url;\n const searchParams = rawClient.data.searchParams;\n\n const sessionId = searchParams.get(\"sessionId\");\n const processAndRoomId = url.match(/\\/[a-zA-Z0-9_\\-]+\\/([a-zA-Z0-9_\\-]+)$/);\n const roomId = processAndRoomId && processAndRoomId[1];\n\n // If sessionId is not provided, allow ping-pong utility.\n if (!sessionId && !roomId) {\n // Disconnect automatically after 1 second if no message is received.\n const timeout = setTimeout(() => rawClient.close(CloseCode.NORMAL_CLOSURE), 1000);\n wrapper.on('message', (_) => rawClient.send(new Uint8Array([Protocol.PING])));\n wrapper.on('close', () => clearTimeout(timeout));\n return;\n }\n\n const room = matchMaker.getLocalRoomById(roomId);\n const client = new WebSocketClient(sessionId, wrapper);\n const reconnectionToken = searchParams.get(\"reconnectionToken\");\n const skipHandshake = searchParams.has(\"skipHandshake\");\n\n try {\n await connectClientToRoom(room, client, {\n token: searchParams.get(\"_authToken\") ?? getBearerToken(rawClient.data.headers.get('authorization')),\n headers: rawClient.data.headers,\n ip: rawClient.data.headers.get('x-real-ip') ?? rawClient.data.headers.get('x-forwarded-for') ?? rawClient.data.remoteAddress,\n }, {\n reconnectionToken,\n skipHandshake\n });\n\n } catch (e: any) {\n debugAndPrintError(e);\n\n // send error code to client then terminate\n client.error(e.code, e.message, () =>\n rawClient.close(reconnectionToken\n ? CloseCode.FAILED_TO_RECONNECT\n : CloseCode.WITH_ERROR));\n }\n }\n\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -22,11 +22,11 @@ export declare class WebSocketClient implements Client, ClientPrivate {
22
22
  enqueueRaw(data: Uint8Array | Buffer, options?: ISendOptions): void;
23
23
  raw(data: Uint8Array | Buffer, options?: ISendOptions, cb?: (err?: Error) => void): void;
24
24
  error(code: number, message?: string, cb?: (err?: Error) => void): void;
25
- get readyState(): any;
25
+ get readyState(): Bun.WebSocketReadyState;
26
26
  leave(code?: number, data?: string): void;
27
27
  close(code?: number, data?: string): void;
28
28
  toJSON(): {
29
29
  sessionId: string;
30
- readyState: any;
30
+ readyState: Bun.WebSocketReadyState;
31
31
  };
32
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colyseus/bun-websockets",
3
- "version": "0.17.6",
3
+ "version": "0.17.8",
4
4
  "type": "module",
5
5
  "input": "./src/index.ts",
6
6
  "main": "./build/index.cjs",
@@ -21,11 +21,13 @@
21
21
  }
22
22
  },
23
23
  "dependencies": {
24
- "@colyseus/core": "^0.17.22"
24
+ "bun-serve-express": "^2.0.0",
25
+ "@colyseus/core": "^0.17.37"
25
26
  },
26
27
  "devDependencies": {
27
28
  "bun-types": "^1.2.0",
28
- "@colyseus/core": "^0.17.22"
29
+ "@colyseus/core": "^0.17.37",
30
+ "@colyseus/sdk": "^0.17.34"
29
31
  },
30
32
  "author": "Endel Dreyer",
31
33
  "license": "MIT",
@@ -3,12 +3,13 @@
3
3
  // "bun-types" is currently conflicting with "ws" types.
4
4
  // @ts-ignore
5
5
  import { Server, ServerWebSocket, WebSocketHandler } from 'bun';
6
- import express, { type Application } from "express";
7
- import type { Router } from '@colyseus/core';
8
6
 
9
- import { matchMaker, Protocol, Transport, debugAndPrintError, getBearerToken, CloseCode, connectClientToRoom, spliceOne } from '@colyseus/core';
7
+ import { matchMaker, Protocol, Transport, debugAndPrintError, getBearerToken, CloseCode, connectClientToRoom, spliceOne, type Router } from '@colyseus/core';
10
8
  import { WebSocketClient, WebSocketWrapper } from './WebSocketClient.ts';
11
9
 
10
+ import type { Application } from "express";
11
+ import bunExpress, { IncomingMessage, ServerResponse } from 'bun-serve-express';
12
+
12
13
  // Bun global is available at runtime
13
14
  declare const Bun: any;
14
15
 
@@ -33,12 +34,18 @@ export class BunWebSockets extends Transport {
33
34
 
34
35
  constructor(options: TransportOptions = {}) {
35
36
  super();
37
+
38
+ if (options.maxPayloadLength === undefined) {
39
+ options.maxPayloadLength = 4 * 1024;
40
+ }
41
+
36
42
  this.options = options;
37
43
  }
38
44
 
39
45
  public getExpressApp(): Application {
40
46
  if (!this._expressApp) {
41
- this._expressApp = express();
47
+ // @ts-ignore
48
+ this._expressApp = bunExpress({});
42
49
  }
43
50
  return this._expressApp;
44
51
  }
@@ -60,7 +67,7 @@ export class BunWebSockets extends Transport {
60
67
  // Try to upgrade to WebSocket
61
68
  if (server.upgrade(req, {
62
69
  data: {
63
- url: url.pathname + url.search,
70
+ url: url.pathname,
64
71
  searchParams: url.searchParams,
65
72
  headers: req.headers as Headers,
66
73
  remoteAddress: server.requestIP(req)?.address || 'unknown',
@@ -70,54 +77,57 @@ export class BunWebSockets extends Transport {
70
77
  }
71
78
 
72
79
  // Handle HTTP requests through router
73
- if (self._router) {
74
- try {
75
- // Write CORS headers
76
- const corsHeaders = {
77
- ...matchMaker.controller.DEFAULT_CORS_HEADERS,
78
- ...matchMaker.controller.getCorsHeaders(req.headers)
79
- };
80
-
81
- // Handle OPTIONS requests
82
- if (req.method === "OPTIONS") {
83
- return new Response(null, {
84
- status: 204,
85
- headers: corsHeaders
86
- });
87
- }
80
+ // Write CORS headers
81
+ const corsHeaders = {
82
+ ...matchMaker.controller.DEFAULT_CORS_HEADERS,
83
+ ...matchMaker.controller.getCorsHeaders(req.headers)
84
+ };
85
+
86
+ // Handle OPTIONS requests
87
+ if (req.method === "OPTIONS") {
88
+ return new Response(null, {
89
+ status: 204,
90
+ headers: corsHeaders
91
+ });
92
+ }
93
+
94
+ if (self._router?.findRoute(req.method, url.pathname) !== undefined) {
95
+ const response = await self._router.handler(req);
88
96
 
89
- const response = await self._router.handler(req);
90
-
91
- // Add CORS headers to response
92
- const headers = new Headers(response.headers);
93
- Object.entries(corsHeaders).forEach(([key, value]) => {
94
- if (!headers.has(key)) {
95
- headers.set(key, value.toString());
96
- }
97
- });
98
-
99
- return new Response(response.body, {
100
- status: response.status,
101
- statusText: response.statusText,
102
- headers
103
- });
104
-
105
- } catch (e: any) {
106
- debugAndPrintError(e);
107
- return new Response(JSON.stringify({
108
- code: e.code,
109
- error: e.message
110
- }), {
111
- status: 500,
112
- headers: { 'Content-Type': 'application/json' }
113
- });
97
+ // Add CORS headers to response
98
+ const headers = new Headers(response.headers);
99
+ Object.entries(corsHeaders).forEach(([key, value]) => {
100
+ if (!headers.has(key)) {
101
+ headers.set(key, value.toString());
102
+ }
103
+ });
104
+
105
+ return new Response(response.body, {
106
+ status: response.status,
107
+ statusText: response.statusText,
108
+ headers
109
+ });
110
+
111
+ } else if (self._expressApp) {
112
+ // Fallback to express routes
113
+ const ereq = new IncomingMessage(req, url, self._expressApp);
114
+ const eres = new ServerResponse(ereq, self._expressApp);
115
+
116
+ // Apply CORS headers through the Express response wrapper
117
+ Object.entries(corsHeaders).forEach(([key, value]) => {
118
+ eres.setHeader(key, value.toString());
119
+ });
120
+
121
+ // Read the request body before passing to express
122
+ if (req.method !== "GET" && req.method !== "HEAD") {
123
+ await (ereq as any).readBody();
114
124
  }
115
- }
116
125
 
117
- // Fallback to express app if available
118
- if (self._expressApp) {
119
- // TODO: Implement express integration for Bun
120
- console.warn("Express integration not yet implemented for BunWebSockets");
126
+ (ereq as any).complete = true;
127
+
128
+ self._expressApp['handle'](ereq, eres);
129
+
130
+ return await eres.getBunResponse();
121
131
  }
122
132
 
123
133
  return new Response("Not Found", { status: 404 });
@@ -131,7 +141,7 @@ export class BunWebSockets extends Transport {
131
141
  },
132
142
 
133
143
  message(ws, message) {
134
- self.clientWrappers.get(ws)?.emit('message', message);
144
+ self.clientWrappers.get(ws)?.emit('message', Buffer.from(message));
135
145
  },
136
146
 
137
147
  close(ws, code, reason) {
@@ -203,9 +213,9 @@ export class BunWebSockets extends Transport {
203
213
 
204
214
  try {
205
215
  await connectClientToRoom(room, client, {
206
- token: searchParams.get("_authToken") ?? getBearerToken(rawClient.data.headers['authorization']),
216
+ token: searchParams.get("_authToken") ?? getBearerToken(rawClient.data.headers.get('authorization')),
207
217
  headers: rawClient.data.headers,
208
- ip: rawClient.data.headers['x-real-ip'] ?? rawClient.data.headers['x-forwarded-for'] ?? rawClient.data.remoteAddress,
218
+ ip: rawClient.data.headers.get('x-real-ip') ?? rawClient.data.headers.get('x-forwarded-for') ?? rawClient.data.remoteAddress,
209
219
  }, {
210
220
  reconnectionToken,
211
221
  skipHandshake
@@ -215,7 +225,10 @@ export class BunWebSockets extends Transport {
215
225
  debugAndPrintError(e);
216
226
 
217
227
  // send error code to client then terminate
218
- client.error(e.code, e.message, () => rawClient.close());
228
+ client.error(e.code, e.message, () =>
229
+ rawClient.close(reconnectionToken
230
+ ? CloseCode.FAILED_TO_RECONNECT
231
+ : CloseCode.WITH_ERROR));
219
232
  }
220
233
  }
221
234