@signe/room 1.4.2 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +365 -22
- package/dist/index.js +1659 -71
- package/dist/index.js.map +1 -1
- package/examples/game/app/client.tsx +2 -2
- package/examples/game/app/components/Admin.tsx +1089 -0
- package/examples/game/app/components/Room.tsx +158 -0
- package/examples/game/party/server.ts +3 -2
- package/examples/game/party/shard.ts +5 -0
- package/examples/game/partykit.json +5 -1
- package/package.json +2 -2
- package/readme.md +234 -2
- package/src/decorators.ts +34 -2
- package/src/index.ts +5 -1
- package/src/interfaces.ts +13 -0
- package/src/jwt.ts +217 -0
- package/src/mock.ts +39 -3
- package/src/request/cors.ts +54 -0
- package/src/request/response.ts +228 -0
- package/src/server.ts +588 -86
- package/src/shard.ts +244 -0
- package/src/testing.ts +47 -6
- package/src/utils.ts +7 -0
- package/src/world.guard.ts +28 -0
- package/src/world.ts +448 -0
- package/examples/game/app/components/Counter.tsx +0 -82
package/dist/index.js
CHANGED
|
@@ -14,6 +14,23 @@ function Action(name, bodyValidation) {
|
|
|
14
14
|
};
|
|
15
15
|
}
|
|
16
16
|
__name(Action, "Action");
|
|
17
|
+
function Request2(options, bodyValidation) {
|
|
18
|
+
return function(target, propertyKey) {
|
|
19
|
+
if (!target.constructor._requestMetadata) {
|
|
20
|
+
target.constructor._requestMetadata = /* @__PURE__ */ new Map();
|
|
21
|
+
}
|
|
22
|
+
const path = options.path.startsWith("/") ? options.path : `/${options.path}`;
|
|
23
|
+
const method = options.method || "GET";
|
|
24
|
+
const routeKey = `${method}:${path}`;
|
|
25
|
+
target.constructor._requestMetadata.set(routeKey, {
|
|
26
|
+
key: propertyKey,
|
|
27
|
+
path,
|
|
28
|
+
method,
|
|
29
|
+
bodyValidation
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
__name(Request2, "Request");
|
|
17
34
|
function Room(options) {
|
|
18
35
|
return function(target) {
|
|
19
36
|
target.path = options.path;
|
|
@@ -108,6 +125,35 @@ var MockPartyClient = class {
|
|
|
108
125
|
return this.server.onMessage(JSON.stringify(data), this.conn);
|
|
109
126
|
}
|
|
110
127
|
};
|
|
128
|
+
var MockLobby = class MockLobby2 {
|
|
129
|
+
static {
|
|
130
|
+
__name(this, "MockLobby");
|
|
131
|
+
}
|
|
132
|
+
server;
|
|
133
|
+
constructor(server) {
|
|
134
|
+
this.server = server;
|
|
135
|
+
}
|
|
136
|
+
socket() {
|
|
137
|
+
return new MockPartyClient(this.server);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
var MockContext = class MockContext2 {
|
|
141
|
+
static {
|
|
142
|
+
__name(this, "MockContext");
|
|
143
|
+
}
|
|
144
|
+
room;
|
|
145
|
+
parties;
|
|
146
|
+
constructor(room, options = {}) {
|
|
147
|
+
this.room = room;
|
|
148
|
+
this.parties = {
|
|
149
|
+
main: /* @__PURE__ */ new Map()
|
|
150
|
+
};
|
|
151
|
+
const parties = options.parties || {};
|
|
152
|
+
for (let lobbyId in parties) {
|
|
153
|
+
this.parties.main.set(lobbyId, new MockLobby(parties[lobbyId](room)));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
};
|
|
111
157
|
var MockPartyRoom = class MockPartyRoom2 {
|
|
112
158
|
static {
|
|
113
159
|
__name(this, "MockPartyRoom");
|
|
@@ -115,18 +161,30 @@ var MockPartyRoom = class MockPartyRoom2 {
|
|
|
115
161
|
id;
|
|
116
162
|
clients;
|
|
117
163
|
storage;
|
|
164
|
+
context;
|
|
118
165
|
env;
|
|
119
|
-
constructor(
|
|
120
|
-
this.id =
|
|
166
|
+
constructor(id2, options = {}) {
|
|
167
|
+
this.id = id2;
|
|
121
168
|
this.clients = /* @__PURE__ */ new Map();
|
|
122
169
|
this.storage = new Storage();
|
|
123
170
|
this.env = {};
|
|
124
|
-
this.id =
|
|
171
|
+
this.id = id2 || generateShortUUID();
|
|
172
|
+
this.context = new MockContext(this, {
|
|
173
|
+
parties: options.parties || {}
|
|
174
|
+
});
|
|
175
|
+
this.env = options.env || {};
|
|
125
176
|
}
|
|
126
177
|
async connection(server) {
|
|
127
178
|
const socket = new MockPartyClient(server);
|
|
179
|
+
const url = new URL("http://localhost");
|
|
180
|
+
const request2 = new Request(url.toString(), {
|
|
181
|
+
method: "GET",
|
|
182
|
+
headers: {
|
|
183
|
+
"Content-Type": "application/json"
|
|
184
|
+
}
|
|
185
|
+
});
|
|
128
186
|
await server.onConnect(socket.conn, {
|
|
129
|
-
request:
|
|
187
|
+
request: request2
|
|
130
188
|
});
|
|
131
189
|
this.clients.set(socket.id, socket);
|
|
132
190
|
return socket;
|
|
@@ -136,11 +194,11 @@ var MockPartyRoom = class MockPartyRoom2 {
|
|
|
136
194
|
client._trigger("message", data);
|
|
137
195
|
});
|
|
138
196
|
}
|
|
139
|
-
getConnection(
|
|
140
|
-
return this.clients.get(
|
|
197
|
+
getConnection(id2) {
|
|
198
|
+
return this.clients.get(id2);
|
|
141
199
|
}
|
|
142
200
|
getConnections() {
|
|
143
|
-
return this.clients;
|
|
201
|
+
return Array.from(this.clients.values()).map((client) => client.conn);
|
|
144
202
|
}
|
|
145
203
|
clear() {
|
|
146
204
|
this.clients.clear();
|
|
@@ -258,6 +316,266 @@ function buildObject(valuesMap, allMemory) {
|
|
|
258
316
|
return memoryObj;
|
|
259
317
|
}
|
|
260
318
|
__name(buildObject, "buildObject");
|
|
319
|
+
function response(status, body) {
|
|
320
|
+
return new Response(JSON.stringify(body), {
|
|
321
|
+
status,
|
|
322
|
+
headers: {
|
|
323
|
+
"Content-Type": "application/json"
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
__name(response, "response");
|
|
328
|
+
|
|
329
|
+
// src/request/response.ts
|
|
330
|
+
var ServerResponse = class {
|
|
331
|
+
static {
|
|
332
|
+
__name(this, "ServerResponse");
|
|
333
|
+
}
|
|
334
|
+
interceptors;
|
|
335
|
+
statusCode = 200;
|
|
336
|
+
responseBody = {};
|
|
337
|
+
responseHeaders = {
|
|
338
|
+
"Content-Type": "application/json"
|
|
339
|
+
};
|
|
340
|
+
/**
|
|
341
|
+
* Creates a new ServerResponse instance
|
|
342
|
+
* @param interceptors Array of interceptor functions that can modify the response
|
|
343
|
+
*/
|
|
344
|
+
constructor(interceptors = []) {
|
|
345
|
+
this.interceptors = interceptors;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Sets the status code for the response
|
|
349
|
+
* @param code HTTP status code
|
|
350
|
+
* @returns this instance for chaining
|
|
351
|
+
*/
|
|
352
|
+
status(code) {
|
|
353
|
+
this.statusCode = code;
|
|
354
|
+
return this;
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Sets the response body and returns this instance (chainable method)
|
|
358
|
+
*
|
|
359
|
+
* @param body Response body
|
|
360
|
+
* @returns this instance for chaining
|
|
361
|
+
*/
|
|
362
|
+
body(body) {
|
|
363
|
+
this.responseBody = body;
|
|
364
|
+
return this;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Adds a header to the response
|
|
368
|
+
* @param name Header name
|
|
369
|
+
* @param value Header value
|
|
370
|
+
* @returns this instance for chaining
|
|
371
|
+
*/
|
|
372
|
+
header(name, value) {
|
|
373
|
+
this.responseHeaders[name] = value;
|
|
374
|
+
return this;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Adds multiple headers to the response
|
|
378
|
+
* @param headers Object containing headers
|
|
379
|
+
* @returns this instance for chaining
|
|
380
|
+
*/
|
|
381
|
+
setHeaders(headers) {
|
|
382
|
+
this.responseHeaders = {
|
|
383
|
+
...this.responseHeaders,
|
|
384
|
+
...headers
|
|
385
|
+
};
|
|
386
|
+
return this;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Add an interceptor to the chain
|
|
390
|
+
* @param interceptor Function that takes a Response and returns a modified Response
|
|
391
|
+
* @returns this instance for chaining
|
|
392
|
+
*/
|
|
393
|
+
use(interceptor) {
|
|
394
|
+
this.interceptors.push(interceptor);
|
|
395
|
+
return this;
|
|
396
|
+
}
|
|
397
|
+
/**
|
|
398
|
+
* Builds and returns the Response object after applying all interceptors
|
|
399
|
+
* @returns Promise<Response> The final Response object
|
|
400
|
+
* @private Internal method used by terminal methods
|
|
401
|
+
*/
|
|
402
|
+
async buildResponse() {
|
|
403
|
+
let response2 = new Response(JSON.stringify(this.responseBody), {
|
|
404
|
+
status: this.statusCode,
|
|
405
|
+
headers: this.responseHeaders
|
|
406
|
+
});
|
|
407
|
+
for (const interceptor of this.interceptors) {
|
|
408
|
+
try {
|
|
409
|
+
const interceptedResponse = interceptor(response2);
|
|
410
|
+
if (interceptedResponse instanceof Promise) {
|
|
411
|
+
response2 = await interceptedResponse;
|
|
412
|
+
} else {
|
|
413
|
+
response2 = interceptedResponse;
|
|
414
|
+
}
|
|
415
|
+
} catch (error) {
|
|
416
|
+
console.error("Error in interceptor:", error);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return response2;
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Sets the response body to the JSON-stringified version of the provided value
|
|
423
|
+
* and sends the response (terminal method)
|
|
424
|
+
*
|
|
425
|
+
* @param body Response body to be JSON stringified
|
|
426
|
+
* @returns Promise<Response> The final Response object
|
|
427
|
+
*/
|
|
428
|
+
async json(body) {
|
|
429
|
+
this.responseBody = body;
|
|
430
|
+
this.responseHeaders["Content-Type"] = "application/json";
|
|
431
|
+
return this.buildResponse();
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Sends the response with the current configuration (terminal method)
|
|
435
|
+
*
|
|
436
|
+
* @param body Optional body to set before sending
|
|
437
|
+
* @returns Promise<Response> The final Response object
|
|
438
|
+
*/
|
|
439
|
+
async send(body) {
|
|
440
|
+
if (body !== void 0) {
|
|
441
|
+
this.responseBody = body;
|
|
442
|
+
}
|
|
443
|
+
return this.buildResponse();
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Sends a plain text response (terminal method)
|
|
447
|
+
*
|
|
448
|
+
* @param text Text to send
|
|
449
|
+
* @returns Promise<Response> The final Response object
|
|
450
|
+
*/
|
|
451
|
+
async text(text) {
|
|
452
|
+
this.responseBody = text;
|
|
453
|
+
this.responseHeaders["Content-Type"] = "text/plain";
|
|
454
|
+
let response2 = new Response(text, {
|
|
455
|
+
status: this.statusCode,
|
|
456
|
+
headers: this.responseHeaders
|
|
457
|
+
});
|
|
458
|
+
for (const interceptor of this.interceptors) {
|
|
459
|
+
try {
|
|
460
|
+
const interceptedResponse = interceptor(response2);
|
|
461
|
+
if (interceptedResponse instanceof Promise) {
|
|
462
|
+
response2 = await interceptedResponse;
|
|
463
|
+
} else {
|
|
464
|
+
response2 = interceptedResponse;
|
|
465
|
+
}
|
|
466
|
+
} catch (error) {
|
|
467
|
+
console.error("Error in interceptor:", error);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
return response2;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Redirects to the specified URL (terminal method)
|
|
474
|
+
*
|
|
475
|
+
* @param url URL to redirect to
|
|
476
|
+
* @param statusCode HTTP status code (default: 302)
|
|
477
|
+
* @returns Promise<Response> The final Response object
|
|
478
|
+
*/
|
|
479
|
+
async redirect(url, statusCode = 302) {
|
|
480
|
+
this.statusCode = statusCode;
|
|
481
|
+
this.responseHeaders["Location"] = url;
|
|
482
|
+
return this.buildResponse();
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Creates a success response with status 200
|
|
486
|
+
* @param body Response body
|
|
487
|
+
* @returns Promise<Response> The final Response object
|
|
488
|
+
*/
|
|
489
|
+
async success(body = {}) {
|
|
490
|
+
return this.status(200).json(body);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Creates an error response with status 400
|
|
494
|
+
* @param message Error message
|
|
495
|
+
* @param details Additional error details
|
|
496
|
+
* @returns Promise<Response> The final Response object
|
|
497
|
+
*/
|
|
498
|
+
async badRequest(message, details = {}) {
|
|
499
|
+
return this.status(400).json({
|
|
500
|
+
error: message,
|
|
501
|
+
...details
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Creates an error response with status 403
|
|
506
|
+
* @param message Error message
|
|
507
|
+
* @returns Promise<Response> The final Response object
|
|
508
|
+
*/
|
|
509
|
+
async notPermitted(message = "Not permitted") {
|
|
510
|
+
return this.status(403).json({
|
|
511
|
+
error: message
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Creates an error response with status 401
|
|
516
|
+
* @param message Error message
|
|
517
|
+
* @returns Promise<Response> The final Response object
|
|
518
|
+
*/
|
|
519
|
+
async unauthorized(message = "Unauthorized") {
|
|
520
|
+
return this.status(401).json({
|
|
521
|
+
error: message
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Creates an error response with status 404
|
|
526
|
+
* @param message Error message
|
|
527
|
+
* @returns Promise<Response> The final Response object
|
|
528
|
+
*/
|
|
529
|
+
async notFound(message = "Not found") {
|
|
530
|
+
return this.status(404).json({
|
|
531
|
+
error: message
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Creates an error response with status 500
|
|
536
|
+
* @param message Error message
|
|
537
|
+
* @returns Promise<Response> The final Response object
|
|
538
|
+
*/
|
|
539
|
+
async serverError(message = "Internal Server Error") {
|
|
540
|
+
return this.status(500).json({
|
|
541
|
+
error: message
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
// src/request/cors.ts
|
|
547
|
+
function cors(res, options = {}) {
|
|
548
|
+
const newHeaders = new Headers(res.headers);
|
|
549
|
+
newHeaders.set("Access-Control-Allow-Origin", options.origin || "*");
|
|
550
|
+
if (options.credentials) {
|
|
551
|
+
newHeaders.set("Access-Control-Allow-Credentials", "true");
|
|
552
|
+
}
|
|
553
|
+
if (options.exposedHeaders && options.exposedHeaders.length) {
|
|
554
|
+
newHeaders.set("Access-Control-Expose-Headers", options.exposedHeaders.join(", "));
|
|
555
|
+
}
|
|
556
|
+
if (options.methods && options.methods.length) {
|
|
557
|
+
newHeaders.set("Access-Control-Allow-Methods", options.methods.join(", "));
|
|
558
|
+
} else {
|
|
559
|
+
newHeaders.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
|
|
560
|
+
}
|
|
561
|
+
if (options.allowedHeaders && options.allowedHeaders.length) {
|
|
562
|
+
newHeaders.set("Access-Control-Allow-Headers", options.allowedHeaders.join(", "));
|
|
563
|
+
} else {
|
|
564
|
+
newHeaders.set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
|
|
565
|
+
}
|
|
566
|
+
if (options.maxAge) {
|
|
567
|
+
newHeaders.set("Access-Control-Max-Age", options.maxAge.toString());
|
|
568
|
+
}
|
|
569
|
+
return new Response(res.body, {
|
|
570
|
+
status: res.status,
|
|
571
|
+
headers: newHeaders
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
__name(cors, "cors");
|
|
575
|
+
function createCorsInterceptor(options = {}) {
|
|
576
|
+
return (res) => cors(res, options);
|
|
577
|
+
}
|
|
578
|
+
__name(createCorsInterceptor, "createCorsInterceptor");
|
|
261
579
|
|
|
262
580
|
// src/server.ts
|
|
263
581
|
var Message = z.object({
|
|
@@ -302,6 +620,22 @@ var Server = class {
|
|
|
302
620
|
get roomStorage() {
|
|
303
621
|
return this.room.storage;
|
|
304
622
|
}
|
|
623
|
+
async send(conn, obj, subRoom) {
|
|
624
|
+
obj = structuredClone(obj);
|
|
625
|
+
if (subRoom.interceptorPacket) {
|
|
626
|
+
const signal2 = this.getUsersProperty(subRoom);
|
|
627
|
+
const { publicId } = conn.state;
|
|
628
|
+
const user = signal2?.()[publicId];
|
|
629
|
+
obj = await awaitReturn(subRoom["interceptorPacket"]?.(user, obj, conn));
|
|
630
|
+
if (obj === null) return;
|
|
631
|
+
}
|
|
632
|
+
conn.send(JSON.stringify(obj));
|
|
633
|
+
}
|
|
634
|
+
broadcast(obj, subRoom) {
|
|
635
|
+
for (let conn of this.room.getConnections()) {
|
|
636
|
+
this.send(conn, obj, subRoom);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
305
639
|
/**
|
|
306
640
|
* @method onStart
|
|
307
641
|
* @async
|
|
@@ -415,10 +749,10 @@ var Server = class {
|
|
|
415
749
|
return;
|
|
416
750
|
}
|
|
417
751
|
const packet = buildObject(values, instance.$memoryAll);
|
|
418
|
-
this.
|
|
752
|
+
this.broadcast({
|
|
419
753
|
type: "sync",
|
|
420
754
|
value: packet
|
|
421
|
-
})
|
|
755
|
+
}, instance);
|
|
422
756
|
values.clear();
|
|
423
757
|
}, "syncCb");
|
|
424
758
|
const persistCb = /* @__PURE__ */ __name(async (values) => {
|
|
@@ -526,23 +860,7 @@ var Server = class {
|
|
|
526
860
|
async deleteSession(privateId) {
|
|
527
861
|
await this.room.storage.delete(`session:${privateId}`);
|
|
528
862
|
}
|
|
529
|
-
|
|
530
|
-
* @method onConnect
|
|
531
|
-
* @async
|
|
532
|
-
* @param {Party.Connection} conn - The connection object for the new user.
|
|
533
|
-
* @param {Party.ConnectionContext} ctx - The context of the connection.
|
|
534
|
-
* @description Handles a new user connection, creates a user object, and sends initial sync data.
|
|
535
|
-
* @returns {Promise<void>}
|
|
536
|
-
*
|
|
537
|
-
* @example
|
|
538
|
-
* ```typescript
|
|
539
|
-
* server.onConnect = async (conn, ctx) => {
|
|
540
|
-
* await server.onConnect(conn, ctx);
|
|
541
|
-
* console.log("New user connected:", conn.id);
|
|
542
|
-
* };
|
|
543
|
-
* ```
|
|
544
|
-
*/
|
|
545
|
-
async onConnect(conn, ctx) {
|
|
863
|
+
async onConnectClient(conn, ctx) {
|
|
546
864
|
const subRoom = await this.getSubRoom({
|
|
547
865
|
getMemoryAll: true
|
|
548
866
|
});
|
|
@@ -565,13 +883,13 @@ var Server = class {
|
|
|
565
883
|
const existingSession = await this.getSession(conn.id);
|
|
566
884
|
const publicId = existingSession?.publicId || generateShortUUID2();
|
|
567
885
|
let user = null;
|
|
568
|
-
const
|
|
886
|
+
const signal2 = this.getUsersProperty(subRoom);
|
|
569
887
|
const usersPropName = this.getUsersPropName(subRoom);
|
|
570
|
-
if (
|
|
571
|
-
const { classType } =
|
|
888
|
+
if (signal2) {
|
|
889
|
+
const { classType } = signal2.options;
|
|
572
890
|
if (!existingSession?.publicId) {
|
|
573
891
|
user = isClass(classType) ? new classType() : classType(conn, ctx);
|
|
574
|
-
|
|
892
|
+
signal2()[publicId] = user;
|
|
575
893
|
const snapshot = createStatesSnapshot(user);
|
|
576
894
|
this.room.storage.put(`${usersPropName}.${publicId}`, snapshot);
|
|
577
895
|
}
|
|
@@ -585,33 +903,70 @@ var Server = class {
|
|
|
585
903
|
}
|
|
586
904
|
await awaitReturn(subRoom["onJoin"]?.(user, conn, ctx));
|
|
587
905
|
conn.setState({
|
|
906
|
+
...conn.state,
|
|
588
907
|
publicId
|
|
589
908
|
});
|
|
590
|
-
|
|
909
|
+
this.send(conn, {
|
|
591
910
|
type: "sync",
|
|
592
911
|
value: {
|
|
593
912
|
pId: publicId,
|
|
594
913
|
...subRoom.$memoryAll
|
|
595
914
|
}
|
|
596
|
-
})
|
|
915
|
+
}, subRoom);
|
|
597
916
|
}
|
|
598
917
|
/**
|
|
599
|
-
* @method
|
|
918
|
+
* @method onConnect
|
|
600
919
|
* @async
|
|
601
|
-
* @param {
|
|
602
|
-
* @param {Party.
|
|
603
|
-
* @description
|
|
920
|
+
* @param {Party.Connection} conn - The connection object for the new user.
|
|
921
|
+
* @param {Party.ConnectionContext} ctx - The context of the connection.
|
|
922
|
+
* @description Handles a new user connection, creates a user object, and sends initial sync data.
|
|
604
923
|
* @returns {Promise<void>}
|
|
605
924
|
*
|
|
606
925
|
* @example
|
|
607
926
|
* ```typescript
|
|
608
|
-
* server.
|
|
609
|
-
* await server.
|
|
610
|
-
* console.log("
|
|
927
|
+
* server.onConnect = async (conn, ctx) => {
|
|
928
|
+
* await server.onConnect(conn, ctx);
|
|
929
|
+
* console.log("New user connected:", conn.id);
|
|
611
930
|
* };
|
|
612
931
|
* ```
|
|
613
932
|
*/
|
|
933
|
+
async onConnect(conn, ctx) {
|
|
934
|
+
if (ctx.request?.headers.has("x-shard-id")) {
|
|
935
|
+
this.onConnectShard(conn, ctx);
|
|
936
|
+
} else {
|
|
937
|
+
await this.onConnectClient(conn, ctx);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* @method onConnectShard
|
|
942
|
+
* @private
|
|
943
|
+
* @param {Party.Connection} conn - The connection object for the new shard.
|
|
944
|
+
* @param {Party.ConnectionContext} ctx - The context of the shard connection.
|
|
945
|
+
* @description Handles a new shard connection, setting up the necessary state.
|
|
946
|
+
* @returns {void}
|
|
947
|
+
*/
|
|
948
|
+
onConnectShard(conn, ctx) {
|
|
949
|
+
const shardId = ctx.request?.headers.get("x-shard-id") || "unknown-shard";
|
|
950
|
+
conn.setState({
|
|
951
|
+
shard: true,
|
|
952
|
+
shardId,
|
|
953
|
+
clients: /* @__PURE__ */ new Map()
|
|
954
|
+
// Track clients connected through this shard
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* @method onMessage
|
|
959
|
+
* @async
|
|
960
|
+
* @param {string} message - The message received from a user or shard.
|
|
961
|
+
* @param {Party.Connection} sender - The connection object of the sender.
|
|
962
|
+
* @description Processes incoming messages, handling differently based on if sender is shard or client.
|
|
963
|
+
* @returns {Promise<void>}
|
|
964
|
+
*/
|
|
614
965
|
async onMessage(message, sender) {
|
|
966
|
+
if (sender.state && sender.state.shard) {
|
|
967
|
+
await this.handleShardMessage(message, sender);
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
615
970
|
let json;
|
|
616
971
|
try {
|
|
617
972
|
json = JSON.parse(message);
|
|
@@ -625,16 +980,16 @@ var Server = class {
|
|
|
625
980
|
const subRoom = await this.getSubRoom();
|
|
626
981
|
const roomGuards = subRoom.constructor["_roomGuards"] || [];
|
|
627
982
|
for (const guard of roomGuards) {
|
|
628
|
-
const isAuthorized = await guard(sender, result.data.value);
|
|
983
|
+
const isAuthorized = await guard(sender, result.data.value, this.room);
|
|
629
984
|
if (!isAuthorized) {
|
|
630
985
|
return;
|
|
631
986
|
}
|
|
632
987
|
}
|
|
633
988
|
const actions = subRoom.constructor["_actionMetadata"];
|
|
634
989
|
if (actions) {
|
|
635
|
-
const
|
|
990
|
+
const signal2 = this.getUsersProperty(subRoom);
|
|
636
991
|
const { publicId } = sender.state;
|
|
637
|
-
const user =
|
|
992
|
+
const user = signal2?.()[publicId];
|
|
638
993
|
const actionName = actions.get(result.data.action);
|
|
639
994
|
if (actionName) {
|
|
640
995
|
const guards = subRoom.constructor["_actionGuards"]?.get(actionName.key) || [];
|
|
@@ -655,6 +1010,173 @@ var Server = class {
|
|
|
655
1010
|
}
|
|
656
1011
|
}
|
|
657
1012
|
/**
|
|
1013
|
+
* @method handleShardMessage
|
|
1014
|
+
* @private
|
|
1015
|
+
* @async
|
|
1016
|
+
* @param {string} message - The message received from a shard.
|
|
1017
|
+
* @param {Party.Connection} shardConnection - The connection object of the shard.
|
|
1018
|
+
* @description Processes messages from shards, extracting client information.
|
|
1019
|
+
* @returns {Promise<void>}
|
|
1020
|
+
*/
|
|
1021
|
+
async handleShardMessage(message, shardConnection) {
|
|
1022
|
+
let parsedMessage;
|
|
1023
|
+
try {
|
|
1024
|
+
parsedMessage = JSON.parse(message);
|
|
1025
|
+
} catch (e) {
|
|
1026
|
+
console.error("Error parsing shard message:", e);
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
const shardState = shardConnection.state;
|
|
1030
|
+
const clients = shardState.clients;
|
|
1031
|
+
switch (parsedMessage.type) {
|
|
1032
|
+
case "shard.clientConnected":
|
|
1033
|
+
await this.handleShardClientConnect(parsedMessage, shardConnection);
|
|
1034
|
+
break;
|
|
1035
|
+
case "shard.clientMessage":
|
|
1036
|
+
await this.handleShardClientMessage(parsedMessage, shardConnection);
|
|
1037
|
+
break;
|
|
1038
|
+
case "shard.clientDisconnected":
|
|
1039
|
+
await this.handleShardClientDisconnect(parsedMessage, shardConnection);
|
|
1040
|
+
break;
|
|
1041
|
+
default:
|
|
1042
|
+
console.warn(`Unknown shard message type: ${parsedMessage.type}`);
|
|
1043
|
+
}
|
|
1044
|
+
}
|
|
1045
|
+
/**
|
|
1046
|
+
* @method handleShardClientConnect
|
|
1047
|
+
* @private
|
|
1048
|
+
* @async
|
|
1049
|
+
* @param {Object} message - The client connection message from a shard.
|
|
1050
|
+
* @param {Party.Connection} shardConnection - The connection object of the shard.
|
|
1051
|
+
* @description Handles a new client connection via a shard.
|
|
1052
|
+
* @returns {Promise<void>}
|
|
1053
|
+
*/
|
|
1054
|
+
async handleShardClientConnect(message, shardConnection) {
|
|
1055
|
+
const { privateId, connectionInfo } = message;
|
|
1056
|
+
const shardState = shardConnection.state;
|
|
1057
|
+
const virtualContext = {
|
|
1058
|
+
request: {
|
|
1059
|
+
headers: new Headers({
|
|
1060
|
+
"x-forwarded-for": connectionInfo.ip,
|
|
1061
|
+
"user-agent": connectionInfo.userAgent
|
|
1062
|
+
}),
|
|
1063
|
+
method: "GET",
|
|
1064
|
+
url: ""
|
|
1065
|
+
}
|
|
1066
|
+
};
|
|
1067
|
+
const virtualConnection = {
|
|
1068
|
+
id: privateId,
|
|
1069
|
+
send: /* @__PURE__ */ __name((data) => {
|
|
1070
|
+
shardConnection.send(JSON.stringify({
|
|
1071
|
+
targetClientId: privateId,
|
|
1072
|
+
data
|
|
1073
|
+
}));
|
|
1074
|
+
}, "send"),
|
|
1075
|
+
state: {},
|
|
1076
|
+
setState: /* @__PURE__ */ __name((state) => {
|
|
1077
|
+
const clients = shardState.clients;
|
|
1078
|
+
const currentState = clients.get(privateId) || {};
|
|
1079
|
+
const mergedState = Object.assign({}, currentState, state);
|
|
1080
|
+
clients.set(privateId, mergedState);
|
|
1081
|
+
virtualConnection.state = clients.get(privateId);
|
|
1082
|
+
return virtualConnection.state;
|
|
1083
|
+
}, "setState"),
|
|
1084
|
+
close: /* @__PURE__ */ __name(() => {
|
|
1085
|
+
shardConnection.send(JSON.stringify({
|
|
1086
|
+
type: "shard.closeClient",
|
|
1087
|
+
privateId
|
|
1088
|
+
}));
|
|
1089
|
+
if (shardState.clients) {
|
|
1090
|
+
shardState.clients.delete(privateId);
|
|
1091
|
+
}
|
|
1092
|
+
}, "close")
|
|
1093
|
+
};
|
|
1094
|
+
if (!shardState.clients.has(privateId)) {
|
|
1095
|
+
shardState.clients.set(privateId, {});
|
|
1096
|
+
}
|
|
1097
|
+
await this.onConnectClient(virtualConnection, virtualContext);
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* @method handleShardClientMessage
|
|
1101
|
+
* @private
|
|
1102
|
+
* @async
|
|
1103
|
+
* @param {Object} message - The client message from a shard.
|
|
1104
|
+
* @param {Party.Connection} shardConnection - The connection object of the shard.
|
|
1105
|
+
* @description Handles a message from a client via a shard.
|
|
1106
|
+
* @returns {Promise<void>}
|
|
1107
|
+
*/
|
|
1108
|
+
async handleShardClientMessage(message, shardConnection) {
|
|
1109
|
+
const { privateId, publicId, payload } = message;
|
|
1110
|
+
const shardState = shardConnection.state;
|
|
1111
|
+
const clients = shardState.clients;
|
|
1112
|
+
if (!clients.has(privateId)) {
|
|
1113
|
+
console.warn(`Received message from unknown client ${privateId}, creating virtual connection`);
|
|
1114
|
+
clients.set(privateId, {
|
|
1115
|
+
publicId
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
const virtualConnection = {
|
|
1119
|
+
id: privateId,
|
|
1120
|
+
send: /* @__PURE__ */ __name((data) => {
|
|
1121
|
+
shardConnection.send(JSON.stringify({
|
|
1122
|
+
targetClientId: privateId,
|
|
1123
|
+
data
|
|
1124
|
+
}));
|
|
1125
|
+
}, "send"),
|
|
1126
|
+
state: clients.get(privateId),
|
|
1127
|
+
setState: /* @__PURE__ */ __name((state) => {
|
|
1128
|
+
const currentState = clients.get(privateId) || {};
|
|
1129
|
+
const mergedState = Object.assign({}, currentState, state);
|
|
1130
|
+
clients.set(privateId, mergedState);
|
|
1131
|
+
virtualConnection.state = clients.get(privateId);
|
|
1132
|
+
return virtualConnection.state;
|
|
1133
|
+
}, "setState"),
|
|
1134
|
+
close: /* @__PURE__ */ __name(() => {
|
|
1135
|
+
shardConnection.send(JSON.stringify({
|
|
1136
|
+
type: "shard.closeClient",
|
|
1137
|
+
privateId
|
|
1138
|
+
}));
|
|
1139
|
+
if (shardState.clients) {
|
|
1140
|
+
shardState.clients.delete(privateId);
|
|
1141
|
+
}
|
|
1142
|
+
}, "close")
|
|
1143
|
+
};
|
|
1144
|
+
const payloadString = typeof payload === "string" ? payload : JSON.stringify(payload);
|
|
1145
|
+
await this.onMessage(payloadString, virtualConnection);
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* @method handleShardClientDisconnect
|
|
1149
|
+
* @private
|
|
1150
|
+
* @async
|
|
1151
|
+
* @param {Object} message - The client disconnection message from a shard.
|
|
1152
|
+
* @param {Party.Connection} shardConnection - The connection object of the shard.
|
|
1153
|
+
* @description Handles a client disconnection via a shard.
|
|
1154
|
+
* @returns {Promise<void>}
|
|
1155
|
+
*/
|
|
1156
|
+
async handleShardClientDisconnect(message, shardConnection) {
|
|
1157
|
+
const { privateId, publicId } = message;
|
|
1158
|
+
const shardState = shardConnection.state;
|
|
1159
|
+
const clients = shardState.clients;
|
|
1160
|
+
const clientState = clients.get(privateId);
|
|
1161
|
+
if (!clientState) {
|
|
1162
|
+
console.warn(`Disconnection for unknown client ${privateId}`);
|
|
1163
|
+
return;
|
|
1164
|
+
}
|
|
1165
|
+
const virtualConnection = {
|
|
1166
|
+
id: privateId,
|
|
1167
|
+
send: /* @__PURE__ */ __name(() => {
|
|
1168
|
+
}, "send"),
|
|
1169
|
+
state: clientState,
|
|
1170
|
+
setState: /* @__PURE__ */ __name(() => {
|
|
1171
|
+
return {};
|
|
1172
|
+
}, "setState"),
|
|
1173
|
+
close: /* @__PURE__ */ __name(() => {
|
|
1174
|
+
}, "close")
|
|
1175
|
+
};
|
|
1176
|
+
await this.onClose(virtualConnection);
|
|
1177
|
+
clients.delete(privateId);
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
658
1180
|
* @method onClose
|
|
659
1181
|
* @async
|
|
660
1182
|
* @param {Party.Connection} conn - The connection object of the disconnecting user.
|
|
@@ -674,22 +1196,22 @@ var Server = class {
|
|
|
674
1196
|
if (!subRoom) {
|
|
675
1197
|
return;
|
|
676
1198
|
}
|
|
677
|
-
const
|
|
1199
|
+
const signal2 = this.getUsersProperty(subRoom);
|
|
678
1200
|
if (!conn.state) {
|
|
679
1201
|
return;
|
|
680
1202
|
}
|
|
681
1203
|
const privateId = conn.id;
|
|
682
1204
|
const { publicId } = conn.state;
|
|
683
|
-
const user =
|
|
1205
|
+
const user = signal2?.()[publicId];
|
|
684
1206
|
if (!user) return;
|
|
685
1207
|
await awaitReturn(subRoom["onLeave"]?.(user, conn));
|
|
686
1208
|
await this.updateSessionConnection(privateId, false);
|
|
687
|
-
this.
|
|
1209
|
+
this.broadcast({
|
|
688
1210
|
type: "user_disconnected",
|
|
689
1211
|
value: {
|
|
690
1212
|
publicId
|
|
691
1213
|
}
|
|
692
|
-
})
|
|
1214
|
+
}, subRoom);
|
|
693
1215
|
}
|
|
694
1216
|
async onAlarm() {
|
|
695
1217
|
const subRoom = await this.getSubRoom();
|
|
@@ -699,41 +1221,440 @@ var Server = class {
|
|
|
699
1221
|
const subRoom = await this.getSubRoom();
|
|
700
1222
|
await awaitReturn(subRoom["onError"]?.(connection, error));
|
|
701
1223
|
}
|
|
1224
|
+
/**
|
|
1225
|
+
* @method onRequest
|
|
1226
|
+
* @async
|
|
1227
|
+
* @param {Party.Request} req - The HTTP request to handle
|
|
1228
|
+
* @description Handles HTTP requests, either directly from clients or forwarded by shards
|
|
1229
|
+
* @returns {Promise<Response>} The response to return to the client
|
|
1230
|
+
*/
|
|
702
1231
|
async onRequest(req) {
|
|
1232
|
+
const isFromShard = req.headers.has("x-forwarded-by-shard");
|
|
1233
|
+
const shardId = req.headers.get("x-shard-id");
|
|
1234
|
+
const res = new ServerResponse([
|
|
1235
|
+
createCorsInterceptor()
|
|
1236
|
+
]);
|
|
1237
|
+
if (isFromShard) {
|
|
1238
|
+
return this.handleShardRequest(req, res, shardId);
|
|
1239
|
+
}
|
|
1240
|
+
return this.handleDirectRequest(req, res);
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* @method handleDirectRequest
|
|
1244
|
+
* @private
|
|
1245
|
+
* @async
|
|
1246
|
+
* @param {Party.Request} req - The HTTP request received directly from a client
|
|
1247
|
+
* @description Processes requests received directly from clients
|
|
1248
|
+
* @returns {Promise<Response>} The response to return to the client
|
|
1249
|
+
*/
|
|
1250
|
+
async handleDirectRequest(req, res) {
|
|
703
1251
|
const subRoom = await this.getSubRoom();
|
|
704
|
-
const res = /* @__PURE__ */ __name((body, status) => {
|
|
705
|
-
return new Response(JSON.stringify(body), {
|
|
706
|
-
status
|
|
707
|
-
});
|
|
708
|
-
}, "res");
|
|
709
1252
|
if (!subRoom) {
|
|
710
|
-
return res(
|
|
711
|
-
|
|
712
|
-
|
|
1253
|
+
return res.notFound();
|
|
1254
|
+
}
|
|
1255
|
+
const response2 = await this.tryMatchRequestHandler(req, res, subRoom);
|
|
1256
|
+
if (response2) {
|
|
1257
|
+
return response2;
|
|
713
1258
|
}
|
|
714
|
-
const
|
|
715
|
-
if (!
|
|
716
|
-
return res(
|
|
717
|
-
error: "Not found"
|
|
718
|
-
}, 404);
|
|
1259
|
+
const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(req, res));
|
|
1260
|
+
if (!legacyResponse) {
|
|
1261
|
+
return res.notFound();
|
|
719
1262
|
}
|
|
720
|
-
if (
|
|
721
|
-
return
|
|
1263
|
+
if (legacyResponse instanceof Response) {
|
|
1264
|
+
return legacyResponse;
|
|
1265
|
+
}
|
|
1266
|
+
return res.success(legacyResponse);
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* @method tryMatchRequestHandler
|
|
1270
|
+
* @private
|
|
1271
|
+
* @async
|
|
1272
|
+
* @param {Party.Request} req - The HTTP request to handle
|
|
1273
|
+
* @param {Object} subRoom - The room instance
|
|
1274
|
+
* @description Attempts to match the request to a registered @Request handler
|
|
1275
|
+
* @returns {Promise<Response | null>} The response or null if no handler matched
|
|
1276
|
+
*/
|
|
1277
|
+
async tryMatchRequestHandler(req, res, subRoom) {
|
|
1278
|
+
const requestHandlers = subRoom.constructor["_requestMetadata"];
|
|
1279
|
+
if (!requestHandlers) {
|
|
1280
|
+
return null;
|
|
722
1281
|
}
|
|
723
|
-
|
|
1282
|
+
const url = new URL(req.url);
|
|
1283
|
+
const method = req.method;
|
|
1284
|
+
let pathname = url.pathname;
|
|
1285
|
+
pathname = "/" + pathname.split("/").slice(4).join("/");
|
|
1286
|
+
for (const [routeKey, handler] of requestHandlers.entries()) {
|
|
1287
|
+
const firstColonIndex = routeKey.indexOf(":");
|
|
1288
|
+
const handlerMethod = routeKey.substring(0, firstColonIndex);
|
|
1289
|
+
const handlerPath = routeKey.substring(firstColonIndex + 1);
|
|
1290
|
+
if (handlerMethod !== method) {
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1293
|
+
if (this.pathMatches(pathname, handlerPath)) {
|
|
1294
|
+
const params = this.extractPathParams(pathname, handlerPath);
|
|
1295
|
+
const guards = subRoom.constructor["_actionGuards"]?.get(handler.key) || [];
|
|
1296
|
+
for (const guard of guards) {
|
|
1297
|
+
const isAuthorized = await guard(null, req, this.room);
|
|
1298
|
+
if (isAuthorized instanceof Response) {
|
|
1299
|
+
return isAuthorized;
|
|
1300
|
+
}
|
|
1301
|
+
if (!isAuthorized) {
|
|
1302
|
+
return res.notPermitted();
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
let bodyData = null;
|
|
1306
|
+
if (handler.bodyValidation && [
|
|
1307
|
+
"POST",
|
|
1308
|
+
"PUT",
|
|
1309
|
+
"PATCH"
|
|
1310
|
+
].includes(method)) {
|
|
1311
|
+
try {
|
|
1312
|
+
const contentType = req.headers.get("content-type") || "";
|
|
1313
|
+
if (contentType.includes("application/json")) {
|
|
1314
|
+
const body = await req.json();
|
|
1315
|
+
const validation = handler.bodyValidation.safeParse(body);
|
|
1316
|
+
if (!validation.success) {
|
|
1317
|
+
return res.badRequest("Invalid request body", {
|
|
1318
|
+
details: validation.error
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
bodyData = validation.data;
|
|
1322
|
+
}
|
|
1323
|
+
} catch (error) {
|
|
1324
|
+
return res.badRequest("Failed to parse request body");
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
try {
|
|
1328
|
+
req["data"] = bodyData;
|
|
1329
|
+
req["params"] = params;
|
|
1330
|
+
const result = await awaitReturn(subRoom[handler.key](req, res));
|
|
1331
|
+
if (result instanceof Response) {
|
|
1332
|
+
return result;
|
|
1333
|
+
}
|
|
1334
|
+
return res.success(result);
|
|
1335
|
+
} catch (error) {
|
|
1336
|
+
console.error("Error executing request handler:", error);
|
|
1337
|
+
return res.serverError();
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
return null;
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* @method pathMatches
|
|
1345
|
+
* @private
|
|
1346
|
+
* @param {string} requestPath - The path from the request
|
|
1347
|
+
* @param {string} handlerPath - The path pattern from the handler
|
|
1348
|
+
* @description Checks if a request path matches a handler path pattern
|
|
1349
|
+
* @returns {boolean} True if the paths match
|
|
1350
|
+
*/
|
|
1351
|
+
pathMatches(requestPath, handlerPath) {
|
|
1352
|
+
const pathRegexString = handlerPath.replace(/\//g, "\\/").replace(/:([^\/]+)/g, "([^/]+)");
|
|
1353
|
+
const pathRegex = new RegExp(`^${pathRegexString}$`);
|
|
1354
|
+
return pathRegex.test(requestPath);
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1357
|
+
* @method extractPathParams
|
|
1358
|
+
* @private
|
|
1359
|
+
* @param {string} requestPath - The path from the request
|
|
1360
|
+
* @param {string} handlerPath - The path pattern from the handler
|
|
1361
|
+
* @description Extracts path parameters from the request path based on the handler pattern
|
|
1362
|
+
* @returns {Object} An object containing the path parameters
|
|
1363
|
+
*/
|
|
1364
|
+
extractPathParams(requestPath, handlerPath) {
|
|
1365
|
+
const params = {};
|
|
1366
|
+
const paramNames = [];
|
|
1367
|
+
handlerPath.split("/").forEach((segment) => {
|
|
1368
|
+
if (segment.startsWith(":")) {
|
|
1369
|
+
paramNames.push(segment.substring(1));
|
|
1370
|
+
}
|
|
1371
|
+
});
|
|
1372
|
+
const pathRegexString = handlerPath.replace(/\//g, "\\/").replace(/:([^\/]+)/g, "([^/]+)");
|
|
1373
|
+
const pathRegex = new RegExp(`^${pathRegexString}$`);
|
|
1374
|
+
const matches = requestPath.match(pathRegex);
|
|
1375
|
+
if (matches && matches.length > 1) {
|
|
1376
|
+
for (let i = 0; i < paramNames.length; i++) {
|
|
1377
|
+
params[paramNames[i]] = matches[i + 1];
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
return params;
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* @method handleShardRequest
|
|
1384
|
+
* @private
|
|
1385
|
+
* @async
|
|
1386
|
+
* @param {Party.Request} req - The HTTP request forwarded by a shard
|
|
1387
|
+
* @param {string | null} shardId - The ID of the shard that forwarded the request
|
|
1388
|
+
* @description Processes requests forwarded by shards, preserving client context
|
|
1389
|
+
* @returns {Promise<Response>} The response to return to the shard (which will forward it to the client)
|
|
1390
|
+
*/
|
|
1391
|
+
async handleShardRequest(req, res, shardId) {
|
|
1392
|
+
const subRoom = await this.getSubRoom();
|
|
1393
|
+
if (!subRoom) {
|
|
1394
|
+
return res.notFound();
|
|
1395
|
+
}
|
|
1396
|
+
const originalClientIp = req.headers.get("x-original-client-ip");
|
|
1397
|
+
const enhancedReq = this.createEnhancedRequest(req, originalClientIp);
|
|
1398
|
+
try {
|
|
1399
|
+
const response2 = await this.tryMatchRequestHandler(enhancedReq, res, subRoom);
|
|
1400
|
+
if (response2) {
|
|
1401
|
+
return response2;
|
|
1402
|
+
}
|
|
1403
|
+
const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(enhancedReq, res));
|
|
1404
|
+
if (!legacyResponse) {
|
|
1405
|
+
return res.notFound();
|
|
1406
|
+
}
|
|
1407
|
+
if (legacyResponse instanceof Response) {
|
|
1408
|
+
return legacyResponse;
|
|
1409
|
+
}
|
|
1410
|
+
return res.success(legacyResponse);
|
|
1411
|
+
} catch (error) {
|
|
1412
|
+
console.error(`Error processing request from shard ${shardId}:`, error);
|
|
1413
|
+
return res.serverError();
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* @method createEnhancedRequest
|
|
1418
|
+
* @private
|
|
1419
|
+
* @param {Party.Request} originalReq - The original request received from the shard
|
|
1420
|
+
* @param {string | null} originalClientIp - The original client IP, if available
|
|
1421
|
+
* @description Creates an enhanced request object that preserves the original client context
|
|
1422
|
+
* @returns {Party.Request} The enhanced request object
|
|
1423
|
+
*/
|
|
1424
|
+
createEnhancedRequest(originalReq, originalClientIp) {
|
|
1425
|
+
const clonedReq = originalReq.clone();
|
|
1426
|
+
clonedReq.viaShard = true;
|
|
1427
|
+
if (originalClientIp) {
|
|
1428
|
+
clonedReq.originalClientIp = originalClientIp;
|
|
1429
|
+
}
|
|
1430
|
+
return clonedReq;
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
|
|
1434
|
+
// src/shard.ts
|
|
1435
|
+
var Shard = class {
|
|
1436
|
+
static {
|
|
1437
|
+
__name(this, "Shard");
|
|
1438
|
+
}
|
|
1439
|
+
room;
|
|
1440
|
+
ws;
|
|
1441
|
+
connectionMap;
|
|
1442
|
+
mainServerStub;
|
|
1443
|
+
worldUrl;
|
|
1444
|
+
worldId;
|
|
1445
|
+
lastReportedConnections;
|
|
1446
|
+
statsInterval;
|
|
1447
|
+
statsIntervalId;
|
|
1448
|
+
constructor(room) {
|
|
1449
|
+
this.room = room;
|
|
1450
|
+
this.connectionMap = /* @__PURE__ */ new Map();
|
|
1451
|
+
this.worldUrl = null;
|
|
1452
|
+
this.worldId = "default";
|
|
1453
|
+
this.lastReportedConnections = 0;
|
|
1454
|
+
this.statsInterval = 3e4;
|
|
1455
|
+
this.statsIntervalId = null;
|
|
1456
|
+
}
|
|
1457
|
+
async onStart() {
|
|
1458
|
+
const roomId = this.room.id.split(":")[0];
|
|
1459
|
+
const roomStub = this.room.context.parties.main.get(roomId);
|
|
1460
|
+
if (!roomStub) {
|
|
1461
|
+
console.warn("No room room stub found in main party context");
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1464
|
+
this.mainServerStub = roomStub;
|
|
1465
|
+
this.ws = await roomStub.socket({
|
|
1466
|
+
headers: {
|
|
1467
|
+
"x-shard-id": this.room.id
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
this.ws.addEventListener("message", (event) => {
|
|
1471
|
+
try {
|
|
1472
|
+
const message = JSON.parse(event.data);
|
|
1473
|
+
if (message.targetClientId) {
|
|
1474
|
+
const clientConn = this.connectionMap.get(message.targetClientId);
|
|
1475
|
+
if (clientConn) {
|
|
1476
|
+
delete message.targetClientId;
|
|
1477
|
+
clientConn.send(message.data);
|
|
1478
|
+
}
|
|
1479
|
+
} else {
|
|
1480
|
+
this.room.broadcast(event.data);
|
|
1481
|
+
}
|
|
1482
|
+
} catch (error) {
|
|
1483
|
+
console.error("Error processing message from main server:", error);
|
|
1484
|
+
}
|
|
1485
|
+
});
|
|
1486
|
+
await this.updateWorldStats();
|
|
1487
|
+
this.startPeriodicStatsUpdates();
|
|
1488
|
+
}
|
|
1489
|
+
startPeriodicStatsUpdates() {
|
|
1490
|
+
if (!this.worldUrl) {
|
|
1491
|
+
return;
|
|
1492
|
+
}
|
|
1493
|
+
if (this.statsIntervalId) {
|
|
1494
|
+
clearInterval(this.statsIntervalId);
|
|
1495
|
+
}
|
|
1496
|
+
this.statsIntervalId = setInterval(() => {
|
|
1497
|
+
this.updateWorldStats().catch((error) => {
|
|
1498
|
+
console.error("Error in periodic stats update:", error);
|
|
1499
|
+
});
|
|
1500
|
+
}, this.statsInterval);
|
|
1501
|
+
}
|
|
1502
|
+
stopPeriodicStatsUpdates() {
|
|
1503
|
+
if (this.statsIntervalId) {
|
|
1504
|
+
clearInterval(this.statsIntervalId);
|
|
1505
|
+
this.statsIntervalId = null;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
onConnect(conn, ctx) {
|
|
1509
|
+
this.connectionMap.set(conn.id, conn);
|
|
1510
|
+
this.ws.send(JSON.stringify({
|
|
1511
|
+
type: "shard.clientConnected",
|
|
1512
|
+
privateId: conn.id,
|
|
1513
|
+
connectionInfo: {
|
|
1514
|
+
ip: ctx.request?.headers.get("x-forwarded-for") || "unknown",
|
|
1515
|
+
userAgent: ctx.request?.headers.get("user-agent") || "unknown"
|
|
1516
|
+
}
|
|
1517
|
+
}));
|
|
1518
|
+
this.updateWorldStats();
|
|
1519
|
+
}
|
|
1520
|
+
onMessage(message, sender) {
|
|
1521
|
+
try {
|
|
1522
|
+
const parsedMessage = typeof message === "string" ? JSON.parse(message) : message;
|
|
1523
|
+
const wrappedMessage = JSON.stringify({
|
|
1524
|
+
type: "shard.clientMessage",
|
|
1525
|
+
privateId: sender.id,
|
|
1526
|
+
publicId: sender.state?.publicId,
|
|
1527
|
+
payload: parsedMessage
|
|
1528
|
+
});
|
|
1529
|
+
this.ws.send(wrappedMessage);
|
|
1530
|
+
} catch (error) {
|
|
1531
|
+
console.error("Error forwarding message to main server:", error);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
onClose(conn) {
|
|
1535
|
+
this.connectionMap.delete(conn.id);
|
|
1536
|
+
this.ws.send(JSON.stringify({
|
|
1537
|
+
type: "shard.clientDisconnected",
|
|
1538
|
+
privateId: conn.id,
|
|
1539
|
+
publicId: conn.state?.publicId
|
|
1540
|
+
}));
|
|
1541
|
+
this.updateWorldStats();
|
|
1542
|
+
}
|
|
1543
|
+
async updateWorldStats() {
|
|
1544
|
+
const currentConnections = this.connectionMap.size;
|
|
1545
|
+
if (currentConnections === this.lastReportedConnections) {
|
|
1546
|
+
return true;
|
|
1547
|
+
}
|
|
1548
|
+
try {
|
|
1549
|
+
const worldRoom = this.room.context.parties.world.get("world-default");
|
|
1550
|
+
const response2 = await worldRoom.fetch("/update-shard", {
|
|
1551
|
+
method: "POST",
|
|
1552
|
+
headers: {
|
|
1553
|
+
"Content-Type": "application/json",
|
|
1554
|
+
"x-access-shard": this.room.env.SHARD_SECRET
|
|
1555
|
+
},
|
|
1556
|
+
body: JSON.stringify({
|
|
1557
|
+
shardId: this.room.id,
|
|
1558
|
+
connections: currentConnections
|
|
1559
|
+
})
|
|
1560
|
+
});
|
|
1561
|
+
if (!response2.ok) {
|
|
1562
|
+
const errorData = await response2.json().catch(() => ({
|
|
1563
|
+
error: "Unknown error"
|
|
1564
|
+
}));
|
|
1565
|
+
console.error(`Failed to update World stats: ${response2.status} - ${errorData.error || "Unknown error"}`);
|
|
1566
|
+
return false;
|
|
1567
|
+
}
|
|
1568
|
+
this.lastReportedConnections = currentConnections;
|
|
1569
|
+
return true;
|
|
1570
|
+
} catch (error) {
|
|
1571
|
+
console.error("Error updating World stats:", error);
|
|
1572
|
+
return false;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
/**
|
|
1576
|
+
* @method onRequest
|
|
1577
|
+
* @async
|
|
1578
|
+
* @param {Party.Request} req - The HTTP request to handle
|
|
1579
|
+
* @description Forwards HTTP requests to the main server, preserving client context
|
|
1580
|
+
* @returns {Promise<Response>} The response from the main server
|
|
1581
|
+
*/
|
|
1582
|
+
async onRequest(req) {
|
|
1583
|
+
if (!this.mainServerStub) {
|
|
1584
|
+
return response(503, {
|
|
1585
|
+
error: "Shard not connected to main server"
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
try {
|
|
1589
|
+
const url = new URL(req.url);
|
|
1590
|
+
const path = url.pathname;
|
|
1591
|
+
const method = req.method;
|
|
1592
|
+
let body = null;
|
|
1593
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
1594
|
+
body = await req.text();
|
|
1595
|
+
}
|
|
1596
|
+
const headers = new Headers();
|
|
1597
|
+
req.headers.forEach((value, key) => {
|
|
1598
|
+
headers.set(key, value);
|
|
1599
|
+
});
|
|
1600
|
+
headers.set("x-shard-id", this.room.id);
|
|
1601
|
+
headers.set("x-forwarded-by-shard", "true");
|
|
1602
|
+
const clientIp = req.headers.get("x-forwarded-for") || "unknown";
|
|
1603
|
+
if (clientIp) {
|
|
1604
|
+
headers.set("x-original-client-ip", clientIp);
|
|
1605
|
+
}
|
|
1606
|
+
const requestInit = {
|
|
1607
|
+
method,
|
|
1608
|
+
headers,
|
|
1609
|
+
body
|
|
1610
|
+
};
|
|
1611
|
+
const response2 = await this.mainServerStub.fetch(path, requestInit);
|
|
1612
|
+
return response2;
|
|
1613
|
+
} catch (error) {
|
|
1614
|
+
return response(500, {
|
|
1615
|
+
error: "Error forwarding request"
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
/**
|
|
1620
|
+
* @method onAlarm
|
|
1621
|
+
* @async
|
|
1622
|
+
* @description Executed periodically, used to perform maintenance tasks
|
|
1623
|
+
*/
|
|
1624
|
+
async onAlarm() {
|
|
1625
|
+
await this.updateWorldStats();
|
|
724
1626
|
}
|
|
725
1627
|
};
|
|
726
1628
|
|
|
727
1629
|
// src/testing.ts
|
|
728
|
-
async function testRoom(
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
1630
|
+
async function testRoom(Room3, options = {}) {
|
|
1631
|
+
const createServer = /* @__PURE__ */ __name((io2) => {
|
|
1632
|
+
const server2 = new Server(io2);
|
|
1633
|
+
server2.rooms = [
|
|
1634
|
+
Room3
|
|
1635
|
+
];
|
|
1636
|
+
return server2;
|
|
1637
|
+
}, "createServer");
|
|
1638
|
+
const isShard = options.shard || false;
|
|
1639
|
+
const io = new ServerIo(Room3.path, isShard ? {
|
|
1640
|
+
parties: {
|
|
1641
|
+
game: createServer
|
|
1642
|
+
},
|
|
1643
|
+
env: options.env
|
|
1644
|
+
} : {
|
|
1645
|
+
env: options.env
|
|
1646
|
+
});
|
|
1647
|
+
Room3.prototype.throttleSync = 0;
|
|
1648
|
+
Room3.prototype.throttleStorage = 0;
|
|
1649
|
+
Room3.prototype.options = options;
|
|
1650
|
+
let server;
|
|
1651
|
+
if (options.shard) {
|
|
1652
|
+
const shardServer = new Shard(io);
|
|
1653
|
+
shardServer.subRoom = null;
|
|
1654
|
+
server = shardServer;
|
|
1655
|
+
} else {
|
|
1656
|
+
server = await createServer(io);
|
|
1657
|
+
}
|
|
737
1658
|
await server.onStart();
|
|
738
1659
|
return {
|
|
739
1660
|
server,
|
|
@@ -745,15 +1666,682 @@ async function testRoom(Room2, options = {}) {
|
|
|
745
1666
|
};
|
|
746
1667
|
}
|
|
747
1668
|
__name(testRoom, "testRoom");
|
|
1669
|
+
async function request(room, path, options = {
|
|
1670
|
+
method: "GET"
|
|
1671
|
+
}) {
|
|
1672
|
+
const url = new URL("http://localhost" + path);
|
|
1673
|
+
const request1 = new Request(url.toString(), options);
|
|
1674
|
+
const response2 = await room.onRequest(request1);
|
|
1675
|
+
return response2;
|
|
1676
|
+
}
|
|
1677
|
+
__name(request, "request");
|
|
1678
|
+
|
|
1679
|
+
// src/world.ts
|
|
1680
|
+
import { signal } from "@signe/reactive";
|
|
1681
|
+
import { sync, id, persist } from "@signe/sync";
|
|
1682
|
+
import { z as z2 } from "zod";
|
|
1683
|
+
|
|
1684
|
+
// src/types/party.ts
|
|
1685
|
+
var party_exports = {};
|
|
1686
|
+
|
|
1687
|
+
// src/jwt.ts
|
|
1688
|
+
var JWTAuth = class {
|
|
1689
|
+
static {
|
|
1690
|
+
__name(this, "JWTAuth");
|
|
1691
|
+
}
|
|
1692
|
+
secret;
|
|
1693
|
+
encoder;
|
|
1694
|
+
decoder;
|
|
1695
|
+
/**
|
|
1696
|
+
* Constructor for the JWTAuth class
|
|
1697
|
+
* @param {string} secret - The secret key used for signing and verifying tokens
|
|
1698
|
+
*/
|
|
1699
|
+
constructor(secret) {
|
|
1700
|
+
if (!secret || typeof secret !== "string") {
|
|
1701
|
+
throw new Error("Secret is required and must be a string");
|
|
1702
|
+
}
|
|
1703
|
+
this.secret = secret;
|
|
1704
|
+
this.encoder = new TextEncoder();
|
|
1705
|
+
this.decoder = new TextDecoder();
|
|
1706
|
+
}
|
|
1707
|
+
/**
|
|
1708
|
+
* Convert the secret to a CryptoKey for HMAC operations
|
|
1709
|
+
* @returns {Promise<CryptoKey>} - The CryptoKey for HMAC operations
|
|
1710
|
+
*/
|
|
1711
|
+
async getSecretKey() {
|
|
1712
|
+
const keyData = this.encoder.encode(this.secret);
|
|
1713
|
+
return await crypto.subtle.importKey(
|
|
1714
|
+
"raw",
|
|
1715
|
+
keyData,
|
|
1716
|
+
{
|
|
1717
|
+
name: "HMAC",
|
|
1718
|
+
hash: {
|
|
1719
|
+
name: "SHA-256"
|
|
1720
|
+
}
|
|
1721
|
+
},
|
|
1722
|
+
false,
|
|
1723
|
+
[
|
|
1724
|
+
"sign",
|
|
1725
|
+
"verify"
|
|
1726
|
+
]
|
|
1727
|
+
// key usages
|
|
1728
|
+
);
|
|
1729
|
+
}
|
|
1730
|
+
/**
|
|
1731
|
+
* Base64Url encode a buffer
|
|
1732
|
+
* @param {ArrayBuffer} buffer - The buffer to encode
|
|
1733
|
+
* @returns {string} - The base64url encoded string
|
|
1734
|
+
*/
|
|
1735
|
+
base64UrlEncode(buffer) {
|
|
1736
|
+
const base64 = btoa(String.fromCharCode(...new Uint8Array(buffer)));
|
|
1737
|
+
return base64.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
1738
|
+
}
|
|
1739
|
+
/**
|
|
1740
|
+
* Base64Url decode a string
|
|
1741
|
+
* @param {string} base64Url - The base64url encoded string
|
|
1742
|
+
* @returns {ArrayBuffer} - The decoded buffer
|
|
1743
|
+
*/
|
|
1744
|
+
base64UrlDecode(base64Url) {
|
|
1745
|
+
const padding = "=".repeat((4 - base64Url.length % 4) % 4);
|
|
1746
|
+
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/") + padding;
|
|
1747
|
+
const rawData = atob(base64);
|
|
1748
|
+
const buffer = new Uint8Array(rawData.length);
|
|
1749
|
+
for (let i = 0; i < rawData.length; i++) {
|
|
1750
|
+
buffer[i] = rawData.charCodeAt(i);
|
|
1751
|
+
}
|
|
1752
|
+
return buffer.buffer;
|
|
1753
|
+
}
|
|
1754
|
+
/**
|
|
1755
|
+
* Sign a payload and create a JWT token
|
|
1756
|
+
* @param {JWTPayload} payload - The payload to include in the token
|
|
1757
|
+
* @param {JWTOptions} [options={}] - Options for the token
|
|
1758
|
+
* @param {string | number} [options.expiresIn='1h'] - Token expiration time
|
|
1759
|
+
* @returns {Promise<string>} - The JWT token
|
|
1760
|
+
*/
|
|
1761
|
+
async sign(payload, options = {}) {
|
|
1762
|
+
if (!payload || typeof payload !== "object") {
|
|
1763
|
+
throw new Error("Payload must be an object");
|
|
1764
|
+
}
|
|
1765
|
+
const expiresIn = options.expiresIn || "1h";
|
|
1766
|
+
let exp;
|
|
1767
|
+
if (typeof expiresIn === "number") {
|
|
1768
|
+
exp = Math.floor(Date.now() / 1e3) + expiresIn;
|
|
1769
|
+
} else if (typeof expiresIn === "string") {
|
|
1770
|
+
const match = expiresIn.match(/^(\d+)([smhd])$/);
|
|
1771
|
+
if (match) {
|
|
1772
|
+
const value = parseInt(match[1]);
|
|
1773
|
+
const unit = match[2];
|
|
1774
|
+
const seconds = {
|
|
1775
|
+
"s": value,
|
|
1776
|
+
"m": value * 60,
|
|
1777
|
+
"h": value * 60 * 60,
|
|
1778
|
+
"d": value * 60 * 60 * 24
|
|
1779
|
+
}[unit];
|
|
1780
|
+
exp = Math.floor(Date.now() / 1e3) + seconds;
|
|
1781
|
+
} else {
|
|
1782
|
+
throw new Error('Invalid expiresIn format. Use a number (seconds) or a string like "1h", "30m", etc.');
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
const fullPayload = {
|
|
1786
|
+
...payload,
|
|
1787
|
+
iat: Math.floor(Date.now() / 1e3),
|
|
1788
|
+
exp
|
|
1789
|
+
};
|
|
1790
|
+
const header = {
|
|
1791
|
+
alg: "HS256",
|
|
1792
|
+
typ: "JWT"
|
|
1793
|
+
};
|
|
1794
|
+
const encodedHeader = this.base64UrlEncode(this.encoder.encode(JSON.stringify(header)));
|
|
1795
|
+
const encodedPayload = this.base64UrlEncode(this.encoder.encode(JSON.stringify(fullPayload)));
|
|
1796
|
+
const signatureBase = `${encodedHeader}.${encodedPayload}`;
|
|
1797
|
+
const key = await this.getSecretKey();
|
|
1798
|
+
const signature = await crypto.subtle.sign({
|
|
1799
|
+
name: "HMAC"
|
|
1800
|
+
}, key, this.encoder.encode(signatureBase));
|
|
1801
|
+
const encodedSignature = this.base64UrlEncode(signature);
|
|
1802
|
+
return `${signatureBase}.${encodedSignature}`;
|
|
1803
|
+
}
|
|
1804
|
+
/**
|
|
1805
|
+
* Verify a JWT token and return the decoded payload
|
|
1806
|
+
* @param {string} token - The JWT token to verify
|
|
1807
|
+
* @returns {Promise<JWTPayload>} - The decoded payload if verification succeeds
|
|
1808
|
+
* @throws {Error} - If verification fails
|
|
1809
|
+
*/
|
|
1810
|
+
async verify(token) {
|
|
1811
|
+
if (!token || typeof token !== "string") {
|
|
1812
|
+
throw new Error("Token is required and must be a string");
|
|
1813
|
+
}
|
|
1814
|
+
const parts = token.split(".");
|
|
1815
|
+
if (parts.length !== 3) {
|
|
1816
|
+
throw new Error("Invalid token format");
|
|
1817
|
+
}
|
|
1818
|
+
const [encodedHeader, encodedPayload, encodedSignature] = parts;
|
|
1819
|
+
try {
|
|
1820
|
+
const header = JSON.parse(this.decoder.decode(this.base64UrlDecode(encodedHeader)));
|
|
1821
|
+
const payload = JSON.parse(this.decoder.decode(this.base64UrlDecode(encodedPayload)));
|
|
1822
|
+
if (header.alg !== "HS256") {
|
|
1823
|
+
throw new Error(`Unsupported algorithm: ${header.alg}`);
|
|
1824
|
+
}
|
|
1825
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
1826
|
+
if (payload.exp && payload.exp < now) {
|
|
1827
|
+
throw new Error("Token has expired");
|
|
1828
|
+
}
|
|
1829
|
+
const key = await this.getSecretKey();
|
|
1830
|
+
const signatureBase = `${encodedHeader}.${encodedPayload}`;
|
|
1831
|
+
const signature = this.base64UrlDecode(encodedSignature);
|
|
1832
|
+
const isValid = await crypto.subtle.verify({
|
|
1833
|
+
name: "HMAC"
|
|
1834
|
+
}, key, signature, this.encoder.encode(signatureBase));
|
|
1835
|
+
if (!isValid) {
|
|
1836
|
+
throw new Error("Invalid signature");
|
|
1837
|
+
}
|
|
1838
|
+
return payload;
|
|
1839
|
+
} catch (error) {
|
|
1840
|
+
if (error instanceof Error) {
|
|
1841
|
+
throw new Error(`Token verification failed: ${error.message}`);
|
|
1842
|
+
}
|
|
1843
|
+
throw new Error("Token verification failed: Unknown error");
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
};
|
|
1847
|
+
|
|
1848
|
+
// src/world.guard.ts
|
|
1849
|
+
var guardManageWorld = /* @__PURE__ */ __name(async (_, req, room) => {
|
|
1850
|
+
const tokenShard = req.headers.get("x-access-shard");
|
|
1851
|
+
if (tokenShard) {
|
|
1852
|
+
if (tokenShard !== room.env.SHARD_SECRET) {
|
|
1853
|
+
return false;
|
|
1854
|
+
}
|
|
1855
|
+
return true;
|
|
1856
|
+
}
|
|
1857
|
+
const url = new URL(req.url);
|
|
1858
|
+
const token = req.headers.get("Authorization") ?? url.searchParams.get("world-auth-token");
|
|
1859
|
+
if (!token) {
|
|
1860
|
+
return false;
|
|
1861
|
+
}
|
|
1862
|
+
const jwt = new JWTAuth(room.env.AUTH_JWT_SECRET);
|
|
1863
|
+
try {
|
|
1864
|
+
const payload = await jwt.verify(token);
|
|
1865
|
+
if (!payload) {
|
|
1866
|
+
return false;
|
|
1867
|
+
}
|
|
1868
|
+
} catch (error) {
|
|
1869
|
+
return false;
|
|
1870
|
+
}
|
|
1871
|
+
return true;
|
|
1872
|
+
}, "guardManageWorld");
|
|
1873
|
+
|
|
1874
|
+
// src/world.ts
|
|
1875
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
1876
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1877
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1878
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1879
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1880
|
+
}
|
|
1881
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
1882
|
+
function _ts_metadata(k, v) {
|
|
1883
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1884
|
+
}
|
|
1885
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
1886
|
+
var RoomConfigSchema = z2.object({
|
|
1887
|
+
name: z2.string(),
|
|
1888
|
+
balancingStrategy: z2.enum([
|
|
1889
|
+
"round-robin",
|
|
1890
|
+
"least-connections",
|
|
1891
|
+
"random"
|
|
1892
|
+
]),
|
|
1893
|
+
public: z2.boolean(),
|
|
1894
|
+
maxPlayersPerShard: z2.number().int().positive(),
|
|
1895
|
+
minShards: z2.number().int().min(0),
|
|
1896
|
+
maxShards: z2.number().int().positive().optional()
|
|
1897
|
+
});
|
|
1898
|
+
var RegisterShardSchema = z2.object({
|
|
1899
|
+
shardId: z2.string(),
|
|
1900
|
+
roomId: z2.string(),
|
|
1901
|
+
url: z2.string().url(),
|
|
1902
|
+
maxConnections: z2.number().int().positive()
|
|
1903
|
+
});
|
|
1904
|
+
var UpdateShardStatsSchema = z2.object({
|
|
1905
|
+
connections: z2.number().int().min(0),
|
|
1906
|
+
status: z2.enum([
|
|
1907
|
+
"active",
|
|
1908
|
+
"maintenance",
|
|
1909
|
+
"draining"
|
|
1910
|
+
]).optional()
|
|
1911
|
+
});
|
|
1912
|
+
var ScaleRoomSchema = z2.object({
|
|
1913
|
+
roomId: z2.string(),
|
|
1914
|
+
targetShardCount: z2.number().int().positive(),
|
|
1915
|
+
shardTemplate: z2.object({
|
|
1916
|
+
urlTemplate: z2.string(),
|
|
1917
|
+
maxConnections: z2.number().int().positive()
|
|
1918
|
+
}).optional()
|
|
1919
|
+
});
|
|
1920
|
+
var RoomConfig = class RoomConfig2 {
|
|
1921
|
+
static {
|
|
1922
|
+
__name(this, "RoomConfig");
|
|
1923
|
+
}
|
|
1924
|
+
id;
|
|
1925
|
+
name = signal("");
|
|
1926
|
+
balancingStrategy = signal("round-robin");
|
|
1927
|
+
public = signal(true);
|
|
1928
|
+
maxPlayersPerShard = signal(100);
|
|
1929
|
+
minShards = signal(1);
|
|
1930
|
+
maxShards = signal(void 0);
|
|
1931
|
+
};
|
|
1932
|
+
_ts_decorate([
|
|
1933
|
+
id(),
|
|
1934
|
+
_ts_metadata("design:type", String)
|
|
1935
|
+
], RoomConfig.prototype, "id", void 0);
|
|
1936
|
+
_ts_decorate([
|
|
1937
|
+
sync()
|
|
1938
|
+
], RoomConfig.prototype, "name", void 0);
|
|
1939
|
+
_ts_decorate([
|
|
1940
|
+
sync()
|
|
1941
|
+
], RoomConfig.prototype, "balancingStrategy", void 0);
|
|
1942
|
+
_ts_decorate([
|
|
1943
|
+
sync()
|
|
1944
|
+
], RoomConfig.prototype, "public", void 0);
|
|
1945
|
+
_ts_decorate([
|
|
1946
|
+
sync()
|
|
1947
|
+
], RoomConfig.prototype, "maxPlayersPerShard", void 0);
|
|
1948
|
+
_ts_decorate([
|
|
1949
|
+
sync()
|
|
1950
|
+
], RoomConfig.prototype, "minShards", void 0);
|
|
1951
|
+
_ts_decorate([
|
|
1952
|
+
sync()
|
|
1953
|
+
], RoomConfig.prototype, "maxShards", void 0);
|
|
1954
|
+
var ShardInfo = class ShardInfo2 {
|
|
1955
|
+
static {
|
|
1956
|
+
__name(this, "ShardInfo");
|
|
1957
|
+
}
|
|
1958
|
+
id;
|
|
1959
|
+
roomId = signal("");
|
|
1960
|
+
url = signal("");
|
|
1961
|
+
currentConnections = signal(0);
|
|
1962
|
+
maxConnections = signal(100);
|
|
1963
|
+
status = signal("active");
|
|
1964
|
+
lastHeartbeat = signal(0);
|
|
1965
|
+
};
|
|
1966
|
+
_ts_decorate([
|
|
1967
|
+
id(),
|
|
1968
|
+
_ts_metadata("design:type", String)
|
|
1969
|
+
], ShardInfo.prototype, "id", void 0);
|
|
1970
|
+
_ts_decorate([
|
|
1971
|
+
sync()
|
|
1972
|
+
], ShardInfo.prototype, "roomId", void 0);
|
|
1973
|
+
_ts_decorate([
|
|
1974
|
+
sync()
|
|
1975
|
+
], ShardInfo.prototype, "url", void 0);
|
|
1976
|
+
_ts_decorate([
|
|
1977
|
+
sync({
|
|
1978
|
+
persist: false
|
|
1979
|
+
})
|
|
1980
|
+
], ShardInfo.prototype, "currentConnections", void 0);
|
|
1981
|
+
_ts_decorate([
|
|
1982
|
+
sync()
|
|
1983
|
+
], ShardInfo.prototype, "maxConnections", void 0);
|
|
1984
|
+
_ts_decorate([
|
|
1985
|
+
sync()
|
|
1986
|
+
], ShardInfo.prototype, "status", void 0);
|
|
1987
|
+
_ts_decorate([
|
|
1988
|
+
sync()
|
|
1989
|
+
], ShardInfo.prototype, "lastHeartbeat", void 0);
|
|
1990
|
+
var WorldRoom = class {
|
|
1991
|
+
static {
|
|
1992
|
+
__name(this, "WorldRoom");
|
|
1993
|
+
}
|
|
1994
|
+
room;
|
|
1995
|
+
// Synchronized state
|
|
1996
|
+
rooms;
|
|
1997
|
+
shards;
|
|
1998
|
+
// Only persisted state (not synced to clients)
|
|
1999
|
+
rrCounters;
|
|
2000
|
+
// Configuration
|
|
2001
|
+
defaultShardUrlTemplate;
|
|
2002
|
+
defaultMaxConnectionsPerShard;
|
|
2003
|
+
constructor(room) {
|
|
2004
|
+
this.room = room;
|
|
2005
|
+
this.rooms = signal({});
|
|
2006
|
+
this.shards = signal({});
|
|
2007
|
+
this.rrCounters = signal({});
|
|
2008
|
+
this.defaultShardUrlTemplate = signal("{shardId}");
|
|
2009
|
+
this.defaultMaxConnectionsPerShard = signal(100);
|
|
2010
|
+
const { AUTH_JWT_SECRET, SHARD_SECRET } = this.room.env;
|
|
2011
|
+
if (!AUTH_JWT_SECRET) {
|
|
2012
|
+
throw new Error("AUTH_JWT_SECRET env variable is not set");
|
|
2013
|
+
}
|
|
2014
|
+
if (!SHARD_SECRET) {
|
|
2015
|
+
throw new Error("SHARD_SECRET env variable is not set");
|
|
2016
|
+
}
|
|
2017
|
+
setTimeout(() => this.cleanupInactiveShards(), 6e4);
|
|
2018
|
+
}
|
|
2019
|
+
async onJoin(user, conn, ctx) {
|
|
2020
|
+
const canConnect = await guardManageWorld(user, ctx.request, this.room);
|
|
2021
|
+
conn.setState({
|
|
2022
|
+
...conn.state,
|
|
2023
|
+
isAdmin: canConnect
|
|
2024
|
+
});
|
|
2025
|
+
}
|
|
2026
|
+
interceptorPacket(_, obj, conn) {
|
|
2027
|
+
if (!conn.state["isAdmin"]) {
|
|
2028
|
+
return null;
|
|
2029
|
+
}
|
|
2030
|
+
return obj;
|
|
2031
|
+
}
|
|
2032
|
+
// Helper methods
|
|
2033
|
+
cleanupInactiveShards() {
|
|
2034
|
+
const now = Date.now();
|
|
2035
|
+
const timeout = 5 * 60 * 1e3;
|
|
2036
|
+
const shardsValue = this.shards();
|
|
2037
|
+
let hasChanges = false;
|
|
2038
|
+
Object.values(shardsValue).forEach((shard) => {
|
|
2039
|
+
if (now - shard.lastHeartbeat() > timeout) {
|
|
2040
|
+
delete this.shards()[shard.id];
|
|
2041
|
+
hasChanges = true;
|
|
2042
|
+
}
|
|
2043
|
+
});
|
|
2044
|
+
setTimeout(() => this.cleanupInactiveShards(), 6e4);
|
|
2045
|
+
}
|
|
2046
|
+
// Actions
|
|
2047
|
+
async registerRoom(req) {
|
|
2048
|
+
const roomConfig = await req.json();
|
|
2049
|
+
const roomId = roomConfig.name;
|
|
2050
|
+
if (!this.rooms()[roomId]) {
|
|
2051
|
+
const newRoom = new RoomConfig();
|
|
2052
|
+
newRoom.id = roomId;
|
|
2053
|
+
newRoom.name.set(roomConfig.name);
|
|
2054
|
+
newRoom.balancingStrategy.set(roomConfig.balancingStrategy);
|
|
2055
|
+
newRoom.public.set(roomConfig.public);
|
|
2056
|
+
newRoom.maxPlayersPerShard.set(roomConfig.maxPlayersPerShard);
|
|
2057
|
+
newRoom.minShards.set(roomConfig.minShards);
|
|
2058
|
+
newRoom.maxShards.set(roomConfig.maxShards);
|
|
2059
|
+
this.rooms()[roomId] = newRoom;
|
|
2060
|
+
if (roomConfig.minShards > 0) {
|
|
2061
|
+
for (let i = 0; i < roomConfig.minShards; i++) {
|
|
2062
|
+
await this.createShard(roomId);
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
} else {
|
|
2066
|
+
const room = this.rooms()[roomId];
|
|
2067
|
+
room.balancingStrategy.set(roomConfig.balancingStrategy);
|
|
2068
|
+
room.public.set(roomConfig.public);
|
|
2069
|
+
room.maxPlayersPerShard.set(roomConfig.maxPlayersPerShard);
|
|
2070
|
+
room.minShards.set(roomConfig.minShards);
|
|
2071
|
+
room.maxShards.set(roomConfig.maxShards);
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
async updateShardStats(req, res) {
|
|
2075
|
+
const body = await req.json();
|
|
2076
|
+
const { shardId, connections, status } = body;
|
|
2077
|
+
const shard = this.shards()[shardId];
|
|
2078
|
+
if (!shard) {
|
|
2079
|
+
return res.notFound(`Shard ${shardId} not found`);
|
|
2080
|
+
}
|
|
2081
|
+
shard.currentConnections.set(connections);
|
|
2082
|
+
if (status) {
|
|
2083
|
+
shard.status.set(status);
|
|
2084
|
+
}
|
|
2085
|
+
shard.lastHeartbeat.set(Date.now());
|
|
2086
|
+
}
|
|
2087
|
+
async scaleRoom(req, res) {
|
|
2088
|
+
const data = await req.json();
|
|
2089
|
+
const { targetShardCount, shardTemplate, roomId } = data;
|
|
2090
|
+
const room = this.rooms()[roomId];
|
|
2091
|
+
if (!room) {
|
|
2092
|
+
return res.notFound(`Room ${roomId} does not exist`);
|
|
2093
|
+
}
|
|
2094
|
+
const roomShards = Object.values(this.shards()).filter((shard) => shard.roomId() === roomId);
|
|
2095
|
+
const previousShardCount = roomShards.length;
|
|
2096
|
+
if (room.maxShards() !== void 0 && targetShardCount > room.maxShards()) {
|
|
2097
|
+
return res.badRequest(`Cannot scale beyond maximum allowed shards (${room.maxShards()})`, {
|
|
2098
|
+
roomId,
|
|
2099
|
+
currentShardCount: previousShardCount
|
|
2100
|
+
});
|
|
2101
|
+
}
|
|
2102
|
+
if (targetShardCount < previousShardCount) {
|
|
2103
|
+
const shardsToRemove = [
|
|
2104
|
+
...roomShards
|
|
2105
|
+
].sort((a, b) => {
|
|
2106
|
+
if (a.status() === "draining" && b.status() !== "draining") return -1;
|
|
2107
|
+
if (a.status() !== "draining" && b.status() === "draining") return 1;
|
|
2108
|
+
return a.currentConnections() - b.currentConnections();
|
|
2109
|
+
}).slice(0, previousShardCount - targetShardCount);
|
|
2110
|
+
const shardsToKeep = roomShards.filter((shard) => !shardsToRemove.some((s) => s.id === shard.id));
|
|
2111
|
+
for (const shard of shardsToRemove) {
|
|
2112
|
+
delete this.shards()[shard.id];
|
|
2113
|
+
}
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
if (targetShardCount > previousShardCount) {
|
|
2117
|
+
const newShards = [];
|
|
2118
|
+
for (let i = 0; i < targetShardCount - previousShardCount; i++) {
|
|
2119
|
+
const newShard = await this.createShard(roomId, shardTemplate?.urlTemplate, shardTemplate?.maxConnections);
|
|
2120
|
+
if (newShard) {
|
|
2121
|
+
newShards.push(newShard);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
async connect(req, res) {
|
|
2127
|
+
try {
|
|
2128
|
+
let data;
|
|
2129
|
+
try {
|
|
2130
|
+
const body = await req.text();
|
|
2131
|
+
if (!body || body.trim() === "") {
|
|
2132
|
+
return res.badRequest("Request body is empty");
|
|
2133
|
+
}
|
|
2134
|
+
data = JSON.parse(body);
|
|
2135
|
+
} catch (parseError) {
|
|
2136
|
+
return res.badRequest("Invalid JSON in request body");
|
|
2137
|
+
}
|
|
2138
|
+
if (!data.roomId) {
|
|
2139
|
+
return res.badRequest("roomId parameter is required");
|
|
2140
|
+
}
|
|
2141
|
+
const autoCreate = data.autoCreate !== void 0 ? data.autoCreate : true;
|
|
2142
|
+
const result = await this.findOptimalShard(data.roomId, autoCreate);
|
|
2143
|
+
if ("error" in result) {
|
|
2144
|
+
return res.notFound(result.error);
|
|
2145
|
+
}
|
|
2146
|
+
return res.success({
|
|
2147
|
+
success: true,
|
|
2148
|
+
shardId: result.shardId,
|
|
2149
|
+
url: result.url
|
|
2150
|
+
});
|
|
2151
|
+
} catch (error) {
|
|
2152
|
+
console.error("Error connecting to shard:", error);
|
|
2153
|
+
return res.serverError();
|
|
2154
|
+
}
|
|
2155
|
+
}
|
|
2156
|
+
async findOptimalShard(roomId, autoCreate = true) {
|
|
2157
|
+
let room = this.rooms()[roomId];
|
|
2158
|
+
if (!room) {
|
|
2159
|
+
if (autoCreate) {
|
|
2160
|
+
const mockRequest = {
|
|
2161
|
+
json: /* @__PURE__ */ __name(async () => ({
|
|
2162
|
+
name: roomId,
|
|
2163
|
+
balancingStrategy: "round-robin",
|
|
2164
|
+
public: true,
|
|
2165
|
+
maxPlayersPerShard: this.defaultMaxConnectionsPerShard(),
|
|
2166
|
+
minShards: 1,
|
|
2167
|
+
maxShards: void 0
|
|
2168
|
+
}), "json")
|
|
2169
|
+
};
|
|
2170
|
+
await this.registerRoom(mockRequest);
|
|
2171
|
+
room = this.rooms()[roomId];
|
|
2172
|
+
if (!room) {
|
|
2173
|
+
return {
|
|
2174
|
+
error: `Failed to create room ${roomId}`
|
|
2175
|
+
};
|
|
2176
|
+
}
|
|
2177
|
+
} else {
|
|
2178
|
+
return {
|
|
2179
|
+
error: `Room ${roomId} does not exist`
|
|
2180
|
+
};
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
const roomShards = Object.values(this.shards()).filter((shard) => shard.roomId() === roomId);
|
|
2184
|
+
if (roomShards.length === 0) {
|
|
2185
|
+
if (autoCreate) {
|
|
2186
|
+
const newShard = await this.createShard(roomId);
|
|
2187
|
+
if (newShard) {
|
|
2188
|
+
return {
|
|
2189
|
+
shardId: newShard.id,
|
|
2190
|
+
url: newShard.url()
|
|
2191
|
+
};
|
|
2192
|
+
} else {
|
|
2193
|
+
return {
|
|
2194
|
+
error: `Failed to create shard for room ${roomId}`
|
|
2195
|
+
};
|
|
2196
|
+
}
|
|
2197
|
+
} else {
|
|
2198
|
+
return {
|
|
2199
|
+
error: `No shards available for room ${roomId}`
|
|
2200
|
+
};
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
const activeShards = roomShards.filter((shard) => shard && shard.status() === "active");
|
|
2204
|
+
if (activeShards.length === 0) {
|
|
2205
|
+
return {
|
|
2206
|
+
error: `No active shards available for room ${roomId}`
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
const balancingStrategy = room.balancingStrategy();
|
|
2210
|
+
let selectedShard;
|
|
2211
|
+
switch (balancingStrategy) {
|
|
2212
|
+
case "least-connections":
|
|
2213
|
+
selectedShard = activeShards.reduce((min, shard) => shard.currentConnections() < min.currentConnections() ? shard : min, activeShards[0]);
|
|
2214
|
+
break;
|
|
2215
|
+
case "random":
|
|
2216
|
+
selectedShard = activeShards[Math.floor(Math.random() * activeShards.length)];
|
|
2217
|
+
break;
|
|
2218
|
+
case "round-robin":
|
|
2219
|
+
default:
|
|
2220
|
+
const counter = this.rrCounters()[roomId] || 0;
|
|
2221
|
+
const nextCounter = (counter + 1) % activeShards.length;
|
|
2222
|
+
this.rrCounters()[roomId] = nextCounter;
|
|
2223
|
+
selectedShard = activeShards[counter];
|
|
2224
|
+
break;
|
|
2225
|
+
}
|
|
2226
|
+
return {
|
|
2227
|
+
shardId: selectedShard.id,
|
|
2228
|
+
url: selectedShard.url()
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
// Private methods
|
|
2232
|
+
async createShard(roomId, urlTemplate, maxConnections) {
|
|
2233
|
+
const room = this.rooms()[roomId];
|
|
2234
|
+
if (!room) {
|
|
2235
|
+
console.error(`Cannot create shard for non-existent room: ${roomId}`);
|
|
2236
|
+
return null;
|
|
2237
|
+
}
|
|
2238
|
+
const shardId = `${roomId}:${Date.now()}-${Math.floor(Math.random() * 1e4)}`;
|
|
2239
|
+
const template = urlTemplate || this.defaultShardUrlTemplate();
|
|
2240
|
+
const url = template.replace("{shardId}", shardId).replace("{roomId}", roomId);
|
|
2241
|
+
const max = maxConnections || room.maxPlayersPerShard();
|
|
2242
|
+
const newShard = new ShardInfo();
|
|
2243
|
+
newShard.id = shardId;
|
|
2244
|
+
newShard.roomId.set(roomId);
|
|
2245
|
+
newShard.url.set(url);
|
|
2246
|
+
newShard.maxConnections.set(max);
|
|
2247
|
+
newShard.currentConnections.set(0);
|
|
2248
|
+
newShard.status.set("active");
|
|
2249
|
+
newShard.lastHeartbeat.set(Date.now());
|
|
2250
|
+
this.shards()[shardId] = newShard;
|
|
2251
|
+
return newShard;
|
|
2252
|
+
}
|
|
2253
|
+
};
|
|
2254
|
+
_ts_decorate([
|
|
2255
|
+
sync(RoomConfig)
|
|
2256
|
+
], WorldRoom.prototype, "rooms", void 0);
|
|
2257
|
+
_ts_decorate([
|
|
2258
|
+
sync(ShardInfo)
|
|
2259
|
+
], WorldRoom.prototype, "shards", void 0);
|
|
2260
|
+
_ts_decorate([
|
|
2261
|
+
persist()
|
|
2262
|
+
], WorldRoom.prototype, "rrCounters", void 0);
|
|
2263
|
+
_ts_decorate([
|
|
2264
|
+
Request2({
|
|
2265
|
+
path: "register-room",
|
|
2266
|
+
method: "POST"
|
|
2267
|
+
}),
|
|
2268
|
+
Guard([
|
|
2269
|
+
guardManageWorld
|
|
2270
|
+
]),
|
|
2271
|
+
_ts_metadata("design:type", Function),
|
|
2272
|
+
_ts_metadata("design:paramtypes", [
|
|
2273
|
+
typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0
|
|
2274
|
+
]),
|
|
2275
|
+
_ts_metadata("design:returntype", Promise)
|
|
2276
|
+
], WorldRoom.prototype, "registerRoom", null);
|
|
2277
|
+
_ts_decorate([
|
|
2278
|
+
Request2({
|
|
2279
|
+
path: "update-shard",
|
|
2280
|
+
method: "POST"
|
|
2281
|
+
}),
|
|
2282
|
+
Guard([
|
|
2283
|
+
guardManageWorld
|
|
2284
|
+
]),
|
|
2285
|
+
_ts_metadata("design:type", Function),
|
|
2286
|
+
_ts_metadata("design:paramtypes", [
|
|
2287
|
+
typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0,
|
|
2288
|
+
typeof ServerResponse === "undefined" ? Object : ServerResponse
|
|
2289
|
+
]),
|
|
2290
|
+
_ts_metadata("design:returntype", Promise)
|
|
2291
|
+
], WorldRoom.prototype, "updateShardStats", null);
|
|
2292
|
+
_ts_decorate([
|
|
2293
|
+
Request2({
|
|
2294
|
+
path: "scale-room",
|
|
2295
|
+
method: "POST"
|
|
2296
|
+
}),
|
|
2297
|
+
Guard([
|
|
2298
|
+
guardManageWorld
|
|
2299
|
+
]),
|
|
2300
|
+
_ts_metadata("design:type", Function),
|
|
2301
|
+
_ts_metadata("design:paramtypes", [
|
|
2302
|
+
typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0,
|
|
2303
|
+
typeof ServerResponse === "undefined" ? Object : ServerResponse
|
|
2304
|
+
]),
|
|
2305
|
+
_ts_metadata("design:returntype", Promise)
|
|
2306
|
+
], WorldRoom.prototype, "scaleRoom", null);
|
|
2307
|
+
_ts_decorate([
|
|
2308
|
+
Request2({
|
|
2309
|
+
path: "connect",
|
|
2310
|
+
method: "POST"
|
|
2311
|
+
}),
|
|
2312
|
+
_ts_metadata("design:type", Function),
|
|
2313
|
+
_ts_metadata("design:paramtypes", [
|
|
2314
|
+
typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0,
|
|
2315
|
+
typeof ServerResponse === "undefined" ? Object : ServerResponse
|
|
2316
|
+
]),
|
|
2317
|
+
_ts_metadata("design:returntype", Promise)
|
|
2318
|
+
], WorldRoom.prototype, "connect", null);
|
|
2319
|
+
WorldRoom = _ts_decorate([
|
|
2320
|
+
Room({
|
|
2321
|
+
path: "world-{worldId}",
|
|
2322
|
+
maxUsers: 100,
|
|
2323
|
+
throttleStorage: 2e3,
|
|
2324
|
+
throttleSync: 500
|
|
2325
|
+
}),
|
|
2326
|
+
_ts_metadata("design:type", Function),
|
|
2327
|
+
_ts_metadata("design:paramtypes", [
|
|
2328
|
+
typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0
|
|
2329
|
+
])
|
|
2330
|
+
], WorldRoom);
|
|
748
2331
|
export {
|
|
749
2332
|
Action,
|
|
750
2333
|
ClientIo,
|
|
751
2334
|
Guard,
|
|
752
2335
|
MockConnection,
|
|
2336
|
+
Request2 as Request,
|
|
753
2337
|
Room,
|
|
754
2338
|
RoomGuard,
|
|
755
2339
|
Server,
|
|
756
2340
|
ServerIo,
|
|
2341
|
+
ServerResponse,
|
|
2342
|
+
Shard,
|
|
2343
|
+
WorldRoom,
|
|
2344
|
+
request,
|
|
757
2345
|
testRoom
|
|
758
2346
|
};
|
|
759
2347
|
//# sourceMappingURL=index.js.map
|