@colyseus/uwebsockets-transport 0.17.13 → 0.17.15
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/LICENSE +1 -3
- package/README.md +17 -16
- package/build/uWebSocketsTransport.cjs +60 -167
- package/build/uWebSocketsTransport.cjs.map +2 -2
- package/build/uWebSocketsTransport.d.ts +0 -1
- package/build/uWebSocketsTransport.mjs +60 -167
- package/build/uWebSocketsTransport.mjs.map +2 -2
- package/package.json +2 -2
- package/src/uWebSocketsTransport.ts +84 -209
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -7,19 +7,19 @@
|
|
|
7
7
|
<a href="https://npmjs.com/package/colyseus">
|
|
8
8
|
<img src="https://img.shields.io/npm/dm/colyseus.svg?style=for-the-badge&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfjAgETESWYxR33AAAAtElEQVQoz4WQMQrCQBRE38Z0QoTcwF4Qg1h4BO0sxGOk80iCtViksrIQRRBTewWxMI1mbELYjYu+4rPMDPtn12ChMT3gavb4US5Jym0tcBIta3oDHv4Gwmr7nC4QAxBrCdzM2q6XqUnm9m9r59h7Rc0n2pFv24k4ttGMUXW+sGELTJjSr7QDKuqLS6UKFChVWWuFkZw9Z2AAvAirKT+JTlppIRnd6XgaP4goefI2Shj++OnjB3tBmHYK8z9zAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE5LTAyLTAxVDE4OjE3OjM3KzAxOjAwGQQixQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxOS0wMi0wMVQxODoxNzozNyswMTowMGhZmnkAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAAAAElFTkSuQmCC">
|
|
9
9
|
</a>
|
|
10
|
-
<a href="https://github.com/colyseus/colyseus/discussions" title="Discuss on Forum">
|
|
11
|
-
<img src="https://img.shields.io/badge/discuss-on%20forum-brightgreen.svg?style=for-the-badge&colorB=0069b8&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfjAgETDROxCNUzAAABB0lEQVQoz4WRvyvEARjGP193CnWRH+dHQmGwKZtFGcSmxHAL400GN95ktIpV2dzlLzDJgsGgGNRdDAzoQueS/PgY3HXHyT3T+/Y87/s89UANBKXBdoZo5J6L4K1K5ZxHfnjnlQUf3bKvkgy57a0r9hS3cXfMO1kWJMza++tj3Ac7/LY343x1NA9cNmYMwnSS/SP8JVFuSJmr44iFqvtmpjhmhBCrOOazCesq6H4P3bPBjFoIBydOk2bUA17I080Es+wSZ51B4DIA2zgjSpYcEe44Js01G0XjRcCU+y4ZMrDeLmfc9EnVd5M/o0VMeu6nJZxWJivLmhyw1WHTvrr2b4+2OFqra+ALwouTMDcqmjMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDItMDFUMTg6MTM6MTkrMDE6MDAC9f6fAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTAyLTAxVDE4OjEzOjE5KzAxOjAwc6hGIwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAASUVORK5CYII=" alt="Discussion forum" />
|
|
12
|
-
</a>
|
|
13
10
|
<a href="http://chat.colyseus.io">
|
|
14
11
|
<img src="https://img.shields.io/discord/525739117951320081.svg?style=for-the-badge&colorB=7581dc&logo=discord&logoColor=white">
|
|
15
12
|
</a>
|
|
13
|
+
<a href="https://github.com/colyseus/colyseus/discussions" title="Discuss Forum">
|
|
14
|
+
<img src="https://img.shields.io/badge/discuss-forum-brightgreen.svg?style=for-the-badge&colorB=0069b8&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAAmJLR0QAAKqNIzIAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAAHdElNRQfjAgETDROxCNUzAAABB0lEQVQoz4WRvyvEARjGP193CnWRH+dHQmGwKZtFGcSmxHAL400GN95ktIpV2dzlLzDJgsGgGNRdDAzoQueS/PgY3HXHyT3T+/Y87/s89UANBKXBdoZo5J6L4K1K5ZxHfnjnlQUf3bKvkgy57a0r9hS3cXfMO1kWJMza++tj3Ac7/LY343x1NA9cNmYMwnSS/SP8JVFuSJmr44iFqvtmpjhmhBCrOOazCesq6H4P3bPBjFoIBydOk2bUA17I080Es+wSZ51B4DIA2zgjSpYcEe44Js01G0XjRcCU+y4ZMrDeLmfc9EnVd5M/o0VMeu6nJZxWJivLmhyw1WHTvrr2b4+2OFqra+ALwouTMDcqmjMAAAAldEVYdGRhdGU6Y3JlYXRlADIwMTktMDItMDFUMTg6MTM6MTkrMDE6MDAC9f6fAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE5LTAyLTAxVDE4OjEzOjE5KzAxOjAwc6hGIwAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAAASUVORK5CYII=" alt="Discussion forum" />
|
|
15
|
+
</a>
|
|
16
16
|
<h3>
|
|
17
17
|
Multiplayer Framework for Node.js. <br /><a href="https://docs.colyseus.io/">View documentation</a>
|
|
18
18
|
</h3>
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
|
-
Colyseus is an Authoritative Multiplayer Framework for Node.js, with
|
|
22
|
-
available for the Web,
|
|
21
|
+
Colyseus is an Authoritative Multiplayer Framework for Node.js, with SDKs
|
|
22
|
+
available for the Web, Unity, Defold, Haxe, Cocos and Construct3. ([See official SDKs](https://docs.colyseus.io/getting-started))
|
|
23
23
|
|
|
24
24
|
The project focuses on providing synchronizable data structures for realtime and
|
|
25
25
|
turn-based games, matchmaking, and ease of usage both on the server-side and
|
|
@@ -28,23 +28,24 @@ client-side.
|
|
|
28
28
|
The mission of the framework is to be a standard netcode & matchmaking solution
|
|
29
29
|
for any kind of project you can think of!
|
|
30
30
|
|
|
31
|
-
##
|
|
31
|
+
## Why developers choose Colyseus:
|
|
32
32
|
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
-
|
|
33
|
+
- ⚡️ **Real-time state sync that just works** → Define your state on the server and it automatically synchronizes to all clients, delta-compressed and binary-encoded.
|
|
34
|
+
- ⚔️ **Built-in matchmaking** → Room-based architecture with filtering, queuing, and reconnection support out of the box.
|
|
35
|
+
- 📈 **Scalable** → Go from 10 to 10,000+ CCU by scaling vertically or horizontally with Redis and load balancers.
|
|
36
|
+
- 🛡️ **Cheat-proof by design** → Authoritative server model ensures game logic runs on the server, not the client.
|
|
37
|
+
- 🛠️ **Use the tools you already know** → Built on Node.js and TypeScript with a simple, familiar API on both server and client.
|
|
38
|
+
- 💙 **Free forever** → MIT licensed, even for commercial games.
|
|
38
39
|
|
|
39
|
-
See [public roadmap](https://
|
|
40
|
+
See [public roadmap](https://docs.colyseus.io/roadmap) for version 1.0.
|
|
40
41
|
|
|
41
42
|
# 🚀 Quickstart
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
Set up your own Colyseus server project for your game using `npm create colyseus-app@latest`:
|
|
44
45
|
|
|
45
46
|
```
|
|
46
|
-
npm create colyseus-app@latest my-
|
|
47
|
-
cd my-
|
|
47
|
+
npm create colyseus-app@latest ./my-server
|
|
48
|
+
cd my-server
|
|
48
49
|
npm start
|
|
49
50
|
```
|
|
50
51
|
|
|
@@ -87,7 +88,7 @@ Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds
|
|
|
87
88
|
<td align="center"><a href="https://github.com/TinyDobbins"><img src="https://avatars2.githubusercontent.com/u/20824844?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Nikita Borisov</b></sub></a><br /><a href="https://github.com/colyseus/colyseus/issues?q=author%3ATinyDobbins" title="Bug reports">🐛</a> <a href="https://github.com/colyseus/colyseus/commits?author=TinyDobbins" title="Code">💻</a> <a href="#business-TinyDobbins" title="Business development">💼</a> <a href="#ideas-TinyDobbins" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
88
89
|
<td align="center"><a href="https://acemobe.com/"><img src="https://avatars2.githubusercontent.com/u/232101?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Phil Harvey</b></sub></a><br /><a href="https://github.com/colyseus/colyseus/commits?author=filharvey" title="Documentation">📖</a></td>
|
|
89
90
|
<td align="center"><a href="https://github.com/serjek"><img src="https://avatars2.githubusercontent.com/u/18265157?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sergey</b></sub></a><br /><a href="https://github.com/colyseus/colyseus/issues?q=author%3Aserjek" title="Bug reports">🐛</a> <a href="https://github.com/colyseus/colyseus/commits?author=serjek" title="Code">💻</a></td>
|
|
90
|
-
<td align="center"><a href="https://
|
|
91
|
+
<td align="center"><a href="https://devlsh.com"><img src="https://avatars0.githubusercontent.com/u/853683?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sophie</b></sub></a><br /><a href="#question-devlsh" title="Answering Questions">💬</a> <a href="https://github.com/colyseus/colyseus/issues?q=author%3Adevlsh" title="Bug reports">🐛</a> <a href="#ideas-devlsh" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
91
92
|
<td align="center"><a href="https://github.com/supertommy"><img src="https://avatars0.githubusercontent.com/u/2236153?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Tommy Leung</b></sub></a><br /><a href="#mentoring-supertommy" title="Mentoring">🧑🏫</a></td>
|
|
92
93
|
<td align="center"><a href="https://github.com/digimbyte"><img src="https://avatars2.githubusercontent.com/u/6645396?v=4?s=100" width="100px;" alt=""/><br /><sub><b>digimbyte</b></sub></a><br /><a href="https://github.com/colyseus/colyseus/commits?author=digimbyte" title="Documentation">📖</a></td>
|
|
93
94
|
</tr>
|
|
@@ -107,9 +107,13 @@ var uWebSocketsTransport = class extends import_core.Transport {
|
|
|
107
107
|
try {
|
|
108
108
|
const module2 = await uWebSocketsExpress;
|
|
109
109
|
uWebSocketsExpressModule = module2;
|
|
110
|
+
const originalAny = this.app.any;
|
|
111
|
+
this.app.any = (() => this.app);
|
|
110
112
|
this._expressApp = module2.default(this.app);
|
|
113
|
+
this.app.any = originalAny;
|
|
111
114
|
resolve(this._expressApp);
|
|
112
115
|
} catch (error) {
|
|
116
|
+
reject(error);
|
|
113
117
|
console.warn("");
|
|
114
118
|
console.warn("\u274C Error: could not initialize express.");
|
|
115
119
|
console.warn("");
|
|
@@ -126,15 +130,18 @@ var uWebSocketsTransport = class extends import_core.Transport {
|
|
|
126
130
|
return this._expressApp;
|
|
127
131
|
}
|
|
128
132
|
bindRouter(router) {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
const headers = Object.assign(
|
|
133
|
+
const getCorsHeaders = (requestHeaders) => {
|
|
134
|
+
return Object.assign(
|
|
134
135
|
{},
|
|
135
136
|
import_core.matchMaker.controller.DEFAULT_CORS_HEADERS,
|
|
136
137
|
import_core.matchMaker.controller.getCorsHeaders(requestHeaders)
|
|
137
138
|
);
|
|
139
|
+
};
|
|
140
|
+
const writeCorsHeaders = (res, requestHeaders) => {
|
|
141
|
+
if (res.aborted) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
const headers = getCorsHeaders(requestHeaders);
|
|
138
145
|
for (const header in headers) {
|
|
139
146
|
res.writeHeader(header, headers[header].toString());
|
|
140
147
|
}
|
|
@@ -144,10 +151,11 @@ var uWebSocketsTransport = class extends import_core.Transport {
|
|
|
144
151
|
res.onAborted(() => res.aborted = true);
|
|
145
152
|
const reqHeaders = new Headers();
|
|
146
153
|
req.forEach((key, value) => reqHeaders.set(key, value));
|
|
147
|
-
|
|
154
|
+
res.cork(() => {
|
|
148
155
|
res.writeStatus("204 No Content");
|
|
156
|
+
writeCorsHeaders(res, reqHeaders);
|
|
149
157
|
res.end();
|
|
150
|
-
}
|
|
158
|
+
});
|
|
151
159
|
});
|
|
152
160
|
this.app.any("/*", async (res, req) => {
|
|
153
161
|
const abortController = new AbortController();
|
|
@@ -157,59 +165,65 @@ var uWebSocketsTransport = class extends import_core.Transport {
|
|
|
157
165
|
});
|
|
158
166
|
const headers = new Headers();
|
|
159
167
|
req.forEach((key, value) => headers.set(key, value));
|
|
160
|
-
|
|
161
|
-
const requestInit = {
|
|
162
|
-
method: req.getMethod().toUpperCase(),
|
|
163
|
-
referrer: req.getHeader("referer"),
|
|
164
|
-
keepalive: req.getHeader("keep-alive") === "true",
|
|
165
|
-
headers,
|
|
166
|
-
signal: abortController.signal
|
|
167
|
-
};
|
|
168
|
+
const method = req.getMethod().toUpperCase();
|
|
168
169
|
const url = req.getUrl();
|
|
169
170
|
const query = req.getQuery();
|
|
170
171
|
const remoteAddress = res.getRemoteAddressAsText();
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
172
|
+
if (router.findRoute(method, url) !== void 0) {
|
|
173
|
+
const requestInit = {
|
|
174
|
+
method,
|
|
175
|
+
referrer: headers.get("referer") || void 0,
|
|
176
|
+
keepalive: headers.get("keep-alive") === "true",
|
|
177
|
+
headers,
|
|
178
|
+
signal: abortController.signal
|
|
179
|
+
};
|
|
180
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
181
|
+
let body = void 0;
|
|
182
|
+
await new Promise((resolve) => {
|
|
183
|
+
res.onData((ab, isLast) => {
|
|
184
|
+
const chunk = Buffer.from(ab);
|
|
185
|
+
if (body === void 0) {
|
|
186
|
+
body = Buffer.from(chunk);
|
|
187
|
+
} else {
|
|
188
|
+
body = Buffer.concat([body, chunk]);
|
|
189
|
+
}
|
|
190
|
+
if (isLast) {
|
|
191
|
+
resolve();
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
requestInit.body = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength);
|
|
196
|
+
}
|
|
197
|
+
const fullUrl = `http://${headers.get("host") || "localhost"}${url}${query ? `?${query}` : ""}`;
|
|
198
|
+
const response = await router.handler(new Request(fullUrl, requestInit));
|
|
199
|
+
if (res.aborted) {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const responseBody = await response.arrayBuffer();
|
|
203
|
+
res.cork(() => {
|
|
204
|
+
res.writeStatus(`${response.status} ${response.statusText}`);
|
|
205
|
+
writeCorsHeaders(res, headers);
|
|
206
|
+
response.headers.forEach((value, key) => {
|
|
207
|
+
res.writeHeader(key, value);
|
|
184
208
|
});
|
|
209
|
+
res.end(responseBody);
|
|
185
210
|
});
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
const fullUrl = `http://${headers.get("host") || "localhost"}${url}${query ? `?${query}` : ""}`;
|
|
189
|
-
const response = await router.handler(new Request(fullUrl, requestInit));
|
|
190
|
-
if (response.status === 404 && this._expressApp) {
|
|
211
|
+
} else if (this._expressApp) {
|
|
212
|
+
const corsHeaders = getCorsHeaders(headers);
|
|
191
213
|
const ereq = new uWebSocketsExpressModule.IncomingMessage(req, res, this._expressApp, {
|
|
192
214
|
headers: Object.fromEntries(headers.entries()),
|
|
193
|
-
method
|
|
215
|
+
method,
|
|
194
216
|
url,
|
|
195
217
|
query,
|
|
196
218
|
remoteAddress
|
|
197
219
|
});
|
|
198
220
|
const eres = new uWebSocketsExpressModule.ServerResponse(res, req, this._expressApp);
|
|
221
|
+
for (const header in corsHeaders) {
|
|
222
|
+
eres.setHeader(header, corsHeaders[header].toString());
|
|
223
|
+
}
|
|
224
|
+
await ereq._readBody();
|
|
199
225
|
this._expressApp["handle"](ereq, eres);
|
|
200
|
-
return;
|
|
201
226
|
}
|
|
202
|
-
if (res.aborted) {
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
const responseBody = await response.arrayBuffer();
|
|
206
|
-
res.cork(() => {
|
|
207
|
-
res.writeStatus(`${response.status} ${response.statusText}`);
|
|
208
|
-
response.headers.forEach((value, key) => {
|
|
209
|
-
res.writeHeader(key, value);
|
|
210
|
-
});
|
|
211
|
-
res.end(responseBody);
|
|
212
|
-
});
|
|
213
227
|
});
|
|
214
228
|
}
|
|
215
229
|
listen(port, hostname, backlog, listeningListener) {
|
|
@@ -269,127 +283,6 @@ var uWebSocketsTransport = class extends import_core.Transport {
|
|
|
269
283
|
client.error(e.code, e.message, () => rawClient.end(reconnectionToken ? import_core.CloseCode.FAILED_TO_RECONNECT : import_core.CloseCode.WITH_ERROR));
|
|
270
284
|
}
|
|
271
285
|
}
|
|
272
|
-
// protected registerMatchMakeRequest() {
|
|
273
|
-
// const matchmakeRoute = 'matchmake';
|
|
274
|
-
// const allowedRoomNameChars = /([a-zA-Z_\-0-9]+)/gi;
|
|
275
|
-
// const writeHeaders = (res: uWebSockets.HttpResponse, requestHeaders: Headers) => {
|
|
276
|
-
// // skip if aborted
|
|
277
|
-
// if (res.aborted) { return; }
|
|
278
|
-
// const headers = Object.assign(
|
|
279
|
-
// {},
|
|
280
|
-
// matchMaker.controller.DEFAULT_CORS_HEADERS,
|
|
281
|
-
// matchMaker.controller.getCorsHeaders(requestHeaders)
|
|
282
|
-
// );
|
|
283
|
-
// for (const header in headers) {
|
|
284
|
-
// res.writeHeader(header, headers[header].toString());
|
|
285
|
-
// }
|
|
286
|
-
// return true;
|
|
287
|
-
// }
|
|
288
|
-
// const writeError = (res: uWebSockets.HttpResponse, error: { code: number, error: string }) => {
|
|
289
|
-
// // skip if aborted
|
|
290
|
-
// if (res.aborted) { return; }
|
|
291
|
-
// res.cork(() => {
|
|
292
|
-
// res.writeStatus("406 Not Acceptable");
|
|
293
|
-
// res.end(JSON.stringify(error));
|
|
294
|
-
// });
|
|
295
|
-
// }
|
|
296
|
-
// const onAborted = (res: uWebSockets.HttpResponse) => {
|
|
297
|
-
// res.aborted = true;
|
|
298
|
-
// };
|
|
299
|
-
// this.app.options("/matchmake/*", (res, req) => {
|
|
300
|
-
// res.onAborted(() => onAborted(res));
|
|
301
|
-
// // cache all headers
|
|
302
|
-
// const reqHeaders = new Headers();
|
|
303
|
-
// req.forEach((key, value) => reqHeaders.set(key, value));
|
|
304
|
-
// if (writeHeaders(res, reqHeaders)) {
|
|
305
|
-
// res.writeStatus("204 No Content");
|
|
306
|
-
// res.end();
|
|
307
|
-
// }
|
|
308
|
-
// });
|
|
309
|
-
// // @ts-ignore
|
|
310
|
-
// this.app.post("/matchmake/*", (res, req) => {
|
|
311
|
-
// res.onAborted(() => onAborted(res));
|
|
312
|
-
// // do not accept matchmaking requests if already shutting down
|
|
313
|
-
// if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {
|
|
314
|
-
// return res.close();
|
|
315
|
-
// }
|
|
316
|
-
// // cache all headers
|
|
317
|
-
// const headers = new Headers();
|
|
318
|
-
// req.forEach((key, value) => headers.set(key, value));
|
|
319
|
-
// writeHeaders(res, headers);
|
|
320
|
-
// res.writeHeader('Content-Type', 'application/json');
|
|
321
|
-
// const url = req.getUrl();
|
|
322
|
-
// const matchedParams = url.match(allowedRoomNameChars);
|
|
323
|
-
// const matchmakeIndex = matchedParams.indexOf(matchmakeRoute);
|
|
324
|
-
// const token = getBearerToken(headers['authorization']);
|
|
325
|
-
// // read json body
|
|
326
|
-
// this.readJson(res, async (clientOptions) => {
|
|
327
|
-
// try {
|
|
328
|
-
// if (clientOptions === undefined) {
|
|
329
|
-
// throw new Error("invalid JSON input");
|
|
330
|
-
// }
|
|
331
|
-
// const method = matchedParams[matchmakeIndex + 1];
|
|
332
|
-
// const roomName = matchedParams[matchmakeIndex + 2] || '';
|
|
333
|
-
// const response = await matchMaker.controller.invokeMethod(
|
|
334
|
-
// method,
|
|
335
|
-
// roomName,
|
|
336
|
-
// clientOptions,
|
|
337
|
-
// {
|
|
338
|
-
// token,
|
|
339
|
-
// headers,
|
|
340
|
-
// ip: headers.get('x-real-ip') ?? headers.get('x-forwarded-for') ?? Buffer.from(res.getRemoteAddressAsText()).toString()
|
|
341
|
-
// }
|
|
342
|
-
// );
|
|
343
|
-
// if (!res.aborted) {
|
|
344
|
-
// res.cork(() => {
|
|
345
|
-
// res.writeStatus("200 OK");
|
|
346
|
-
// res.end(JSON.stringify(response));
|
|
347
|
-
// });
|
|
348
|
-
// }
|
|
349
|
-
// } catch (e: any) {
|
|
350
|
-
// debugAndPrintError(e);
|
|
351
|
-
// writeError(res, {
|
|
352
|
-
// code: e.code || ErrorCode.MATCHMAKE_UNHANDLED,
|
|
353
|
-
// error: e.message
|
|
354
|
-
// });
|
|
355
|
-
// }
|
|
356
|
-
// });
|
|
357
|
-
// });
|
|
358
|
-
// }
|
|
359
|
-
/* Helper function for reading a posted JSON body */
|
|
360
|
-
/* Extracted from https://github.com/uNetworking/uWebSockets.js/blob/master/examples/JsonPost.js */
|
|
361
|
-
readJson(res, cb) {
|
|
362
|
-
let buffer;
|
|
363
|
-
res.onData((ab, isLast) => {
|
|
364
|
-
let chunk = Buffer.from(ab);
|
|
365
|
-
if (isLast) {
|
|
366
|
-
let json;
|
|
367
|
-
if (buffer) {
|
|
368
|
-
try {
|
|
369
|
-
json = JSON.parse(Buffer.concat([buffer, chunk]));
|
|
370
|
-
} catch (e) {
|
|
371
|
-
cb(void 0);
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
cb(json);
|
|
375
|
-
} else {
|
|
376
|
-
try {
|
|
377
|
-
json = JSON.parse(chunk);
|
|
378
|
-
} catch (e) {
|
|
379
|
-
cb(void 0);
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
cb(json);
|
|
383
|
-
}
|
|
384
|
-
} else {
|
|
385
|
-
if (buffer) {
|
|
386
|
-
buffer = Buffer.concat([buffer, chunk]);
|
|
387
|
-
} else {
|
|
388
|
-
buffer = Buffer.concat([chunk]);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
286
|
};
|
|
394
287
|
// Annotate the CommonJS export names for ESM import in node:
|
|
395
288
|
0 && (module.exports = {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/uWebSocketsTransport.ts"],
|
|
4
|
-
"sourcesContent": ["import querystring, { type ParsedUrlQuery } from 'querystring';\nimport uWebSockets, { type WebSocket } from 'uWebSockets.js';\nimport type express from 'express';\n\nimport { type AuthContext, Transport, matchMaker, Protocol, getBearerToken, debugAndPrintError, spliceOne, connectClientToRoom, CloseCode, type Router } from '@colyseus/core';\nimport { uWebSocketClient, uWebSocketWrapper } from './uWebSocketClient.ts';\nimport { Deferred } from '@colyseus/core';\n\nconst uWebSocketsExpress = new Deferred<typeof import('uwebsockets-express')>;\nlet uWebSocketsExpressModule: typeof import('uwebsockets-express') | undefined = undefined;\nimport('uwebsockets-express')\n .then((module) => uWebSocketsExpress.resolve(module))\n .catch((error) => uWebSocketsExpress.reject(error));\n\nexport type TransportOptions = Omit<uWebSockets.WebSocketBehavior<any>, \"upgrade\" | \"open\" | \"pong\" | \"close\" | \"message\">;\n\ntype RawWebSocketClient = uWebSockets.WebSocket<any> & {\n url: string,\n searchParams: ParsedUrlQuery,\n context: AuthContext,\n};\n\nexport class uWebSocketsTransport extends Transport {\n public app: uWebSockets.TemplatedApp;\n\n protected clients: RawWebSocketClient[] = [];\n protected clientWrappers = new WeakMap<RawWebSocketClient, uWebSocketWrapper>();\n\n private _listeningSocket: any;\n private _originalRawSend: typeof uWebSocketClient.prototype.raw | null = null;\n private _expressApp?: express.Application;\n\n constructor(options: TransportOptions = {}, appOptions: uWebSockets.AppOptions = {}) {\n super();\n\n this.app = (appOptions.cert_file_name && appOptions.key_file_name)\n ? uWebSockets.SSLApp(appOptions)\n : uWebSockets.App(appOptions);\n\n if (options.maxBackpressure === undefined) {\n options.maxBackpressure = 1024 * 1024;\n }\n\n if (options.compression === undefined) {\n options.compression = uWebSockets.DISABLED;\n }\n\n if (options.maxPayloadLength === undefined) {\n options.maxPayloadLength = 4 * 1024;\n }\n\n if (options.sendPingsAutomatically === undefined) {\n options.sendPingsAutomatically = true;\n }\n\n this.app.ws('/*', {\n ...options,\n\n upgrade: (res, req, context) => {\n // get all headers\n const headers: { [id: string]: string } = {};\n req.forEach((key, value) => headers[key] = value);\n\n const searchParams = querystring.parse(req.getQuery());\n\n /* This immediately calls open handler, you must not use res after this call */\n /* Spell these correctly */\n res.upgrade(\n {\n url: req.getUrl(),\n searchParams,\n context: {\n token: searchParams._authToken ?? getBearerToken(req.getHeader('authorization')),\n headers,\n ip: headers['x-real-ip'] ?? headers['x-forwarded-for'] ?? Buffer.from(res.getRemoteAddressAsText()).toString(),\n }\n },\n req.getHeader('sec-websocket-key'),\n req.getHeader('sec-websocket-protocol'),\n req.getHeader('sec-websocket-extensions'),\n context\n );\n },\n\n open: async (ws: WebSocket<any>) => {\n // ws.pingCount = 0;\n await this.onConnection(ws as RawWebSocketClient);\n },\n\n // pong: (ws: RawWebSocketClient) => {\n // ws.pingCount = 0;\n // },\n\n close: (ws: WebSocket<any>, code: number, message: ArrayBuffer) => {\n // remove from client list\n spliceOne(this.clients, this.clients.indexOf(ws as RawWebSocketClient));\n\n const clientWrapper = this.clientWrappers.get(ws as RawWebSocketClient);\n if (clientWrapper) {\n this.clientWrappers.delete(ws as RawWebSocketClient);\n\n // emit 'close' on wrapper\n clientWrapper.emit('close', code);\n }\n },\n\n message: (ws: WebSocket<any>, message: ArrayBuffer, isBinary: boolean) => {\n // emit 'message' on wrapper\n this.clientWrappers.get(ws as RawWebSocketClient)?.emit('message', Buffer.from(message));\n },\n\n });\n }\n\n public getExpressApp(): Promise<express.Application> | express.Application {\n if (!this._expressApp) {\n return new Promise(async (resolve, reject) => {\n try {\n const module = await uWebSocketsExpress;\n uWebSocketsExpressModule = module;\n this._expressApp = (module.default(this.app) as unknown) as express.Application;\n resolve(this._expressApp);\n } catch (error) {\n console.warn(\"\");\n console.warn(\"\u274C Error: could not initialize express.\");\n console.warn(\"\");\n console.warn(\" For Express v5, use:\");\n console.warn(\" \uD83D\uDC49 npm install --save uwebsockets-express@^2.0.1\");\n console.warn(\"\");\n console.warn(\" For Express v4, use:\");\n console.warn(\" \uD83D\uDC49 npm install --save uwebsockets-express@^1.4.1\");\n console.warn(\"\");\n process.exit();\n }\n });\n }\n return this._expressApp;\n }\n\n public bindRouter(router: Router) {\n const writeHeaders = (res: uWebSockets.HttpResponse, requestHeaders: Headers) => {\n // skip if aborted\n if (res.aborted) { return; }\n\n const headers = Object.assign(\n {},\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders(requestHeaders)\n );\n\n for (const header in headers) {\n res.writeHeader(header, headers[header].toString());\n }\n\n return true;\n }\n\n this.app.options(\"/*\", (res, req) => {\n res.onAborted(() => res.aborted = true);\n\n // cache all headers\n const reqHeaders = new Headers();\n req.forEach((key, value) => reqHeaders.set(key, value));\n\n if (writeHeaders(res, reqHeaders)) {\n res.writeStatus(\"204 No Content\");\n res.end();\n }\n });\n\n this.app.any('/*', async (res, req) => {\n const abortController = new AbortController();\n\n res.onAborted(() => {\n abortController.abort();\n res.aborted = true;\n });\n\n const headers = new Headers();\n req.forEach((key, value) => headers.set(key, value));\n\n // write cors headers\n writeHeaders(res, headers);\n\n const requestInit: RequestInit = {\n method: req.getMethod().toUpperCase(),\n referrer: req.getHeader('referer'),\n keepalive: req.getHeader('keep-alive') === 'true',\n headers,\n signal: abortController.signal,\n };\n\n // Construct full URL (Request constructor requires absolute URL)\n const url = req.getUrl();\n const query = req.getQuery();\n const remoteAddress = res.getRemoteAddressAsText();\n\n // read request body\n if (requestInit.method.toUpperCase() !== \"GET\" && requestInit.method.toUpperCase() !== \"HEAD\") {\n let body: Buffer = undefined;\n\n // uWebSockets.js `HttpRequest` does not provide 'getData', must aggregate POST body via HttpResponse\n await new Promise<void>((resolve) => {\n res.onData((ab, isLast) => {\n const chunk = Buffer.from(ab);\n if (body === undefined) {\n body = Buffer.from(chunk);\n } else {\n body = Buffer.concat([body, chunk]);\n }\n if (isLast) {\n resolve();\n }\n });\n });\n\n requestInit.body = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer;\n }\n\n const fullUrl = `http://${headers.get('host') || 'localhost'}${url}${(query ? `?${query}` : '')}`\n const response = await router.handler(new Request(fullUrl, requestInit));\n\n // fallback to express stack if 404\n if (response.status === 404 && this._expressApp) {\n const ereq = new uWebSocketsExpressModule.IncomingMessage(req, res, this._expressApp as any, {\n headers: Object.fromEntries((headers as any).entries()),\n method: requestInit.method,\n url,\n query,\n remoteAddress\n });\n const eres = new uWebSocketsExpressModule.ServerResponse(res, req, this._expressApp);\n this._expressApp['handle'](ereq, eres);\n return;\n }\n\n // skip if aborted\n if (res.aborted) { return; }\n\n // read response body before cork (cork callback must be synchronous)\n const responseBody = await response.arrayBuffer();\n\n res.cork(() => {\n res.writeStatus(`${response.status} ${response.statusText}`);\n response.headers.forEach((value, key) => {\n res.writeHeader(key, value);\n });\n res.end(responseBody);\n });\n });\n }\n\n public listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void) {\n const callback = (listeningSocket: any) => {\n this._listeningSocket = listeningSocket;\n listeningListener?.();\n };\n\n if (typeof (port) === \"string\") {\n this.app.listen_unix(callback, port);\n\n } else {\n this.app.listen(port, callback);\n\n }\n return this;\n }\n\n public shutdown() {\n if (this._listeningSocket) {\n uWebSockets.us_listen_socket_close(this._listeningSocket);\n }\n }\n\n public simulateLatency(milliseconds: number) {\n if (this._originalRawSend == null) {\n this._originalRawSend = uWebSocketClient.prototype.raw;\n }\n\n const originalRawSend = this._originalRawSend;\n uWebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalRawSend : function (...args: any[]) {\n // copy buffer\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: RawWebSocketClient) {\n const wrapper = new uWebSocketWrapper(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.url;\n const searchParams = rawClient.searchParams;\n\n const sessionId = searchParams.sessionId as string;\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(), 1000);\n wrapper.on('message', (_) => rawClient.send(new Uint8Array([Protocol.PING]), true));\n wrapper.on('close', () => clearTimeout(timeout));\n return;\n }\n\n const room = matchMaker.getLocalRoomById(roomId);\n const client = new uWebSocketClient(sessionId, wrapper);\n const reconnectionToken = searchParams.reconnectionToken as string;\n const skipHandshake = (searchParams.skipHandshake !== undefined);\n\n try {\n await connectClientToRoom(room, client, rawClient.context, {\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.end(reconnectionToken\n ? CloseCode.FAILED_TO_RECONNECT\n : CloseCode.WITH_ERROR));\n }\n }\n\n // protected registerMatchMakeRequest() {\n // const matchmakeRoute = 'matchmake';\n // const allowedRoomNameChars = /([a-zA-Z_\\-0-9]+)/gi;\n\n // const writeHeaders = (res: uWebSockets.HttpResponse, requestHeaders: Headers) => {\n // // skip if aborted\n // if (res.aborted) { return; }\n\n // const headers = Object.assign(\n // {},\n // matchMaker.controller.DEFAULT_CORS_HEADERS,\n // matchMaker.controller.getCorsHeaders(requestHeaders)\n // );\n\n // for (const header in headers) {\n // res.writeHeader(header, headers[header].toString());\n // }\n\n // return true;\n // }\n\n // const writeError = (res: uWebSockets.HttpResponse, error: { code: number, error: string }) => {\n // // skip if aborted\n // if (res.aborted) { return; }\n\n // res.cork(() => {\n // res.writeStatus(\"406 Not Acceptable\");\n // res.end(JSON.stringify(error));\n // });\n // }\n\n // const onAborted = (res: uWebSockets.HttpResponse) => {\n // res.aborted = true;\n // };\n\n // this.app.options(\"/matchmake/*\", (res, req) => {\n // res.onAborted(() => onAborted(res));\n\n // // cache all headers\n // const reqHeaders = new Headers();\n // req.forEach((key, value) => reqHeaders.set(key, value));\n\n // if (writeHeaders(res, reqHeaders)) {\n // res.writeStatus(\"204 No Content\");\n // res.end();\n // }\n // });\n\n\n // // @ts-ignore\n // this.app.post(\"/matchmake/*\", (res, req) => {\n // res.onAborted(() => onAborted(res));\n\n // // do not accept matchmaking requests if already shutting down\n // if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n // return res.close();\n // }\n\n // // cache all headers\n // const headers = new Headers();\n // req.forEach((key, value) => headers.set(key, value));\n\n // writeHeaders(res, headers);\n // res.writeHeader('Content-Type', 'application/json');\n\n // const url = req.getUrl();\n // const matchedParams = url.match(allowedRoomNameChars);\n // const matchmakeIndex = matchedParams.indexOf(matchmakeRoute);\n\n // const token = getBearerToken(headers['authorization']);\n\n // // read json body\n // this.readJson(res, async (clientOptions) => {\n // try {\n // if (clientOptions === undefined) {\n // throw new Error(\"invalid JSON input\");\n // }\n\n // const method = matchedParams[matchmakeIndex + 1];\n // const roomName = matchedParams[matchmakeIndex + 2] || '';\n\n // const response = await matchMaker.controller.invokeMethod(\n // method,\n // roomName,\n // clientOptions,\n // {\n // token,\n // headers,\n // ip: headers.get('x-real-ip') ?? headers.get('x-forwarded-for') ?? Buffer.from(res.getRemoteAddressAsText()).toString()\n // }\n // );\n\n // if (!res.aborted) {\n // res.cork(() => {\n // res.writeStatus(\"200 OK\");\n // res.end(JSON.stringify(response));\n // });\n // }\n\n // } catch (e: any) {\n // debugAndPrintError(e);\n // writeError(res, {\n // code: e.code || ErrorCode.MATCHMAKE_UNHANDLED,\n // error: e.message\n // });\n // }\n\n // });\n // });\n // }\n\n /* Helper function for reading a posted JSON body */\n /* Extracted from https://github.com/uNetworking/uWebSockets.js/blob/master/examples/JsonPost.js */\n private readJson(res: uWebSockets.HttpResponse, cb: (json: any) => void) {\n let buffer: Buffer;\n /* Register data cb */\n res.onData((ab, isLast) => {\n let chunk = Buffer.from(ab);\n if (isLast) {\n let json;\n if (buffer) {\n try {\n // @ts-ignore\n json = JSON.parse(Buffer.concat([buffer, chunk]));\n } catch (e) {\n /* res.close calls onAborted */\n // res.close();\n cb(undefined);\n return;\n }\n cb(json);\n } else {\n try {\n // @ts-ignore\n json = JSON.parse(chunk);\n } catch (e) {\n /* res.close calls onAborted */\n // res.close();\n cb(undefined);\n return;\n }\n cb(json);\n }\n } else {\n if (buffer) {\n buffer = Buffer.concat([buffer, chunk]);\n } else {\n buffer = Buffer.concat([chunk]);\n }\n }\n });\n }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import querystring, { type ParsedUrlQuery } from 'querystring';\nimport uWebSockets, { type WebSocket } from 'uWebSockets.js';\nimport type express from 'express';\n\nimport { type AuthContext, Transport, matchMaker, Protocol, getBearerToken, debugAndPrintError, spliceOne, connectClientToRoom, CloseCode, type Router } from '@colyseus/core';\nimport { uWebSocketClient, uWebSocketWrapper } from './uWebSocketClient.ts';\nimport { Deferred } from '@colyseus/core';\n\nconst uWebSocketsExpress = new Deferred<typeof import('uwebsockets-express')>;\nlet uWebSocketsExpressModule: typeof import('uwebsockets-express') | undefined = undefined;\nimport('uwebsockets-express')\n .then((module) => uWebSocketsExpress.resolve(module))\n .catch((error) => uWebSocketsExpress.reject(error));\n\nexport type TransportOptions = Omit<uWebSockets.WebSocketBehavior<any>, \"upgrade\" | \"open\" | \"pong\" | \"close\" | \"message\">;\n\ntype RawWebSocketClient = uWebSockets.WebSocket<any> & {\n url: string,\n searchParams: ParsedUrlQuery,\n context: AuthContext,\n};\n\nexport class uWebSocketsTransport extends Transport {\n public app: uWebSockets.TemplatedApp;\n\n protected clients: RawWebSocketClient[] = [];\n protected clientWrappers = new WeakMap<RawWebSocketClient, uWebSocketWrapper>();\n\n private _listeningSocket: any;\n private _originalRawSend: typeof uWebSocketClient.prototype.raw | null = null;\n private _expressApp?: express.Application;\n\n constructor(options: TransportOptions = {}, appOptions: uWebSockets.AppOptions = {}) {\n super();\n\n this.app = (appOptions.cert_file_name && appOptions.key_file_name)\n ? uWebSockets.SSLApp(appOptions)\n : uWebSockets.App(appOptions);\n\n if (options.maxBackpressure === undefined) {\n options.maxBackpressure = 1024 * 1024;\n }\n\n if (options.compression === undefined) {\n options.compression = uWebSockets.DISABLED;\n }\n\n if (options.maxPayloadLength === undefined) {\n options.maxPayloadLength = 4 * 1024;\n }\n\n if (options.sendPingsAutomatically === undefined) {\n options.sendPingsAutomatically = true;\n }\n\n this.app.ws('/*', {\n ...options,\n\n upgrade: (res, req, context) => {\n // get all headers\n const headers: { [id: string]: string } = {};\n req.forEach((key, value) => headers[key] = value);\n\n const searchParams = querystring.parse(req.getQuery());\n\n /* This immediately calls open handler, you must not use res after this call */\n /* Spell these correctly */\n res.upgrade(\n {\n url: req.getUrl(),\n searchParams,\n context: {\n token: searchParams._authToken ?? getBearerToken(req.getHeader('authorization')),\n headers,\n ip: headers['x-real-ip'] ?? headers['x-forwarded-for'] ?? Buffer.from(res.getRemoteAddressAsText()).toString(),\n }\n },\n req.getHeader('sec-websocket-key'),\n req.getHeader('sec-websocket-protocol'),\n req.getHeader('sec-websocket-extensions'),\n context\n );\n },\n\n open: async (ws: WebSocket<any>) => {\n // ws.pingCount = 0;\n await this.onConnection(ws as RawWebSocketClient);\n },\n\n // pong: (ws: RawWebSocketClient) => {\n // ws.pingCount = 0;\n // },\n\n close: (ws: WebSocket<any>, code: number, message: ArrayBuffer) => {\n // remove from client list\n spliceOne(this.clients, this.clients.indexOf(ws as RawWebSocketClient));\n\n const clientWrapper = this.clientWrappers.get(ws as RawWebSocketClient);\n if (clientWrapper) {\n this.clientWrappers.delete(ws as RawWebSocketClient);\n\n // emit 'close' on wrapper\n clientWrapper.emit('close', code);\n }\n },\n\n message: (ws: WebSocket<any>, message: ArrayBuffer, isBinary: boolean) => {\n // emit 'message' on wrapper\n this.clientWrappers.get(ws as RawWebSocketClient)?.emit('message', Buffer.from(message));\n },\n\n });\n }\n\n public getExpressApp(): Promise<express.Application> | express.Application {\n if (!this._expressApp) {\n return new Promise(async (resolve, reject) => {\n try {\n const module = await uWebSocketsExpress;\n uWebSocketsExpressModule = module;\n\n // Temporarily stub `app.any` to prevent uwebsockets-express Application.init()\n // from registering its own catch-all handler \u2014 we manage HTTP routing ourselves\n // in bindRouter().\n const originalAny = this.app.any;\n this.app.any = (() => this.app) as any;\n this._expressApp = (module.default(this.app) as unknown) as express.Application;\n this.app.any = originalAny;\n resolve(this._expressApp);\n } catch (error) {\n reject(error);\n console.warn(\"\");\n console.warn(\"\u274C Error: could not initialize express.\");\n console.warn(\"\");\n console.warn(\" For Express v5, use:\");\n console.warn(\" \uD83D\uDC49 npm install --save uwebsockets-express@^2.0.1\");\n console.warn(\"\");\n console.warn(\" For Express v4, use:\");\n console.warn(\" \uD83D\uDC49 npm install --save uwebsockets-express@^1.4.1\");\n console.warn(\"\");\n process.exit();\n }\n });\n }\n return this._expressApp;\n }\n\n public bindRouter(router: Router) {\n const getCorsHeaders = (requestHeaders: Headers) => {\n return Object.assign(\n {},\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders(requestHeaders)\n );\n }\n\n const writeCorsHeaders = (res: uWebSockets.HttpResponse, requestHeaders: Headers) => {\n // skip if aborted\n if (res.aborted) { return; }\n\n const headers = getCorsHeaders(requestHeaders);\n\n for (const header in headers) {\n res.writeHeader(header, headers[header].toString());\n }\n\n return true;\n }\n\n this.app.options(\"/*\", (res, req) => {\n res.onAborted(() => res.aborted = true);\n\n // cache all headers\n const reqHeaders = new Headers();\n req.forEach((key, value) => reqHeaders.set(key, value));\n\n res.cork(() => {\n res.writeStatus(\"204 No Content\");\n writeCorsHeaders(res, reqHeaders);\n res.end();\n });\n });\n\n this.app.any('/*', async (res, req) => {\n const abortController = new AbortController();\n\n res.onAborted(() => {\n abortController.abort();\n res.aborted = true;\n });\n\n // cache all headers and request info synchronously\n // (uWebSockets.js req is only valid in the synchronous callback scope)\n const headers = new Headers();\n req.forEach((key, value) => headers.set(key, value));\n\n const method = req.getMethod().toUpperCase();\n const url = req.getUrl();\n const query = req.getQuery();\n const remoteAddress = res.getRemoteAddressAsText();\n\n // check if the route is defined in the router\n // if so, use the router handler, otherwise fallback to express\n if (router.findRoute(method, url) !== undefined) {\n const requestInit: RequestInit = {\n method,\n referrer: headers.get('referer') || undefined,\n keepalive: headers.get('keep-alive') === 'true',\n headers,\n signal: abortController.signal,\n };\n\n // read request body\n if (method !== \"GET\" && method !== \"HEAD\") {\n let body: Buffer = undefined;\n\n // uWebSockets.js `HttpRequest` does not provide 'getData', must aggregate POST body via HttpResponse\n await new Promise<void>((resolve) => {\n res.onData((ab, isLast) => {\n const chunk = Buffer.from(ab);\n if (body === undefined) {\n body = Buffer.from(chunk);\n } else {\n body = Buffer.concat([body, chunk]);\n }\n if (isLast) {\n resolve();\n }\n });\n });\n\n requestInit.body = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer;\n }\n\n const fullUrl = `http://${headers.get('host') || 'localhost'}${url}${(query ? `?${query}` : '')}`;\n const response = await router.handler(new Request(fullUrl, requestInit));\n\n // skip if aborted\n if (res.aborted) { return; }\n\n // read response body before cork (cork callback must be synchronous)\n const responseBody = await response.arrayBuffer();\n\n // writeStatus() must be called before writeHeader() in uWebSockets.js\n res.cork(() => {\n res.writeStatus(`${response.status} ${response.statusText}`);\n writeCorsHeaders(res, headers);\n response.headers.forEach((value, key) => {\n res.writeHeader(key, value);\n });\n res.end(responseBody);\n });\n\n } else if (this._expressApp) {\n const corsHeaders = getCorsHeaders(headers);\n\n const ereq = new uWebSocketsExpressModule.IncomingMessage(req, res, this._expressApp as any, {\n headers: Object.fromEntries((headers as any).entries()),\n method,\n url,\n query,\n remoteAddress\n });\n const eres = new uWebSocketsExpressModule.ServerResponse(res, req, this._expressApp);\n\n // Apply CORS headers through the Express response wrapper\n for (const header in corsHeaders) {\n eres.setHeader(header, corsHeaders[header].toString());\n }\n\n // Read the request body from uWebSockets before passing to express\n // (uWebSockets requires res.onData() to be called to consume the body)\n await ereq._readBody();\n\n this._expressApp['handle'](ereq, eres);\n }\n });\n }\n\n public listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void) {\n const callback = (listeningSocket: any) => {\n this._listeningSocket = listeningSocket;\n listeningListener?.();\n };\n\n if (typeof (port) === \"string\") {\n this.app.listen_unix(callback, port);\n\n } else {\n this.app.listen(port, callback);\n\n }\n return this;\n }\n\n public shutdown() {\n if (this._listeningSocket) {\n uWebSockets.us_listen_socket_close(this._listeningSocket);\n }\n }\n\n public simulateLatency(milliseconds: number) {\n if (this._originalRawSend == null) {\n this._originalRawSend = uWebSocketClient.prototype.raw;\n }\n\n const originalRawSend = this._originalRawSend;\n uWebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalRawSend : function (...args: any[]) {\n // copy buffer\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: RawWebSocketClient) {\n const wrapper = new uWebSocketWrapper(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.url;\n const searchParams = rawClient.searchParams;\n\n const sessionId = searchParams.sessionId as string;\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(), 1000);\n wrapper.on('message', (_) => rawClient.send(new Uint8Array([Protocol.PING]), true));\n wrapper.on('close', () => clearTimeout(timeout));\n return;\n }\n\n const room = matchMaker.getLocalRoomById(roomId);\n const client = new uWebSocketClient(sessionId, wrapper);\n const reconnectionToken = searchParams.reconnectionToken as string;\n const skipHandshake = (searchParams.skipHandshake !== undefined);\n\n try {\n await connectClientToRoom(room, client, rawClient.context, {\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.end(reconnectionToken\n ? CloseCode.FAILED_TO_RECONNECT\n : CloseCode.WITH_ERROR));\n }\n }\n\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["import_core", "module", "uWebSockets", "querystring"]
|
|
7
7
|
}
|
|
@@ -73,9 +73,13 @@ var uWebSocketsTransport = class extends Transport {
|
|
|
73
73
|
try {
|
|
74
74
|
const module = await uWebSocketsExpress;
|
|
75
75
|
uWebSocketsExpressModule = module;
|
|
76
|
+
const originalAny = this.app.any;
|
|
77
|
+
this.app.any = (() => this.app);
|
|
76
78
|
this._expressApp = module.default(this.app);
|
|
79
|
+
this.app.any = originalAny;
|
|
77
80
|
resolve(this._expressApp);
|
|
78
81
|
} catch (error) {
|
|
82
|
+
reject(error);
|
|
79
83
|
console.warn("");
|
|
80
84
|
console.warn("\u274C Error: could not initialize express.");
|
|
81
85
|
console.warn("");
|
|
@@ -92,15 +96,18 @@ var uWebSocketsTransport = class extends Transport {
|
|
|
92
96
|
return this._expressApp;
|
|
93
97
|
}
|
|
94
98
|
bindRouter(router) {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const headers = Object.assign(
|
|
99
|
+
const getCorsHeaders = (requestHeaders) => {
|
|
100
|
+
return Object.assign(
|
|
100
101
|
{},
|
|
101
102
|
matchMaker.controller.DEFAULT_CORS_HEADERS,
|
|
102
103
|
matchMaker.controller.getCorsHeaders(requestHeaders)
|
|
103
104
|
);
|
|
105
|
+
};
|
|
106
|
+
const writeCorsHeaders = (res, requestHeaders) => {
|
|
107
|
+
if (res.aborted) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const headers = getCorsHeaders(requestHeaders);
|
|
104
111
|
for (const header in headers) {
|
|
105
112
|
res.writeHeader(header, headers[header].toString());
|
|
106
113
|
}
|
|
@@ -110,10 +117,11 @@ var uWebSocketsTransport = class extends Transport {
|
|
|
110
117
|
res.onAborted(() => res.aborted = true);
|
|
111
118
|
const reqHeaders = new Headers();
|
|
112
119
|
req.forEach((key, value) => reqHeaders.set(key, value));
|
|
113
|
-
|
|
120
|
+
res.cork(() => {
|
|
114
121
|
res.writeStatus("204 No Content");
|
|
122
|
+
writeCorsHeaders(res, reqHeaders);
|
|
115
123
|
res.end();
|
|
116
|
-
}
|
|
124
|
+
});
|
|
117
125
|
});
|
|
118
126
|
this.app.any("/*", async (res, req) => {
|
|
119
127
|
const abortController = new AbortController();
|
|
@@ -123,59 +131,65 @@ var uWebSocketsTransport = class extends Transport {
|
|
|
123
131
|
});
|
|
124
132
|
const headers = new Headers();
|
|
125
133
|
req.forEach((key, value) => headers.set(key, value));
|
|
126
|
-
|
|
127
|
-
const requestInit = {
|
|
128
|
-
method: req.getMethod().toUpperCase(),
|
|
129
|
-
referrer: req.getHeader("referer"),
|
|
130
|
-
keepalive: req.getHeader("keep-alive") === "true",
|
|
131
|
-
headers,
|
|
132
|
-
signal: abortController.signal
|
|
133
|
-
};
|
|
134
|
+
const method = req.getMethod().toUpperCase();
|
|
134
135
|
const url = req.getUrl();
|
|
135
136
|
const query = req.getQuery();
|
|
136
137
|
const remoteAddress = res.getRemoteAddressAsText();
|
|
137
|
-
if (
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
138
|
+
if (router.findRoute(method, url) !== void 0) {
|
|
139
|
+
const requestInit = {
|
|
140
|
+
method,
|
|
141
|
+
referrer: headers.get("referer") || void 0,
|
|
142
|
+
keepalive: headers.get("keep-alive") === "true",
|
|
143
|
+
headers,
|
|
144
|
+
signal: abortController.signal
|
|
145
|
+
};
|
|
146
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
147
|
+
let body = void 0;
|
|
148
|
+
await new Promise((resolve) => {
|
|
149
|
+
res.onData((ab, isLast) => {
|
|
150
|
+
const chunk = Buffer.from(ab);
|
|
151
|
+
if (body === void 0) {
|
|
152
|
+
body = Buffer.from(chunk);
|
|
153
|
+
} else {
|
|
154
|
+
body = Buffer.concat([body, chunk]);
|
|
155
|
+
}
|
|
156
|
+
if (isLast) {
|
|
157
|
+
resolve();
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
requestInit.body = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength);
|
|
162
|
+
}
|
|
163
|
+
const fullUrl = `http://${headers.get("host") || "localhost"}${url}${query ? `?${query}` : ""}`;
|
|
164
|
+
const response = await router.handler(new Request(fullUrl, requestInit));
|
|
165
|
+
if (res.aborted) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
const responseBody = await response.arrayBuffer();
|
|
169
|
+
res.cork(() => {
|
|
170
|
+
res.writeStatus(`${response.status} ${response.statusText}`);
|
|
171
|
+
writeCorsHeaders(res, headers);
|
|
172
|
+
response.headers.forEach((value, key) => {
|
|
173
|
+
res.writeHeader(key, value);
|
|
150
174
|
});
|
|
175
|
+
res.end(responseBody);
|
|
151
176
|
});
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const fullUrl = `http://${headers.get("host") || "localhost"}${url}${query ? `?${query}` : ""}`;
|
|
155
|
-
const response = await router.handler(new Request(fullUrl, requestInit));
|
|
156
|
-
if (response.status === 404 && this._expressApp) {
|
|
177
|
+
} else if (this._expressApp) {
|
|
178
|
+
const corsHeaders = getCorsHeaders(headers);
|
|
157
179
|
const ereq = new uWebSocketsExpressModule.IncomingMessage(req, res, this._expressApp, {
|
|
158
180
|
headers: Object.fromEntries(headers.entries()),
|
|
159
|
-
method
|
|
181
|
+
method,
|
|
160
182
|
url,
|
|
161
183
|
query,
|
|
162
184
|
remoteAddress
|
|
163
185
|
});
|
|
164
186
|
const eres = new uWebSocketsExpressModule.ServerResponse(res, req, this._expressApp);
|
|
187
|
+
for (const header in corsHeaders) {
|
|
188
|
+
eres.setHeader(header, corsHeaders[header].toString());
|
|
189
|
+
}
|
|
190
|
+
await ereq._readBody();
|
|
165
191
|
this._expressApp["handle"](ereq, eres);
|
|
166
|
-
return;
|
|
167
192
|
}
|
|
168
|
-
if (res.aborted) {
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
const responseBody = await response.arrayBuffer();
|
|
172
|
-
res.cork(() => {
|
|
173
|
-
res.writeStatus(`${response.status} ${response.statusText}`);
|
|
174
|
-
response.headers.forEach((value, key) => {
|
|
175
|
-
res.writeHeader(key, value);
|
|
176
|
-
});
|
|
177
|
-
res.end(responseBody);
|
|
178
|
-
});
|
|
179
193
|
});
|
|
180
194
|
}
|
|
181
195
|
listen(port, hostname, backlog, listeningListener) {
|
|
@@ -235,127 +249,6 @@ var uWebSocketsTransport = class extends Transport {
|
|
|
235
249
|
client.error(e.code, e.message, () => rawClient.end(reconnectionToken ? CloseCode.FAILED_TO_RECONNECT : CloseCode.WITH_ERROR));
|
|
236
250
|
}
|
|
237
251
|
}
|
|
238
|
-
// protected registerMatchMakeRequest() {
|
|
239
|
-
// const matchmakeRoute = 'matchmake';
|
|
240
|
-
// const allowedRoomNameChars = /([a-zA-Z_\-0-9]+)/gi;
|
|
241
|
-
// const writeHeaders = (res: uWebSockets.HttpResponse, requestHeaders: Headers) => {
|
|
242
|
-
// // skip if aborted
|
|
243
|
-
// if (res.aborted) { return; }
|
|
244
|
-
// const headers = Object.assign(
|
|
245
|
-
// {},
|
|
246
|
-
// matchMaker.controller.DEFAULT_CORS_HEADERS,
|
|
247
|
-
// matchMaker.controller.getCorsHeaders(requestHeaders)
|
|
248
|
-
// );
|
|
249
|
-
// for (const header in headers) {
|
|
250
|
-
// res.writeHeader(header, headers[header].toString());
|
|
251
|
-
// }
|
|
252
|
-
// return true;
|
|
253
|
-
// }
|
|
254
|
-
// const writeError = (res: uWebSockets.HttpResponse, error: { code: number, error: string }) => {
|
|
255
|
-
// // skip if aborted
|
|
256
|
-
// if (res.aborted) { return; }
|
|
257
|
-
// res.cork(() => {
|
|
258
|
-
// res.writeStatus("406 Not Acceptable");
|
|
259
|
-
// res.end(JSON.stringify(error));
|
|
260
|
-
// });
|
|
261
|
-
// }
|
|
262
|
-
// const onAborted = (res: uWebSockets.HttpResponse) => {
|
|
263
|
-
// res.aborted = true;
|
|
264
|
-
// };
|
|
265
|
-
// this.app.options("/matchmake/*", (res, req) => {
|
|
266
|
-
// res.onAborted(() => onAborted(res));
|
|
267
|
-
// // cache all headers
|
|
268
|
-
// const reqHeaders = new Headers();
|
|
269
|
-
// req.forEach((key, value) => reqHeaders.set(key, value));
|
|
270
|
-
// if (writeHeaders(res, reqHeaders)) {
|
|
271
|
-
// res.writeStatus("204 No Content");
|
|
272
|
-
// res.end();
|
|
273
|
-
// }
|
|
274
|
-
// });
|
|
275
|
-
// // @ts-ignore
|
|
276
|
-
// this.app.post("/matchmake/*", (res, req) => {
|
|
277
|
-
// res.onAborted(() => onAborted(res));
|
|
278
|
-
// // do not accept matchmaking requests if already shutting down
|
|
279
|
-
// if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {
|
|
280
|
-
// return res.close();
|
|
281
|
-
// }
|
|
282
|
-
// // cache all headers
|
|
283
|
-
// const headers = new Headers();
|
|
284
|
-
// req.forEach((key, value) => headers.set(key, value));
|
|
285
|
-
// writeHeaders(res, headers);
|
|
286
|
-
// res.writeHeader('Content-Type', 'application/json');
|
|
287
|
-
// const url = req.getUrl();
|
|
288
|
-
// const matchedParams = url.match(allowedRoomNameChars);
|
|
289
|
-
// const matchmakeIndex = matchedParams.indexOf(matchmakeRoute);
|
|
290
|
-
// const token = getBearerToken(headers['authorization']);
|
|
291
|
-
// // read json body
|
|
292
|
-
// this.readJson(res, async (clientOptions) => {
|
|
293
|
-
// try {
|
|
294
|
-
// if (clientOptions === undefined) {
|
|
295
|
-
// throw new Error("invalid JSON input");
|
|
296
|
-
// }
|
|
297
|
-
// const method = matchedParams[matchmakeIndex + 1];
|
|
298
|
-
// const roomName = matchedParams[matchmakeIndex + 2] || '';
|
|
299
|
-
// const response = await matchMaker.controller.invokeMethod(
|
|
300
|
-
// method,
|
|
301
|
-
// roomName,
|
|
302
|
-
// clientOptions,
|
|
303
|
-
// {
|
|
304
|
-
// token,
|
|
305
|
-
// headers,
|
|
306
|
-
// ip: headers.get('x-real-ip') ?? headers.get('x-forwarded-for') ?? Buffer.from(res.getRemoteAddressAsText()).toString()
|
|
307
|
-
// }
|
|
308
|
-
// );
|
|
309
|
-
// if (!res.aborted) {
|
|
310
|
-
// res.cork(() => {
|
|
311
|
-
// res.writeStatus("200 OK");
|
|
312
|
-
// res.end(JSON.stringify(response));
|
|
313
|
-
// });
|
|
314
|
-
// }
|
|
315
|
-
// } catch (e: any) {
|
|
316
|
-
// debugAndPrintError(e);
|
|
317
|
-
// writeError(res, {
|
|
318
|
-
// code: e.code || ErrorCode.MATCHMAKE_UNHANDLED,
|
|
319
|
-
// error: e.message
|
|
320
|
-
// });
|
|
321
|
-
// }
|
|
322
|
-
// });
|
|
323
|
-
// });
|
|
324
|
-
// }
|
|
325
|
-
/* Helper function for reading a posted JSON body */
|
|
326
|
-
/* Extracted from https://github.com/uNetworking/uWebSockets.js/blob/master/examples/JsonPost.js */
|
|
327
|
-
readJson(res, cb) {
|
|
328
|
-
let buffer;
|
|
329
|
-
res.onData((ab, isLast) => {
|
|
330
|
-
let chunk = Buffer.from(ab);
|
|
331
|
-
if (isLast) {
|
|
332
|
-
let json;
|
|
333
|
-
if (buffer) {
|
|
334
|
-
try {
|
|
335
|
-
json = JSON.parse(Buffer.concat([buffer, chunk]));
|
|
336
|
-
} catch (e) {
|
|
337
|
-
cb(void 0);
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
cb(json);
|
|
341
|
-
} else {
|
|
342
|
-
try {
|
|
343
|
-
json = JSON.parse(chunk);
|
|
344
|
-
} catch (e) {
|
|
345
|
-
cb(void 0);
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
cb(json);
|
|
349
|
-
}
|
|
350
|
-
} else {
|
|
351
|
-
if (buffer) {
|
|
352
|
-
buffer = Buffer.concat([buffer, chunk]);
|
|
353
|
-
} else {
|
|
354
|
-
buffer = Buffer.concat([chunk]);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
}
|
|
359
252
|
};
|
|
360
253
|
export {
|
|
361
254
|
uWebSocketsTransport
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/uWebSocketsTransport.ts"],
|
|
4
|
-
"sourcesContent": ["import querystring, { type ParsedUrlQuery } from 'querystring';\nimport uWebSockets, { type WebSocket } from 'uWebSockets.js';\nimport type express from 'express';\n\nimport { type AuthContext, Transport, matchMaker, Protocol, getBearerToken, debugAndPrintError, spliceOne, connectClientToRoom, CloseCode, type Router } from '@colyseus/core';\nimport { uWebSocketClient, uWebSocketWrapper } from './uWebSocketClient.ts';\nimport { Deferred } from '@colyseus/core';\n\nconst uWebSocketsExpress = new Deferred<typeof import('uwebsockets-express')>;\nlet uWebSocketsExpressModule: typeof import('uwebsockets-express') | undefined = undefined;\nimport('uwebsockets-express')\n .then((module) => uWebSocketsExpress.resolve(module))\n .catch((error) => uWebSocketsExpress.reject(error));\n\nexport type TransportOptions = Omit<uWebSockets.WebSocketBehavior<any>, \"upgrade\" | \"open\" | \"pong\" | \"close\" | \"message\">;\n\ntype RawWebSocketClient = uWebSockets.WebSocket<any> & {\n url: string,\n searchParams: ParsedUrlQuery,\n context: AuthContext,\n};\n\nexport class uWebSocketsTransport extends Transport {\n public app: uWebSockets.TemplatedApp;\n\n protected clients: RawWebSocketClient[] = [];\n protected clientWrappers = new WeakMap<RawWebSocketClient, uWebSocketWrapper>();\n\n private _listeningSocket: any;\n private _originalRawSend: typeof uWebSocketClient.prototype.raw | null = null;\n private _expressApp?: express.Application;\n\n constructor(options: TransportOptions = {}, appOptions: uWebSockets.AppOptions = {}) {\n super();\n\n this.app = (appOptions.cert_file_name && appOptions.key_file_name)\n ? uWebSockets.SSLApp(appOptions)\n : uWebSockets.App(appOptions);\n\n if (options.maxBackpressure === undefined) {\n options.maxBackpressure = 1024 * 1024;\n }\n\n if (options.compression === undefined) {\n options.compression = uWebSockets.DISABLED;\n }\n\n if (options.maxPayloadLength === undefined) {\n options.maxPayloadLength = 4 * 1024;\n }\n\n if (options.sendPingsAutomatically === undefined) {\n options.sendPingsAutomatically = true;\n }\n\n this.app.ws('/*', {\n ...options,\n\n upgrade: (res, req, context) => {\n // get all headers\n const headers: { [id: string]: string } = {};\n req.forEach((key, value) => headers[key] = value);\n\n const searchParams = querystring.parse(req.getQuery());\n\n /* This immediately calls open handler, you must not use res after this call */\n /* Spell these correctly */\n res.upgrade(\n {\n url: req.getUrl(),\n searchParams,\n context: {\n token: searchParams._authToken ?? getBearerToken(req.getHeader('authorization')),\n headers,\n ip: headers['x-real-ip'] ?? headers['x-forwarded-for'] ?? Buffer.from(res.getRemoteAddressAsText()).toString(),\n }\n },\n req.getHeader('sec-websocket-key'),\n req.getHeader('sec-websocket-protocol'),\n req.getHeader('sec-websocket-extensions'),\n context\n );\n },\n\n open: async (ws: WebSocket<any>) => {\n // ws.pingCount = 0;\n await this.onConnection(ws as RawWebSocketClient);\n },\n\n // pong: (ws: RawWebSocketClient) => {\n // ws.pingCount = 0;\n // },\n\n close: (ws: WebSocket<any>, code: number, message: ArrayBuffer) => {\n // remove from client list\n spliceOne(this.clients, this.clients.indexOf(ws as RawWebSocketClient));\n\n const clientWrapper = this.clientWrappers.get(ws as RawWebSocketClient);\n if (clientWrapper) {\n this.clientWrappers.delete(ws as RawWebSocketClient);\n\n // emit 'close' on wrapper\n clientWrapper.emit('close', code);\n }\n },\n\n message: (ws: WebSocket<any>, message: ArrayBuffer, isBinary: boolean) => {\n // emit 'message' on wrapper\n this.clientWrappers.get(ws as RawWebSocketClient)?.emit('message', Buffer.from(message));\n },\n\n });\n }\n\n public getExpressApp(): Promise<express.Application> | express.Application {\n if (!this._expressApp) {\n return new Promise(async (resolve, reject) => {\n try {\n const module = await uWebSocketsExpress;\n uWebSocketsExpressModule = module;\n this._expressApp = (module.default(this.app) as unknown) as express.Application;\n resolve(this._expressApp);\n } catch (error) {\n console.warn(\"\");\n console.warn(\"\u274C Error: could not initialize express.\");\n console.warn(\"\");\n console.warn(\" For Express v5, use:\");\n console.warn(\" \uD83D\uDC49 npm install --save uwebsockets-express@^2.0.1\");\n console.warn(\"\");\n console.warn(\" For Express v4, use:\");\n console.warn(\" \uD83D\uDC49 npm install --save uwebsockets-express@^1.4.1\");\n console.warn(\"\");\n process.exit();\n }\n });\n }\n return this._expressApp;\n }\n\n public bindRouter(router: Router) {\n const writeHeaders = (res: uWebSockets.HttpResponse, requestHeaders: Headers) => {\n // skip if aborted\n if (res.aborted) { return; }\n\n const headers = Object.assign(\n {},\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders(requestHeaders)\n );\n\n for (const header in headers) {\n res.writeHeader(header, headers[header].toString());\n }\n\n return true;\n }\n\n this.app.options(\"/*\", (res, req) => {\n res.onAborted(() => res.aborted = true);\n\n // cache all headers\n const reqHeaders = new Headers();\n req.forEach((key, value) => reqHeaders.set(key, value));\n\n if (writeHeaders(res, reqHeaders)) {\n res.writeStatus(\"204 No Content\");\n res.end();\n }\n });\n\n this.app.any('/*', async (res, req) => {\n const abortController = new AbortController();\n\n res.onAborted(() => {\n abortController.abort();\n res.aborted = true;\n });\n\n const headers = new Headers();\n req.forEach((key, value) => headers.set(key, value));\n\n // write cors headers\n writeHeaders(res, headers);\n\n const requestInit: RequestInit = {\n method: req.getMethod().toUpperCase(),\n referrer: req.getHeader('referer'),\n keepalive: req.getHeader('keep-alive') === 'true',\n headers,\n signal: abortController.signal,\n };\n\n // Construct full URL (Request constructor requires absolute URL)\n const url = req.getUrl();\n const query = req.getQuery();\n const remoteAddress = res.getRemoteAddressAsText();\n\n // read request body\n if (requestInit.method.toUpperCase() !== \"GET\" && requestInit.method.toUpperCase() !== \"HEAD\") {\n let body: Buffer = undefined;\n\n // uWebSockets.js `HttpRequest` does not provide 'getData', must aggregate POST body via HttpResponse\n await new Promise<void>((resolve) => {\n res.onData((ab, isLast) => {\n const chunk = Buffer.from(ab);\n if (body === undefined) {\n body = Buffer.from(chunk);\n } else {\n body = Buffer.concat([body, chunk]);\n }\n if (isLast) {\n resolve();\n }\n });\n });\n\n requestInit.body = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer;\n }\n\n const fullUrl = `http://${headers.get('host') || 'localhost'}${url}${(query ? `?${query}` : '')}`\n const response = await router.handler(new Request(fullUrl, requestInit));\n\n // fallback to express stack if 404\n if (response.status === 404 && this._expressApp) {\n const ereq = new uWebSocketsExpressModule.IncomingMessage(req, res, this._expressApp as any, {\n headers: Object.fromEntries((headers as any).entries()),\n method: requestInit.method,\n url,\n query,\n remoteAddress\n });\n const eres = new uWebSocketsExpressModule.ServerResponse(res, req, this._expressApp);\n this._expressApp['handle'](ereq, eres);\n return;\n }\n\n // skip if aborted\n if (res.aborted) { return; }\n\n // read response body before cork (cork callback must be synchronous)\n const responseBody = await response.arrayBuffer();\n\n res.cork(() => {\n res.writeStatus(`${response.status} ${response.statusText}`);\n response.headers.forEach((value, key) => {\n res.writeHeader(key, value);\n });\n res.end(responseBody);\n });\n });\n }\n\n public listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void) {\n const callback = (listeningSocket: any) => {\n this._listeningSocket = listeningSocket;\n listeningListener?.();\n };\n\n if (typeof (port) === \"string\") {\n this.app.listen_unix(callback, port);\n\n } else {\n this.app.listen(port, callback);\n\n }\n return this;\n }\n\n public shutdown() {\n if (this._listeningSocket) {\n uWebSockets.us_listen_socket_close(this._listeningSocket);\n }\n }\n\n public simulateLatency(milliseconds: number) {\n if (this._originalRawSend == null) {\n this._originalRawSend = uWebSocketClient.prototype.raw;\n }\n\n const originalRawSend = this._originalRawSend;\n uWebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalRawSend : function (...args: any[]) {\n // copy buffer\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: RawWebSocketClient) {\n const wrapper = new uWebSocketWrapper(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.url;\n const searchParams = rawClient.searchParams;\n\n const sessionId = searchParams.sessionId as string;\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(), 1000);\n wrapper.on('message', (_) => rawClient.send(new Uint8Array([Protocol.PING]), true));\n wrapper.on('close', () => clearTimeout(timeout));\n return;\n }\n\n const room = matchMaker.getLocalRoomById(roomId);\n const client = new uWebSocketClient(sessionId, wrapper);\n const reconnectionToken = searchParams.reconnectionToken as string;\n const skipHandshake = (searchParams.skipHandshake !== undefined);\n\n try {\n await connectClientToRoom(room, client, rawClient.context, {\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.end(reconnectionToken\n ? CloseCode.FAILED_TO_RECONNECT\n : CloseCode.WITH_ERROR));\n }\n }\n\n // protected registerMatchMakeRequest() {\n // const matchmakeRoute = 'matchmake';\n // const allowedRoomNameChars = /([a-zA-Z_\\-0-9]+)/gi;\n\n // const writeHeaders = (res: uWebSockets.HttpResponse, requestHeaders: Headers) => {\n // // skip if aborted\n // if (res.aborted) { return; }\n\n // const headers = Object.assign(\n // {},\n // matchMaker.controller.DEFAULT_CORS_HEADERS,\n // matchMaker.controller.getCorsHeaders(requestHeaders)\n // );\n\n // for (const header in headers) {\n // res.writeHeader(header, headers[header].toString());\n // }\n\n // return true;\n // }\n\n // const writeError = (res: uWebSockets.HttpResponse, error: { code: number, error: string }) => {\n // // skip if aborted\n // if (res.aborted) { return; }\n\n // res.cork(() => {\n // res.writeStatus(\"406 Not Acceptable\");\n // res.end(JSON.stringify(error));\n // });\n // }\n\n // const onAborted = (res: uWebSockets.HttpResponse) => {\n // res.aborted = true;\n // };\n\n // this.app.options(\"/matchmake/*\", (res, req) => {\n // res.onAborted(() => onAborted(res));\n\n // // cache all headers\n // const reqHeaders = new Headers();\n // req.forEach((key, value) => reqHeaders.set(key, value));\n\n // if (writeHeaders(res, reqHeaders)) {\n // res.writeStatus(\"204 No Content\");\n // res.end();\n // }\n // });\n\n\n // // @ts-ignore\n // this.app.post(\"/matchmake/*\", (res, req) => {\n // res.onAborted(() => onAborted(res));\n\n // // do not accept matchmaking requests if already shutting down\n // if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {\n // return res.close();\n // }\n\n // // cache all headers\n // const headers = new Headers();\n // req.forEach((key, value) => headers.set(key, value));\n\n // writeHeaders(res, headers);\n // res.writeHeader('Content-Type', 'application/json');\n\n // const url = req.getUrl();\n // const matchedParams = url.match(allowedRoomNameChars);\n // const matchmakeIndex = matchedParams.indexOf(matchmakeRoute);\n\n // const token = getBearerToken(headers['authorization']);\n\n // // read json body\n // this.readJson(res, async (clientOptions) => {\n // try {\n // if (clientOptions === undefined) {\n // throw new Error(\"invalid JSON input\");\n // }\n\n // const method = matchedParams[matchmakeIndex + 1];\n // const roomName = matchedParams[matchmakeIndex + 2] || '';\n\n // const response = await matchMaker.controller.invokeMethod(\n // method,\n // roomName,\n // clientOptions,\n // {\n // token,\n // headers,\n // ip: headers.get('x-real-ip') ?? headers.get('x-forwarded-for') ?? Buffer.from(res.getRemoteAddressAsText()).toString()\n // }\n // );\n\n // if (!res.aborted) {\n // res.cork(() => {\n // res.writeStatus(\"200 OK\");\n // res.end(JSON.stringify(response));\n // });\n // }\n\n // } catch (e: any) {\n // debugAndPrintError(e);\n // writeError(res, {\n // code: e.code || ErrorCode.MATCHMAKE_UNHANDLED,\n // error: e.message\n // });\n // }\n\n // });\n // });\n // }\n\n /* Helper function for reading a posted JSON body */\n /* Extracted from https://github.com/uNetworking/uWebSockets.js/blob/master/examples/JsonPost.js */\n private readJson(res: uWebSockets.HttpResponse, cb: (json: any) => void) {\n let buffer: Buffer;\n /* Register data cb */\n res.onData((ab, isLast) => {\n let chunk = Buffer.from(ab);\n if (isLast) {\n let json;\n if (buffer) {\n try {\n // @ts-ignore\n json = JSON.parse(Buffer.concat([buffer, chunk]));\n } catch (e) {\n /* res.close calls onAborted */\n // res.close();\n cb(undefined);\n return;\n }\n cb(json);\n } else {\n try {\n // @ts-ignore\n json = JSON.parse(chunk);\n } catch (e) {\n /* res.close calls onAborted */\n // res.close();\n cb(undefined);\n return;\n }\n cb(json);\n }\n } else {\n if (buffer) {\n buffer = Buffer.concat([buffer, chunk]);\n } else {\n buffer = Buffer.concat([chunk]);\n }\n }\n });\n }\n}\n"],
|
|
5
|
-
"mappings": "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
|
|
4
|
+
"sourcesContent": ["import querystring, { type ParsedUrlQuery } from 'querystring';\nimport uWebSockets, { type WebSocket } from 'uWebSockets.js';\nimport type express from 'express';\n\nimport { type AuthContext, Transport, matchMaker, Protocol, getBearerToken, debugAndPrintError, spliceOne, connectClientToRoom, CloseCode, type Router } from '@colyseus/core';\nimport { uWebSocketClient, uWebSocketWrapper } from './uWebSocketClient.ts';\nimport { Deferred } from '@colyseus/core';\n\nconst uWebSocketsExpress = new Deferred<typeof import('uwebsockets-express')>;\nlet uWebSocketsExpressModule: typeof import('uwebsockets-express') | undefined = undefined;\nimport('uwebsockets-express')\n .then((module) => uWebSocketsExpress.resolve(module))\n .catch((error) => uWebSocketsExpress.reject(error));\n\nexport type TransportOptions = Omit<uWebSockets.WebSocketBehavior<any>, \"upgrade\" | \"open\" | \"pong\" | \"close\" | \"message\">;\n\ntype RawWebSocketClient = uWebSockets.WebSocket<any> & {\n url: string,\n searchParams: ParsedUrlQuery,\n context: AuthContext,\n};\n\nexport class uWebSocketsTransport extends Transport {\n public app: uWebSockets.TemplatedApp;\n\n protected clients: RawWebSocketClient[] = [];\n protected clientWrappers = new WeakMap<RawWebSocketClient, uWebSocketWrapper>();\n\n private _listeningSocket: any;\n private _originalRawSend: typeof uWebSocketClient.prototype.raw | null = null;\n private _expressApp?: express.Application;\n\n constructor(options: TransportOptions = {}, appOptions: uWebSockets.AppOptions = {}) {\n super();\n\n this.app = (appOptions.cert_file_name && appOptions.key_file_name)\n ? uWebSockets.SSLApp(appOptions)\n : uWebSockets.App(appOptions);\n\n if (options.maxBackpressure === undefined) {\n options.maxBackpressure = 1024 * 1024;\n }\n\n if (options.compression === undefined) {\n options.compression = uWebSockets.DISABLED;\n }\n\n if (options.maxPayloadLength === undefined) {\n options.maxPayloadLength = 4 * 1024;\n }\n\n if (options.sendPingsAutomatically === undefined) {\n options.sendPingsAutomatically = true;\n }\n\n this.app.ws('/*', {\n ...options,\n\n upgrade: (res, req, context) => {\n // get all headers\n const headers: { [id: string]: string } = {};\n req.forEach((key, value) => headers[key] = value);\n\n const searchParams = querystring.parse(req.getQuery());\n\n /* This immediately calls open handler, you must not use res after this call */\n /* Spell these correctly */\n res.upgrade(\n {\n url: req.getUrl(),\n searchParams,\n context: {\n token: searchParams._authToken ?? getBearerToken(req.getHeader('authorization')),\n headers,\n ip: headers['x-real-ip'] ?? headers['x-forwarded-for'] ?? Buffer.from(res.getRemoteAddressAsText()).toString(),\n }\n },\n req.getHeader('sec-websocket-key'),\n req.getHeader('sec-websocket-protocol'),\n req.getHeader('sec-websocket-extensions'),\n context\n );\n },\n\n open: async (ws: WebSocket<any>) => {\n // ws.pingCount = 0;\n await this.onConnection(ws as RawWebSocketClient);\n },\n\n // pong: (ws: RawWebSocketClient) => {\n // ws.pingCount = 0;\n // },\n\n close: (ws: WebSocket<any>, code: number, message: ArrayBuffer) => {\n // remove from client list\n spliceOne(this.clients, this.clients.indexOf(ws as RawWebSocketClient));\n\n const clientWrapper = this.clientWrappers.get(ws as RawWebSocketClient);\n if (clientWrapper) {\n this.clientWrappers.delete(ws as RawWebSocketClient);\n\n // emit 'close' on wrapper\n clientWrapper.emit('close', code);\n }\n },\n\n message: (ws: WebSocket<any>, message: ArrayBuffer, isBinary: boolean) => {\n // emit 'message' on wrapper\n this.clientWrappers.get(ws as RawWebSocketClient)?.emit('message', Buffer.from(message));\n },\n\n });\n }\n\n public getExpressApp(): Promise<express.Application> | express.Application {\n if (!this._expressApp) {\n return new Promise(async (resolve, reject) => {\n try {\n const module = await uWebSocketsExpress;\n uWebSocketsExpressModule = module;\n\n // Temporarily stub `app.any` to prevent uwebsockets-express Application.init()\n // from registering its own catch-all handler \u2014 we manage HTTP routing ourselves\n // in bindRouter().\n const originalAny = this.app.any;\n this.app.any = (() => this.app) as any;\n this._expressApp = (module.default(this.app) as unknown) as express.Application;\n this.app.any = originalAny;\n resolve(this._expressApp);\n } catch (error) {\n reject(error);\n console.warn(\"\");\n console.warn(\"\u274C Error: could not initialize express.\");\n console.warn(\"\");\n console.warn(\" For Express v5, use:\");\n console.warn(\" \uD83D\uDC49 npm install --save uwebsockets-express@^2.0.1\");\n console.warn(\"\");\n console.warn(\" For Express v4, use:\");\n console.warn(\" \uD83D\uDC49 npm install --save uwebsockets-express@^1.4.1\");\n console.warn(\"\");\n process.exit();\n }\n });\n }\n return this._expressApp;\n }\n\n public bindRouter(router: Router) {\n const getCorsHeaders = (requestHeaders: Headers) => {\n return Object.assign(\n {},\n matchMaker.controller.DEFAULT_CORS_HEADERS,\n matchMaker.controller.getCorsHeaders(requestHeaders)\n );\n }\n\n const writeCorsHeaders = (res: uWebSockets.HttpResponse, requestHeaders: Headers) => {\n // skip if aborted\n if (res.aborted) { return; }\n\n const headers = getCorsHeaders(requestHeaders);\n\n for (const header in headers) {\n res.writeHeader(header, headers[header].toString());\n }\n\n return true;\n }\n\n this.app.options(\"/*\", (res, req) => {\n res.onAborted(() => res.aborted = true);\n\n // cache all headers\n const reqHeaders = new Headers();\n req.forEach((key, value) => reqHeaders.set(key, value));\n\n res.cork(() => {\n res.writeStatus(\"204 No Content\");\n writeCorsHeaders(res, reqHeaders);\n res.end();\n });\n });\n\n this.app.any('/*', async (res, req) => {\n const abortController = new AbortController();\n\n res.onAborted(() => {\n abortController.abort();\n res.aborted = true;\n });\n\n // cache all headers and request info synchronously\n // (uWebSockets.js req is only valid in the synchronous callback scope)\n const headers = new Headers();\n req.forEach((key, value) => headers.set(key, value));\n\n const method = req.getMethod().toUpperCase();\n const url = req.getUrl();\n const query = req.getQuery();\n const remoteAddress = res.getRemoteAddressAsText();\n\n // check if the route is defined in the router\n // if so, use the router handler, otherwise fallback to express\n if (router.findRoute(method, url) !== undefined) {\n const requestInit: RequestInit = {\n method,\n referrer: headers.get('referer') || undefined,\n keepalive: headers.get('keep-alive') === 'true',\n headers,\n signal: abortController.signal,\n };\n\n // read request body\n if (method !== \"GET\" && method !== \"HEAD\") {\n let body: Buffer = undefined;\n\n // uWebSockets.js `HttpRequest` does not provide 'getData', must aggregate POST body via HttpResponse\n await new Promise<void>((resolve) => {\n res.onData((ab, isLast) => {\n const chunk = Buffer.from(ab);\n if (body === undefined) {\n body = Buffer.from(chunk);\n } else {\n body = Buffer.concat([body, chunk]);\n }\n if (isLast) {\n resolve();\n }\n });\n });\n\n requestInit.body = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer;\n }\n\n const fullUrl = `http://${headers.get('host') || 'localhost'}${url}${(query ? `?${query}` : '')}`;\n const response = await router.handler(new Request(fullUrl, requestInit));\n\n // skip if aborted\n if (res.aborted) { return; }\n\n // read response body before cork (cork callback must be synchronous)\n const responseBody = await response.arrayBuffer();\n\n // writeStatus() must be called before writeHeader() in uWebSockets.js\n res.cork(() => {\n res.writeStatus(`${response.status} ${response.statusText}`);\n writeCorsHeaders(res, headers);\n response.headers.forEach((value, key) => {\n res.writeHeader(key, value);\n });\n res.end(responseBody);\n });\n\n } else if (this._expressApp) {\n const corsHeaders = getCorsHeaders(headers);\n\n const ereq = new uWebSocketsExpressModule.IncomingMessage(req, res, this._expressApp as any, {\n headers: Object.fromEntries((headers as any).entries()),\n method,\n url,\n query,\n remoteAddress\n });\n const eres = new uWebSocketsExpressModule.ServerResponse(res, req, this._expressApp);\n\n // Apply CORS headers through the Express response wrapper\n for (const header in corsHeaders) {\n eres.setHeader(header, corsHeaders[header].toString());\n }\n\n // Read the request body from uWebSockets before passing to express\n // (uWebSockets requires res.onData() to be called to consume the body)\n await ereq._readBody();\n\n this._expressApp['handle'](ereq, eres);\n }\n });\n }\n\n public listen(port: number, hostname?: string, backlog?: number, listeningListener?: () => void) {\n const callback = (listeningSocket: any) => {\n this._listeningSocket = listeningSocket;\n listeningListener?.();\n };\n\n if (typeof (port) === \"string\") {\n this.app.listen_unix(callback, port);\n\n } else {\n this.app.listen(port, callback);\n\n }\n return this;\n }\n\n public shutdown() {\n if (this._listeningSocket) {\n uWebSockets.us_listen_socket_close(this._listeningSocket);\n }\n }\n\n public simulateLatency(milliseconds: number) {\n if (this._originalRawSend == null) {\n this._originalRawSend = uWebSocketClient.prototype.raw;\n }\n\n const originalRawSend = this._originalRawSend;\n uWebSocketClient.prototype.raw = milliseconds <= Number.EPSILON ? originalRawSend : function (...args: any[]) {\n // copy buffer\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: RawWebSocketClient) {\n const wrapper = new uWebSocketWrapper(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.url;\n const searchParams = rawClient.searchParams;\n\n const sessionId = searchParams.sessionId as string;\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(), 1000);\n wrapper.on('message', (_) => rawClient.send(new Uint8Array([Protocol.PING]), true));\n wrapper.on('close', () => clearTimeout(timeout));\n return;\n }\n\n const room = matchMaker.getLocalRoomById(roomId);\n const client = new uWebSocketClient(sessionId, wrapper);\n const reconnectionToken = searchParams.reconnectionToken as string;\n const skipHandshake = (searchParams.skipHandshake !== undefined);\n\n try {\n await connectClientToRoom(room, client, rawClient.context, {\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.end(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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colyseus/uwebsockets-transport",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"input": "./src/index.ts",
|
|
6
6
|
"main": "./build/index.cjs",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.57.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@colyseus/core": "^0.17.
|
|
27
|
+
"@colyseus/core": "^0.17.34"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"@colyseus/core": "0.17.x",
|
|
@@ -118,9 +118,17 @@ export class uWebSocketsTransport extends Transport {
|
|
|
118
118
|
try {
|
|
119
119
|
const module = await uWebSocketsExpress;
|
|
120
120
|
uWebSocketsExpressModule = module;
|
|
121
|
+
|
|
122
|
+
// Temporarily stub `app.any` to prevent uwebsockets-express Application.init()
|
|
123
|
+
// from registering its own catch-all handler — we manage HTTP routing ourselves
|
|
124
|
+
// in bindRouter().
|
|
125
|
+
const originalAny = this.app.any;
|
|
126
|
+
this.app.any = (() => this.app) as any;
|
|
121
127
|
this._expressApp = (module.default(this.app) as unknown) as express.Application;
|
|
128
|
+
this.app.any = originalAny;
|
|
122
129
|
resolve(this._expressApp);
|
|
123
130
|
} catch (error) {
|
|
131
|
+
reject(error);
|
|
124
132
|
console.warn("");
|
|
125
133
|
console.warn("❌ Error: could not initialize express.");
|
|
126
134
|
console.warn("");
|
|
@@ -138,15 +146,19 @@ export class uWebSocketsTransport extends Transport {
|
|
|
138
146
|
}
|
|
139
147
|
|
|
140
148
|
public bindRouter(router: Router) {
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
if (res.aborted) { return; }
|
|
144
|
-
|
|
145
|
-
const headers = Object.assign(
|
|
149
|
+
const getCorsHeaders = (requestHeaders: Headers) => {
|
|
150
|
+
return Object.assign(
|
|
146
151
|
{},
|
|
147
152
|
matchMaker.controller.DEFAULT_CORS_HEADERS,
|
|
148
153
|
matchMaker.controller.getCorsHeaders(requestHeaders)
|
|
149
154
|
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const writeCorsHeaders = (res: uWebSockets.HttpResponse, requestHeaders: Headers) => {
|
|
158
|
+
// skip if aborted
|
|
159
|
+
if (res.aborted) { return; }
|
|
160
|
+
|
|
161
|
+
const headers = getCorsHeaders(requestHeaders);
|
|
150
162
|
|
|
151
163
|
for (const header in headers) {
|
|
152
164
|
res.writeHeader(header, headers[header].toString());
|
|
@@ -162,10 +174,11 @@ export class uWebSocketsTransport extends Transport {
|
|
|
162
174
|
const reqHeaders = new Headers();
|
|
163
175
|
req.forEach((key, value) => reqHeaders.set(key, value));
|
|
164
176
|
|
|
165
|
-
|
|
177
|
+
res.cork(() => {
|
|
166
178
|
res.writeStatus("204 No Content");
|
|
179
|
+
writeCorsHeaders(res, reqHeaders);
|
|
167
180
|
res.end();
|
|
168
|
-
}
|
|
181
|
+
});
|
|
169
182
|
});
|
|
170
183
|
|
|
171
184
|
this.app.any('/*', async (res, req) => {
|
|
@@ -176,77 +189,91 @@ export class uWebSocketsTransport extends Transport {
|
|
|
176
189
|
res.aborted = true;
|
|
177
190
|
});
|
|
178
191
|
|
|
192
|
+
// cache all headers and request info synchronously
|
|
193
|
+
// (uWebSockets.js req is only valid in the synchronous callback scope)
|
|
179
194
|
const headers = new Headers();
|
|
180
195
|
req.forEach((key, value) => headers.set(key, value));
|
|
181
196
|
|
|
182
|
-
|
|
183
|
-
writeHeaders(res, headers);
|
|
184
|
-
|
|
185
|
-
const requestInit: RequestInit = {
|
|
186
|
-
method: req.getMethod().toUpperCase(),
|
|
187
|
-
referrer: req.getHeader('referer'),
|
|
188
|
-
keepalive: req.getHeader('keep-alive') === 'true',
|
|
189
|
-
headers,
|
|
190
|
-
signal: abortController.signal,
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
// Construct full URL (Request constructor requires absolute URL)
|
|
197
|
+
const method = req.getMethod().toUpperCase();
|
|
194
198
|
const url = req.getUrl();
|
|
195
199
|
const query = req.getQuery();
|
|
196
200
|
const remoteAddress = res.getRemoteAddressAsText();
|
|
197
201
|
|
|
198
|
-
//
|
|
199
|
-
if
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
202
|
+
// check if the route is defined in the router
|
|
203
|
+
// if so, use the router handler, otherwise fallback to express
|
|
204
|
+
if (router.findRoute(method, url) !== undefined) {
|
|
205
|
+
const requestInit: RequestInit = {
|
|
206
|
+
method,
|
|
207
|
+
referrer: headers.get('referer') || undefined,
|
|
208
|
+
keepalive: headers.get('keep-alive') === 'true',
|
|
209
|
+
headers,
|
|
210
|
+
signal: abortController.signal,
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// read request body
|
|
214
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
215
|
+
let body: Buffer = undefined;
|
|
216
|
+
|
|
217
|
+
// uWebSockets.js `HttpRequest` does not provide 'getData', must aggregate POST body via HttpResponse
|
|
218
|
+
await new Promise<void>((resolve) => {
|
|
219
|
+
res.onData((ab, isLast) => {
|
|
220
|
+
const chunk = Buffer.from(ab);
|
|
221
|
+
if (body === undefined) {
|
|
222
|
+
body = Buffer.from(chunk);
|
|
223
|
+
} else {
|
|
224
|
+
body = Buffer.concat([body, chunk]);
|
|
225
|
+
}
|
|
226
|
+
if (isLast) {
|
|
227
|
+
resolve();
|
|
228
|
+
}
|
|
229
|
+
});
|
|
214
230
|
});
|
|
215
|
-
});
|
|
216
231
|
|
|
217
|
-
|
|
218
|
-
|
|
232
|
+
requestInit.body = body.buffer.slice(body.byteOffset, body.byteOffset + body.byteLength) as ArrayBuffer;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const fullUrl = `http://${headers.get('host') || 'localhost'}${url}${(query ? `?${query}` : '')}`;
|
|
236
|
+
const response = await router.handler(new Request(fullUrl, requestInit));
|
|
219
237
|
|
|
220
|
-
|
|
221
|
-
|
|
238
|
+
// skip if aborted
|
|
239
|
+
if (res.aborted) { return; }
|
|
240
|
+
|
|
241
|
+
// read response body before cork (cork callback must be synchronous)
|
|
242
|
+
const responseBody = await response.arrayBuffer();
|
|
243
|
+
|
|
244
|
+
// writeStatus() must be called before writeHeader() in uWebSockets.js
|
|
245
|
+
res.cork(() => {
|
|
246
|
+
res.writeStatus(`${response.status} ${response.statusText}`);
|
|
247
|
+
writeCorsHeaders(res, headers);
|
|
248
|
+
response.headers.forEach((value, key) => {
|
|
249
|
+
res.writeHeader(key, value);
|
|
250
|
+
});
|
|
251
|
+
res.end(responseBody);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
} else if (this._expressApp) {
|
|
255
|
+
const corsHeaders = getCorsHeaders(headers);
|
|
222
256
|
|
|
223
|
-
// fallback to express stack if 404
|
|
224
|
-
if (response.status === 404 && this._expressApp) {
|
|
225
257
|
const ereq = new uWebSocketsExpressModule.IncomingMessage(req, res, this._expressApp as any, {
|
|
226
258
|
headers: Object.fromEntries((headers as any).entries()),
|
|
227
|
-
method
|
|
259
|
+
method,
|
|
228
260
|
url,
|
|
229
261
|
query,
|
|
230
262
|
remoteAddress
|
|
231
263
|
});
|
|
232
264
|
const eres = new uWebSocketsExpressModule.ServerResponse(res, req, this._expressApp);
|
|
233
|
-
this._expressApp['handle'](ereq, eres);
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
265
|
|
|
237
|
-
|
|
238
|
-
|
|
266
|
+
// Apply CORS headers through the Express response wrapper
|
|
267
|
+
for (const header in corsHeaders) {
|
|
268
|
+
eres.setHeader(header, corsHeaders[header].toString());
|
|
269
|
+
}
|
|
239
270
|
|
|
240
|
-
|
|
241
|
-
|
|
271
|
+
// Read the request body from uWebSockets before passing to express
|
|
272
|
+
// (uWebSockets requires res.onData() to be called to consume the body)
|
|
273
|
+
await ereq._readBody();
|
|
242
274
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
response.headers.forEach((value, key) => {
|
|
246
|
-
res.writeHeader(key, value);
|
|
247
|
-
});
|
|
248
|
-
res.end(responseBody);
|
|
249
|
-
});
|
|
275
|
+
this._expressApp['handle'](ereq, eres);
|
|
276
|
+
}
|
|
250
277
|
});
|
|
251
278
|
}
|
|
252
279
|
|
|
@@ -331,156 +358,4 @@ export class uWebSocketsTransport extends Transport {
|
|
|
331
358
|
}
|
|
332
359
|
}
|
|
333
360
|
|
|
334
|
-
// protected registerMatchMakeRequest() {
|
|
335
|
-
// const matchmakeRoute = 'matchmake';
|
|
336
|
-
// const allowedRoomNameChars = /([a-zA-Z_\-0-9]+)/gi;
|
|
337
|
-
|
|
338
|
-
// const writeHeaders = (res: uWebSockets.HttpResponse, requestHeaders: Headers) => {
|
|
339
|
-
// // skip if aborted
|
|
340
|
-
// if (res.aborted) { return; }
|
|
341
|
-
|
|
342
|
-
// const headers = Object.assign(
|
|
343
|
-
// {},
|
|
344
|
-
// matchMaker.controller.DEFAULT_CORS_HEADERS,
|
|
345
|
-
// matchMaker.controller.getCorsHeaders(requestHeaders)
|
|
346
|
-
// );
|
|
347
|
-
|
|
348
|
-
// for (const header in headers) {
|
|
349
|
-
// res.writeHeader(header, headers[header].toString());
|
|
350
|
-
// }
|
|
351
|
-
|
|
352
|
-
// return true;
|
|
353
|
-
// }
|
|
354
|
-
|
|
355
|
-
// const writeError = (res: uWebSockets.HttpResponse, error: { code: number, error: string }) => {
|
|
356
|
-
// // skip if aborted
|
|
357
|
-
// if (res.aborted) { return; }
|
|
358
|
-
|
|
359
|
-
// res.cork(() => {
|
|
360
|
-
// res.writeStatus("406 Not Acceptable");
|
|
361
|
-
// res.end(JSON.stringify(error));
|
|
362
|
-
// });
|
|
363
|
-
// }
|
|
364
|
-
|
|
365
|
-
// const onAborted = (res: uWebSockets.HttpResponse) => {
|
|
366
|
-
// res.aborted = true;
|
|
367
|
-
// };
|
|
368
|
-
|
|
369
|
-
// this.app.options("/matchmake/*", (res, req) => {
|
|
370
|
-
// res.onAborted(() => onAborted(res));
|
|
371
|
-
|
|
372
|
-
// // cache all headers
|
|
373
|
-
// const reqHeaders = new Headers();
|
|
374
|
-
// req.forEach((key, value) => reqHeaders.set(key, value));
|
|
375
|
-
|
|
376
|
-
// if (writeHeaders(res, reqHeaders)) {
|
|
377
|
-
// res.writeStatus("204 No Content");
|
|
378
|
-
// res.end();
|
|
379
|
-
// }
|
|
380
|
-
// });
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
// // @ts-ignore
|
|
384
|
-
// this.app.post("/matchmake/*", (res, req) => {
|
|
385
|
-
// res.onAborted(() => onAborted(res));
|
|
386
|
-
|
|
387
|
-
// // do not accept matchmaking requests if already shutting down
|
|
388
|
-
// if (matchMaker.state === matchMaker.MatchMakerState.SHUTTING_DOWN) {
|
|
389
|
-
// return res.close();
|
|
390
|
-
// }
|
|
391
|
-
|
|
392
|
-
// // cache all headers
|
|
393
|
-
// const headers = new Headers();
|
|
394
|
-
// req.forEach((key, value) => headers.set(key, value));
|
|
395
|
-
|
|
396
|
-
// writeHeaders(res, headers);
|
|
397
|
-
// res.writeHeader('Content-Type', 'application/json');
|
|
398
|
-
|
|
399
|
-
// const url = req.getUrl();
|
|
400
|
-
// const matchedParams = url.match(allowedRoomNameChars);
|
|
401
|
-
// const matchmakeIndex = matchedParams.indexOf(matchmakeRoute);
|
|
402
|
-
|
|
403
|
-
// const token = getBearerToken(headers['authorization']);
|
|
404
|
-
|
|
405
|
-
// // read json body
|
|
406
|
-
// this.readJson(res, async (clientOptions) => {
|
|
407
|
-
// try {
|
|
408
|
-
// if (clientOptions === undefined) {
|
|
409
|
-
// throw new Error("invalid JSON input");
|
|
410
|
-
// }
|
|
411
|
-
|
|
412
|
-
// const method = matchedParams[matchmakeIndex + 1];
|
|
413
|
-
// const roomName = matchedParams[matchmakeIndex + 2] || '';
|
|
414
|
-
|
|
415
|
-
// const response = await matchMaker.controller.invokeMethod(
|
|
416
|
-
// method,
|
|
417
|
-
// roomName,
|
|
418
|
-
// clientOptions,
|
|
419
|
-
// {
|
|
420
|
-
// token,
|
|
421
|
-
// headers,
|
|
422
|
-
// ip: headers.get('x-real-ip') ?? headers.get('x-forwarded-for') ?? Buffer.from(res.getRemoteAddressAsText()).toString()
|
|
423
|
-
// }
|
|
424
|
-
// );
|
|
425
|
-
|
|
426
|
-
// if (!res.aborted) {
|
|
427
|
-
// res.cork(() => {
|
|
428
|
-
// res.writeStatus("200 OK");
|
|
429
|
-
// res.end(JSON.stringify(response));
|
|
430
|
-
// });
|
|
431
|
-
// }
|
|
432
|
-
|
|
433
|
-
// } catch (e: any) {
|
|
434
|
-
// debugAndPrintError(e);
|
|
435
|
-
// writeError(res, {
|
|
436
|
-
// code: e.code || ErrorCode.MATCHMAKE_UNHANDLED,
|
|
437
|
-
// error: e.message
|
|
438
|
-
// });
|
|
439
|
-
// }
|
|
440
|
-
|
|
441
|
-
// });
|
|
442
|
-
// });
|
|
443
|
-
// }
|
|
444
|
-
|
|
445
|
-
/* Helper function for reading a posted JSON body */
|
|
446
|
-
/* Extracted from https://github.com/uNetworking/uWebSockets.js/blob/master/examples/JsonPost.js */
|
|
447
|
-
private readJson(res: uWebSockets.HttpResponse, cb: (json: any) => void) {
|
|
448
|
-
let buffer: Buffer;
|
|
449
|
-
/* Register data cb */
|
|
450
|
-
res.onData((ab, isLast) => {
|
|
451
|
-
let chunk = Buffer.from(ab);
|
|
452
|
-
if (isLast) {
|
|
453
|
-
let json;
|
|
454
|
-
if (buffer) {
|
|
455
|
-
try {
|
|
456
|
-
// @ts-ignore
|
|
457
|
-
json = JSON.parse(Buffer.concat([buffer, chunk]));
|
|
458
|
-
} catch (e) {
|
|
459
|
-
/* res.close calls onAborted */
|
|
460
|
-
// res.close();
|
|
461
|
-
cb(undefined);
|
|
462
|
-
return;
|
|
463
|
-
}
|
|
464
|
-
cb(json);
|
|
465
|
-
} else {
|
|
466
|
-
try {
|
|
467
|
-
// @ts-ignore
|
|
468
|
-
json = JSON.parse(chunk);
|
|
469
|
-
} catch (e) {
|
|
470
|
-
/* res.close calls onAborted */
|
|
471
|
-
// res.close();
|
|
472
|
-
cb(undefined);
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
cb(json);
|
|
476
|
-
}
|
|
477
|
-
} else {
|
|
478
|
-
if (buffer) {
|
|
479
|
-
buffer = Buffer.concat([buffer, chunk]);
|
|
480
|
-
} else {
|
|
481
|
-
buffer = Buffer.concat([chunk]);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
});
|
|
485
|
-
}
|
|
486
361
|
}
|