@colyseus/bun-websockets 0.17.7 → 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.
- package/build/BunWebSockets.cjs +42 -41
- package/build/BunWebSockets.cjs.map +3 -3
- package/build/BunWebSockets.d.ts +2 -3
- package/build/BunWebSockets.mjs +42 -41
- package/build/BunWebSockets.mjs.map +2 -2
- package/build/WebSocketClient.d.ts +2 -2
- package/package.json +5 -3
- package/src/BunWebSockets.ts +63 -53
package/build/BunWebSockets.cjs
CHANGED
|
@@ -34,9 +34,9 @@ __export(BunWebSockets_exports, {
|
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(BunWebSockets_exports);
|
|
36
36
|
var import_bun = require("bun");
|
|
37
|
-
var import_express = __toESM(require("express"), 1);
|
|
38
37
|
var import_core = require("@colyseus/core");
|
|
39
38
|
var import_WebSocketClient = require("./WebSocketClient.cjs");
|
|
39
|
+
var import_bun_serve_express = __toESM(require("bun-serve-express"), 1);
|
|
40
40
|
var BunWebSockets = class extends import_core.Transport {
|
|
41
41
|
constructor(options = {}) {
|
|
42
42
|
super();
|
|
@@ -44,11 +44,14 @@ var BunWebSockets = class extends import_core.Transport {
|
|
|
44
44
|
this.clientWrappers = /* @__PURE__ */ new WeakMap();
|
|
45
45
|
this._originalRawSend = null;
|
|
46
46
|
this.options = {};
|
|
47
|
+
if (options.maxPayloadLength === void 0) {
|
|
48
|
+
options.maxPayloadLength = 4 * 1024;
|
|
49
|
+
}
|
|
47
50
|
this.options = options;
|
|
48
51
|
}
|
|
49
52
|
getExpressApp() {
|
|
50
53
|
if (!this._expressApp) {
|
|
51
|
-
this._expressApp = (0,
|
|
54
|
+
this._expressApp = (0, import_bun_serve_express.default)({});
|
|
52
55
|
}
|
|
53
56
|
return this._expressApp;
|
|
54
57
|
}
|
|
@@ -64,7 +67,7 @@ var BunWebSockets = class extends import_core.Transport {
|
|
|
64
67
|
const url = new URL(req.url);
|
|
65
68
|
if (server.upgrade(req, {
|
|
66
69
|
data: {
|
|
67
|
-
url: url.pathname
|
|
70
|
+
url: url.pathname,
|
|
68
71
|
searchParams: url.searchParams,
|
|
69
72
|
headers: req.headers,
|
|
70
73
|
remoteAddress: server.requestIP(req)?.address || "unknown"
|
|
@@ -72,43 +75,41 @@ var BunWebSockets = class extends import_core.Transport {
|
|
|
72
75
|
})) {
|
|
73
76
|
return;
|
|
74
77
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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());
|
|
86
94
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
return new Response(JSON.stringify({
|
|
102
|
-
code: e.code,
|
|
103
|
-
error: e.message
|
|
104
|
-
}), {
|
|
105
|
-
status: 500,
|
|
106
|
-
headers: { "Content-Type": "application/json" }
|
|
107
|
-
});
|
|
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();
|
|
108
109
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
ereq.complete = true;
|
|
111
|
+
self._expressApp["handle"](ereq, eres);
|
|
112
|
+
return await eres.getBunResponse();
|
|
112
113
|
}
|
|
113
114
|
return new Response("Not Found", { status: 404 });
|
|
114
115
|
},
|
|
@@ -118,7 +119,7 @@ var BunWebSockets = class extends import_core.Transport {
|
|
|
118
119
|
await self.onConnection(ws);
|
|
119
120
|
},
|
|
120
121
|
message(ws, message) {
|
|
121
|
-
self.clientWrappers.get(ws)?.emit("message", message);
|
|
122
|
+
self.clientWrappers.get(ws)?.emit("message", Buffer.from(message));
|
|
122
123
|
},
|
|
123
124
|
close(ws, code, reason) {
|
|
124
125
|
(0, import_core.spliceOne)(self.clients, self.clients.indexOf(ws));
|
|
@@ -170,9 +171,9 @@ var BunWebSockets = class extends import_core.Transport {
|
|
|
170
171
|
const skipHandshake = searchParams.has("skipHandshake");
|
|
171
172
|
try {
|
|
172
173
|
await (0, import_core.connectClientToRoom)(room, client, {
|
|
173
|
-
token: searchParams.get("_authToken") ?? (0, import_core.getBearerToken)(rawClient.data.headers
|
|
174
|
+
token: searchParams.get("_authToken") ?? (0, import_core.getBearerToken)(rawClient.data.headers.get("authorization")),
|
|
174
175
|
headers: rawClient.data.headers,
|
|
175
|
-
ip: rawClient.data.headers
|
|
176
|
+
ip: rawClient.data.headers.get("x-real-ip") ?? rawClient.data.headers.get("x-forwarded-for") ?? rawClient.data.remoteAddress
|
|
176
177
|
}, {
|
|
177
178
|
reconnectionToken,
|
|
178
179
|
skipHandshake
|
|
@@ -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';\
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAIA,iBAA0D;
|
|
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
|
}
|
package/build/BunWebSockets.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { ServerWebSocket, WebSocketHandler } from 'bun';
|
|
2
|
-
import { type
|
|
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;
|
package/build/BunWebSockets.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// packages/transport/bun-websockets/src/BunWebSockets.ts
|
|
2
2
|
import "bun";
|
|
3
|
-
import express from "express";
|
|
4
3
|
import { matchMaker, Protocol, Transport, debugAndPrintError, getBearerToken, CloseCode, connectClientToRoom, spliceOne } from "@colyseus/core";
|
|
5
4
|
import { WebSocketClient, WebSocketWrapper } from "./WebSocketClient.mjs";
|
|
5
|
+
import bunExpress, { IncomingMessage, ServerResponse } from "bun-serve-express";
|
|
6
6
|
var BunWebSockets = class extends Transport {
|
|
7
7
|
constructor(options = {}) {
|
|
8
8
|
super();
|
|
@@ -10,11 +10,14 @@ var BunWebSockets = class extends Transport {
|
|
|
10
10
|
this.clientWrappers = /* @__PURE__ */ new WeakMap();
|
|
11
11
|
this._originalRawSend = null;
|
|
12
12
|
this.options = {};
|
|
13
|
+
if (options.maxPayloadLength === void 0) {
|
|
14
|
+
options.maxPayloadLength = 4 * 1024;
|
|
15
|
+
}
|
|
13
16
|
this.options = options;
|
|
14
17
|
}
|
|
15
18
|
getExpressApp() {
|
|
16
19
|
if (!this._expressApp) {
|
|
17
|
-
this._expressApp =
|
|
20
|
+
this._expressApp = bunExpress({});
|
|
18
21
|
}
|
|
19
22
|
return this._expressApp;
|
|
20
23
|
}
|
|
@@ -30,7 +33,7 @@ var BunWebSockets = class extends Transport {
|
|
|
30
33
|
const url = new URL(req.url);
|
|
31
34
|
if (server.upgrade(req, {
|
|
32
35
|
data: {
|
|
33
|
-
url: url.pathname
|
|
36
|
+
url: url.pathname,
|
|
34
37
|
searchParams: url.searchParams,
|
|
35
38
|
headers: req.headers,
|
|
36
39
|
remoteAddress: server.requestIP(req)?.address || "unknown"
|
|
@@ -38,43 +41,41 @@ var BunWebSockets = class extends Transport {
|
|
|
38
41
|
})) {
|
|
39
42
|
return;
|
|
40
43
|
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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());
|
|
52
60
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return new Response(JSON.stringify({
|
|
68
|
-
code: e.code,
|
|
69
|
-
error: e.message
|
|
70
|
-
}), {
|
|
71
|
-
status: 500,
|
|
72
|
-
headers: { "Content-Type": "application/json" }
|
|
73
|
-
});
|
|
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();
|
|
74
75
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
ereq.complete = true;
|
|
77
|
+
self._expressApp["handle"](ereq, eres);
|
|
78
|
+
return await eres.getBunResponse();
|
|
78
79
|
}
|
|
79
80
|
return new Response("Not Found", { status: 404 });
|
|
80
81
|
},
|
|
@@ -84,7 +85,7 @@ var BunWebSockets = class extends Transport {
|
|
|
84
85
|
await self.onConnection(ws);
|
|
85
86
|
},
|
|
86
87
|
message(ws, message) {
|
|
87
|
-
self.clientWrappers.get(ws)?.emit("message", message);
|
|
88
|
+
self.clientWrappers.get(ws)?.emit("message", Buffer.from(message));
|
|
88
89
|
},
|
|
89
90
|
close(ws, code, reason) {
|
|
90
91
|
spliceOne(self.clients, self.clients.indexOf(ws));
|
|
@@ -136,9 +137,9 @@ var BunWebSockets = class extends Transport {
|
|
|
136
137
|
const skipHandshake = searchParams.has("skipHandshake");
|
|
137
138
|
try {
|
|
138
139
|
await connectClientToRoom(room, client, {
|
|
139
|
-
token: searchParams.get("_authToken") ?? getBearerToken(rawClient.data.headers
|
|
140
|
+
token: searchParams.get("_authToken") ?? getBearerToken(rawClient.data.headers.get("authorization")),
|
|
140
141
|
headers: rawClient.data.headers,
|
|
141
|
-
ip: rawClient.data.headers
|
|
142
|
+
ip: rawClient.data.headers.get("x-real-ip") ?? rawClient.data.headers.get("x-forwarded-for") ?? rawClient.data.remoteAddress
|
|
142
143
|
}, {
|
|
143
144
|
reconnectionToken,
|
|
144
145
|
skipHandshake
|
|
@@ -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';\
|
|
5
|
-
"mappings": ";AAIA,OAA0D;
|
|
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": ";AAIA,OAA0D;AAE1D,SAAS,YAAY,UAAU,WAAW,oBAAoB,gBAAgB,WAAW,qBAAqB,iBAA8B;AAC5I,SAAS,iBAAiB,wBAAwB;AAGlD,OAAO,cAAc,iBAAiB,sBAAsB;AAcrD,IAAM,gBAAN,cAA4B,UAAU;AAAA,EAU3C,YAAY,UAA4B,CAAC,GAAG;AAC1C,UAAM;AAVR,SAAU,UAA4C,CAAC;AACvD,SAAU,iBAAiB,oBAAI,QAA0D;AAKzF,SAAQ,mBAAgE;AACxE,SAAQ,UAA4B,CAAC;AAKnC,QAAI,QAAQ,qBAAqB,QAAW;AAC1C,cAAQ,mBAAmB,IAAI;AAAA,IACjC;AAEA,SAAK,UAAU;AAAA,EACjB;AAAA,EAEO,gBAA6B;AAClC,QAAI,CAAC,KAAK,aAAa;AAErB,WAAK,cAAc,WAAW,CAAC,CAAC;AAAA,IAClC;AACA,WAAO,KAAK;AAAA,EACd;AAAA,EAEO,WAAW,QAAgB;AAChC,SAAK,UAAU;AAAA,EACjB;AAAA,EAEO,OAAO,MAAc,UAAmB,SAAkB,mBAAgC;AAC/F,UAAM,OAAO;AAEb,SAAK,UAAU,IAAI,MAAM;AAAA,MACvB;AAAA,MACA;AAAA,MAEA,MAAM,MAAM,KAAK,QAAQ;AACvB,cAAM,MAAM,IAAI,IAAI,IAAI,GAAG;AAG3B,YAAI,OAAO,QAAQ,KAAK;AAAA,UACtB,MAAM;AAAA,YACJ,KAAK,IAAI;AAAA,YACT,cAAc,IAAI;AAAA,YAClB,SAAS,IAAI;AAAA,YACb,eAAe,OAAO,UAAU,GAAG,GAAG,WAAW;AAAA,UACnD;AAAA,QACF,CAAC,GAAG;AACF;AAAA,QACF;AAIA,cAAM,cAAc;AAAA,UAClB,GAAG,WAAW,WAAW;AAAA,UACzB,GAAG,WAAW,WAAW,eAAe,IAAI,OAAO;AAAA,QACrD;AAGA,YAAI,IAAI,WAAW,WAAW;AAC5B,iBAAO,IAAI,SAAS,MAAM;AAAA,YACxB,QAAQ;AAAA,YACR,SAAS;AAAA,UACX,CAAC;AAAA,QACH;AAEA,YAAI,KAAK,SAAS,UAAU,IAAI,QAAQ,IAAI,QAAQ,MAAM,QAAW;AACnE,gBAAM,WAAW,MAAM,KAAK,QAAQ,QAAQ,GAAG;AAG/C,gBAAM,UAAU,IAAI,QAAQ,SAAS,OAAO;AAC5C,iBAAO,QAAQ,WAAW,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AACpD,gBAAI,CAAC,QAAQ,IAAI,GAAG,GAAG;AACrB,sBAAQ,IAAI,KAAK,MAAM,SAAS,CAAC;AAAA,YACnC;AAAA,UACF,CAAC;AAED,iBAAO,IAAI,SAAS,SAAS,MAAM;AAAA,YACjC,QAAQ,SAAS;AAAA,YACjB,YAAY,SAAS;AAAA,YACrB;AAAA,UACF,CAAC;AAAA,QAEH,WAAW,KAAK,aAAa;AAE3B,gBAAM,OAAO,IAAI,gBAAgB,KAAK,KAAK,KAAK,WAAW;AAC3D,gBAAM,OAAO,IAAI,eAAe,MAAM,KAAK,WAAW;AAGtD,iBAAO,QAAQ,WAAW,EAAE,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AACpD,iBAAK,UAAU,KAAK,MAAM,SAAS,CAAC;AAAA,UACtC,CAAC;AAGD,cAAI,IAAI,WAAW,SAAS,IAAI,WAAW,QAAQ;AACjD,kBAAO,KAAa,SAAS;AAAA,UAC/B;AAEA,UAAC,KAAa,WAAW;AAEzB,eAAK,YAAY,QAAQ,EAAE,MAAM,IAAI;AAErC,iBAAO,MAAM,KAAK,eAAe;AAAA,QACnC;AAEA,eAAO,IAAI,SAAS,aAAa,EAAE,QAAQ,IAAI,CAAC;AAAA,MAClD;AAAA,MAEA,WAAW;AAAA,QACT,GAAG,KAAK;AAAA,QAER,MAAM,KAAK,IAAI;AACb,gBAAM,KAAK,aAAa,EAAE;AAAA,QAC5B;AAAA,QAEA,QAAQ,IAAI,SAAS;AACnB,eAAK,eAAe,IAAI,EAAE,GAAG,KAAK,WAAW,OAAO,KAAK,OAAO,CAAC;AAAA,QACnE;AAAA,QAEA,MAAM,IAAI,MAAM,QAAQ;AAEtB,oBAAU,KAAK,SAAS,KAAK,QAAQ,QAAQ,EAAE,CAAC;AAEhD,gBAAM,gBAAgB,KAAK,eAAe,IAAI,EAAE;AAChD,cAAI,eAAe;AACjB,iBAAK,eAAe,OAAO,EAAE;AAG7B,0BAAc,KAAK,SAAS,IAAI;AAAA,UAClC;AAAA,QACF;AAAA,MACF;AAAA,IACF,CAAC;AAED,wBAAoB;AAEpB,WAAO;AAAA,EACT;AAAA,EAEO,WAAW;AAChB,QAAI,KAAK,SAAS;AAChB,WAAK,QAAQ,KAAK;AAAA,IACpB;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,MAAM,UAAU,KAAK;AAC3B,UAAM,eAAe,UAAU,KAAK;AAEpC,UAAM,YAAY,aAAa,IAAI,WAAW;AAC9C,UAAM,mBAAmB,IAAI,MAAM,uCAAuC;AAC1E,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,aAAa,IAAI,mBAAmB;AAC9D,UAAM,gBAAgB,aAAa,IAAI,eAAe;AAEtD,QAAI;AACF,YAAM,oBAAoB,MAAM,QAAQ;AAAA,QACtC,OAAO,aAAa,IAAI,YAAY,KAAK,eAAe,UAAU,KAAK,QAAQ,IAAI,eAAe,CAAC;AAAA,QACnG,SAAS,UAAU,KAAK;AAAA,QACxB,IAAI,UAAU,KAAK,QAAQ,IAAI,WAAW,KAAK,UAAU,KAAK,QAAQ,IAAI,iBAAiB,KAAK,UAAU,KAAK;AAAA,MACjH,GAAG;AAAA,QACD;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IAEH,SAAS,GAAQ;AACf,yBAAmB,CAAC;AAGpB,aAAO,MAAM,EAAE,MAAM,EAAE,SAAS,MAC9B,UAAU,MAAM,oBACZ,UAAU,sBACV,UAAU,UAAU,CAAC;AAAA,IAC7B;AAAA,EACF;AAEF;",
|
|
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():
|
|
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:
|
|
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.
|
|
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
|
-
"
|
|
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.
|
|
29
|
+
"@colyseus/core": "^0.17.37",
|
|
30
|
+
"@colyseus/sdk": "^0.17.34"
|
|
29
31
|
},
|
|
30
32
|
"author": "Endel Dreyer",
|
|
31
33
|
"license": "MIT",
|
package/src/BunWebSockets.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
|
216
|
+
token: searchParams.get("_authToken") ?? getBearerToken(rawClient.data.headers.get('authorization')),
|
|
207
217
|
headers: rawClient.data.headers,
|
|
208
|
-
ip: rawClient.data.headers
|
|
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
|