@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.
- package/build/BunWebSockets.cjs +53 -44
- package/build/BunWebSockets.cjs.map +3 -3
- package/build/BunWebSockets.d.ts +2 -3
- package/build/BunWebSockets.mjs +43 -44
- package/build/BunWebSockets.mjs.map +2 -2
- package/build/WebSocketClient.d.ts +2 -2
- package/package.json +5 -3
- package/src/BunWebSockets.ts +67 -54
package/build/BunWebSockets.cjs
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
|
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
|
|
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';\
|
|
5
|
-
"mappings": "
|
|
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
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
79
|
-
|
|
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
|
|
140
|
+
token: searchParams.get("_authToken") ?? getBearerToken(rawClient.data.headers.get("authorization")),
|
|
142
141
|
headers: rawClient.data.headers,
|
|
143
|
-
ip: rawClient.data.headers
|
|
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';\
|
|
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": "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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():
|
|
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
|
|
@@ -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, () =>
|
|
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
|
|