@rpgjs/client 5.0.0-alpha.10 → 5.0.0-alpha.3

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.
Files changed (115) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/Game/EffectManager.d.ts +5 -0
  3. package/dist/RpgClient.d.ts +68 -99
  4. package/dist/RpgClientEngine.d.ts +4 -86
  5. package/dist/components/effects/index.d.ts +4 -0
  6. package/dist/components/index.d.ts +1 -2
  7. package/dist/index.d.ts +1 -1
  8. package/dist/index.js +1 -2
  9. package/dist/index.js.map +1 -1
  10. package/dist/index10.js +1 -1
  11. package/dist/index11.js +4 -4
  12. package/dist/index11.js.map +1 -1
  13. package/dist/index12.js +2 -6
  14. package/dist/index12.js.map +1 -1
  15. package/dist/index13.js +2 -2
  16. package/dist/index13.js.map +1 -1
  17. package/dist/index14.js +35 -95
  18. package/dist/index14.js.map +1 -1
  19. package/dist/index15.js +186 -45
  20. package/dist/index15.js.map +1 -1
  21. package/dist/index16.js +5 -187
  22. package/dist/index16.js.map +1 -1
  23. package/dist/index17.js +383 -5
  24. package/dist/index17.js.map +1 -1
  25. package/dist/index18.js +28 -384
  26. package/dist/index18.js.map +1 -1
  27. package/dist/index19.js +17 -24
  28. package/dist/index19.js.map +1 -1
  29. package/dist/index2.js +25 -147
  30. package/dist/index2.js.map +1 -1
  31. package/dist/index20.js +2413 -16
  32. package/dist/index20.js.map +1 -1
  33. package/dist/index21.js +88 -2395
  34. package/dist/index21.js.map +1 -1
  35. package/dist/index22.js +103 -108
  36. package/dist/index22.js.map +1 -1
  37. package/dist/index23.js +57 -95
  38. package/dist/index23.js.map +1 -1
  39. package/dist/index24.js +12 -62
  40. package/dist/index24.js.map +1 -1
  41. package/dist/index25.js +37 -18
  42. package/dist/index25.js.map +1 -1
  43. package/dist/index26.js +3 -25
  44. package/dist/index26.js.map +1 -1
  45. package/dist/index27.js +314 -87
  46. package/dist/index27.js.map +1 -1
  47. package/dist/index28.js +21 -37
  48. package/dist/index28.js.map +1 -1
  49. package/dist/index29.js +9 -3
  50. package/dist/index29.js.map +1 -1
  51. package/dist/index3.js +2 -2
  52. package/dist/index30.js +6 -317
  53. package/dist/index30.js.map +1 -1
  54. package/dist/index31.js +171 -24
  55. package/dist/index31.js.map +1 -1
  56. package/dist/index32.js +497 -7
  57. package/dist/index32.js.map +1 -1
  58. package/dist/index33.js +9 -8
  59. package/dist/index33.js.map +1 -1
  60. package/dist/index34.js +4400 -9
  61. package/dist/index34.js.map +1 -1
  62. package/dist/index35.js +85 -4397
  63. package/dist/index35.js.map +1 -1
  64. package/dist/index36.js +55 -310
  65. package/dist/index36.js.map +1 -1
  66. package/dist/index37.js +15 -169
  67. package/dist/index37.js.map +1 -1
  68. package/dist/index38.js +15 -496
  69. package/dist/index38.js.map +1 -1
  70. package/dist/index4.js +5 -18
  71. package/dist/index4.js.map +1 -1
  72. package/dist/index5.js +1 -2
  73. package/dist/index5.js.map +1 -1
  74. package/dist/index6.js +1 -1
  75. package/dist/index7.js +2 -10
  76. package/dist/index7.js.map +1 -1
  77. package/dist/index8.js +6 -24
  78. package/dist/index8.js.map +1 -1
  79. package/dist/index9.js +2 -2
  80. package/dist/presets/index.d.ts +0 -102
  81. package/dist/services/loadMap.d.ts +2 -123
  82. package/dist/services/mmorpg.d.ts +3 -7
  83. package/package.json +12 -14
  84. package/src/Game/{AnimationManager.ts → EffectManager.ts} +2 -2
  85. package/src/Game/Object.ts +0 -69
  86. package/src/RpgClient.ts +67 -101
  87. package/src/RpgClientEngine.ts +24 -159
  88. package/src/components/character.ce +33 -74
  89. package/src/components/{animations → effects}/animation.ce +5 -3
  90. package/src/components/{animations → effects}/index.ts +1 -1
  91. package/src/components/index.ts +1 -2
  92. package/src/components/scenes/draw-map.ce +23 -6
  93. package/src/components/scenes/element-map.ce +23 -0
  94. package/src/components/scenes/event-layer.ce +3 -3
  95. package/src/core/setup.ts +0 -2
  96. package/src/index.ts +1 -1
  97. package/src/module.ts +5 -23
  98. package/src/presets/index.ts +1 -5
  99. package/src/services/loadMap.ts +2 -131
  100. package/src/services/mmorpg.ts +4 -20
  101. package/tsconfig.json +1 -1
  102. package/vite.config.ts +1 -1
  103. package/dist/Game/AnimationManager.d.ts +0 -8
  104. package/dist/components/animations/index.d.ts +0 -4
  105. package/dist/index39.js +0 -61
  106. package/dist/index39.js.map +0 -1
  107. package/dist/index40.js +0 -20
  108. package/dist/index40.js.map +0 -1
  109. package/dist/index41.js +0 -82
  110. package/dist/index41.js.map +0 -1
  111. package/dist/presets/animation.d.ts +0 -31
  112. package/dist/presets/lpc.d.ts +0 -89
  113. package/src/presets/animation.ts +0 -46
  114. package/src/presets/lpc.ts +0 -108
  115. /package/src/components/{animations → effects}/hit.ce +0 -0
package/dist/index21.js CHANGED
@@ -1,2421 +1,114 @@
1
- import { dset } from './index34.js';
2
- import z from './index35.js';
3
- import { syncClass, generateShortUUID as generateShortUUID$1, createStatesSnapshot, id, sync, persist, load, getByPath, DELETE_TOKEN } from './index18.js';
4
- import { signal } from './index36.js';
1
+ import { __name } from './index26.js';
2
+ import { load } from './index17.js';
3
+ import { PartySocket } from './index31.js';
4
+ import './index32.js';
5
5
 
6
- var __defProp = Object.defineProperty;
7
- var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
-
9
- // src/decorators.ts
10
- function Action(name, bodyValidation) {
11
- return function(target, propertyKey) {
12
- if (!target.constructor._actionMetadata) {
13
- target.constructor._actionMetadata = /* @__PURE__ */ new Map();
14
- }
15
- target.constructor._actionMetadata.set(name, {
16
- key: propertyKey,
17
- bodyValidation
18
- });
19
- };
20
- }
21
- __name(Action, "Action");
22
- function Request2(options, bodyValidation) {
23
- return function(target, propertyKey) {
24
- if (!target.constructor._requestMetadata) {
25
- target.constructor._requestMetadata = /* @__PURE__ */ new Map();
26
- }
27
- const path = options.path.startsWith("/") ? options.path : `/${options.path}`;
28
- const method = options.method || "GET";
29
- const routeKey = `${method}:${path}`;
30
- target.constructor._requestMetadata.set(routeKey, {
31
- key: propertyKey,
32
- path,
33
- method,
34
- bodyValidation
35
- });
36
- };
37
- }
38
- __name(Request2, "Request");
39
- function Room(options) {
40
- return function(target) {
41
- target.path = options.path;
42
- target.prototype.maxUsers = options.maxUsers;
43
- target.prototype.throttleStorage = options.throttleStorage;
44
- target.prototype.throttleSync = options.throttleSync;
45
- target.prototype.sessionExpiryTime = options.sessionExpiryTime ?? 5 * 60 * 1e3;
46
- if (options.guards) {
47
- target["_roomGuards"] = options.guards;
48
- }
49
- };
50
- }
51
- __name(Room, "Room");
52
- function RoomGuard(guards) {
53
- return function(target) {
54
- target["_roomGuards"] = guards;
55
- };
56
- }
57
- __name(RoomGuard, "RoomGuard");
58
- function Guard(guards) {
59
- return function(target, propertyKey, descriptor) {
60
- if (!target.constructor["_actionGuards"]) {
61
- target.constructor["_actionGuards"] = /* @__PURE__ */ new Map();
62
- }
63
- if (!Array.isArray(guards)) {
64
- guards = [
65
- guards
66
- ];
67
- }
68
- target.constructor["_actionGuards"].set(propertyKey, guards);
69
- };
70
- }
71
- __name(Guard, "Guard");
72
-
73
- // ../sync/src/utils.ts
74
- function generateShortUUID() {
75
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
76
- let uuid = "";
77
- for (let i = 0; i < 8; i++) {
78
- const randomIndex = Math.floor(Math.random() * chars.length);
79
- uuid += chars[randomIndex];
80
- }
81
- return uuid;
82
- }
83
- __name(generateShortUUID, "generateShortUUID");
84
-
85
- // src/storage.ts
86
- var Storage = class {
87
- static {
88
- __name(this, "Storage");
89
- }
90
- memory = /* @__PURE__ */ new Map();
91
- async put(key, value) {
92
- this.memory.set(key, value);
93
- }
94
- async get(key) {
95
- return this.memory.get(key);
96
- }
97
- async delete(key) {
98
- this.memory.delete(key);
99
- }
100
- async list() {
101
- return this.memory;
102
- }
103
- };
104
- function isPromise(value) {
105
- return value instanceof Promise;
106
- }
107
- __name(isPromise, "isPromise");
108
- async function awaitReturn(val) {
109
- return isPromise(val) ? await val : val;
110
- }
111
- __name(awaitReturn, "awaitReturn");
112
- function isClass(obj) {
113
- return typeof obj === "function" && obj.prototype && obj.prototype.constructor === obj;
114
- }
115
- __name(isClass, "isClass");
116
- function throttle(func, wait) {
117
- let timeout = null;
118
- let lastArgs = null;
119
- return function(...args) {
120
- if (!timeout) {
121
- func(...args);
122
- timeout = setTimeout(() => {
123
- if (lastArgs) {
124
- func(...lastArgs);
125
- lastArgs = null;
126
- }
127
- timeout = null;
128
- }, wait);
129
- } else {
130
- lastArgs = args;
131
- }
132
- };
133
- }
134
- __name(throttle, "throttle");
135
- function extractParams(pattern, str) {
136
- const regexPattern = pattern.replace(/{(\w+)}/g, "(?<$1>[\\w-]+)");
137
- const regex = new RegExp(`^${regexPattern}$`);
138
- const match = regex.exec(str);
139
- if (match && match.groups) {
140
- return match.groups;
141
- } else if (pattern === str) {
142
- return {};
143
- } else {
144
- return null;
145
- }
146
- }
147
- __name(extractParams, "extractParams");
148
- function dremove(obj, keys) {
149
- if (typeof keys === "string") {
150
- keys = keys.split(".");
151
- }
152
- let i = 0;
153
- const l = keys.length;
154
- let t = obj;
155
- let k;
156
- while (i < l - 1) {
157
- k = keys[i++];
158
- if (k === "__proto__" || k === "constructor" || k === "prototype") return;
159
- if (typeof t[k] !== "object" || t[k] === null) return;
160
- t = t[k];
161
- }
162
- k = keys[i];
163
- if (t && typeof t === "object" && !(k === "__proto__" || k === "constructor" || k === "prototype")) {
164
- delete t[k];
165
- }
166
- }
167
- __name(dremove, "dremove");
168
- function buildObject(valuesMap, allMemory) {
169
- let memoryObj = {};
170
- for (let path of valuesMap.keys()) {
171
- const value = valuesMap.get(path);
172
- dset(memoryObj, path, value);
173
- if (value === "$delete") {
174
- dremove(allMemory, path);
175
- } else {
176
- dset(allMemory, path, value);
177
- }
178
- }
179
- return memoryObj;
180
- }
181
- __name(buildObject, "buildObject");
182
- function response(status, body) {
183
- return new Response(JSON.stringify(body), {
184
- status,
185
- headers: {
186
- "Content-Type": "application/json"
187
- }
188
- });
189
- }
190
- __name(response, "response");
191
-
192
- // src/request/response.ts
193
- var ServerResponse = class {
194
- static {
195
- __name(this, "ServerResponse");
196
- }
197
- interceptors;
198
- statusCode = 200;
199
- responseBody = {};
200
- responseHeaders = {
201
- "Content-Type": "application/json"
202
- };
203
- /**
204
- * Creates a new ServerResponse instance
205
- * @param interceptors Array of interceptor functions that can modify the response
206
- */
207
- constructor(interceptors = []) {
208
- this.interceptors = interceptors;
209
- }
210
- /**
211
- * Sets the status code for the response
212
- * @param code HTTP status code
213
- * @returns this instance for chaining
214
- */
215
- status(code) {
216
- this.statusCode = code;
217
- return this;
218
- }
219
- /**
220
- * Sets the response body and returns this instance (chainable method)
221
- *
222
- * @param body Response body
223
- * @returns this instance for chaining
224
- */
225
- body(body) {
226
- this.responseBody = body;
227
- return this;
228
- }
229
- /**
230
- * Adds a header to the response
231
- * @param name Header name
232
- * @param value Header value
233
- * @returns this instance for chaining
234
- */
235
- header(name, value) {
236
- this.responseHeaders[name] = value;
237
- return this;
238
- }
239
- /**
240
- * Adds multiple headers to the response
241
- * @param headers Object containing headers
242
- * @returns this instance for chaining
243
- */
244
- setHeaders(headers) {
245
- this.responseHeaders = {
246
- ...this.responseHeaders,
247
- ...headers
248
- };
249
- return this;
250
- }
251
- /**
252
- * Add an interceptor to the chain
253
- * @param interceptor Function that takes a Response and returns a modified Response
254
- * @returns this instance for chaining
255
- */
256
- use(interceptor) {
257
- this.interceptors.push(interceptor);
258
- return this;
259
- }
260
- /**
261
- * Builds and returns the Response object after applying all interceptors
262
- * @returns Promise<Response> The final Response object
263
- * @private Internal method used by terminal methods
264
- */
265
- async buildResponse() {
266
- let response2 = new Response(JSON.stringify(this.responseBody), {
267
- status: this.statusCode,
268
- headers: this.responseHeaders
269
- });
270
- for (const interceptor of this.interceptors) {
271
- try {
272
- const interceptedResponse = interceptor(response2);
273
- if (interceptedResponse instanceof Promise) {
274
- response2 = await interceptedResponse;
275
- } else {
276
- response2 = interceptedResponse;
277
- }
278
- } catch (error) {
279
- console.error("Error in interceptor:", error);
280
- }
281
- }
282
- return response2;
283
- }
284
- /**
285
- * Sets the response body to the JSON-stringified version of the provided value
286
- * and sends the response (terminal method)
287
- *
288
- * @param body Response body to be JSON stringified
289
- * @returns Promise<Response> The final Response object
290
- */
291
- async json(body) {
292
- this.responseBody = body;
293
- this.responseHeaders["Content-Type"] = "application/json";
294
- return this.buildResponse();
295
- }
296
- /**
297
- * Sends the response with the current configuration (terminal method)
298
- *
299
- * @param body Optional body to set before sending
300
- * @returns Promise<Response> The final Response object
301
- */
302
- async send(body) {
303
- if (body !== void 0) {
304
- this.responseBody = body;
305
- }
306
- return this.buildResponse();
307
- }
308
- /**
309
- * Sends a plain text response (terminal method)
310
- *
311
- * @param text Text to send
312
- * @returns Promise<Response> The final Response object
313
- */
314
- async text(text) {
315
- this.responseBody = text;
316
- this.responseHeaders["Content-Type"] = "text/plain";
317
- let response2 = new Response(text, {
318
- status: this.statusCode,
319
- headers: this.responseHeaders
320
- });
321
- for (const interceptor of this.interceptors) {
322
- try {
323
- const interceptedResponse = interceptor(response2);
324
- if (interceptedResponse instanceof Promise) {
325
- response2 = await interceptedResponse;
326
- } else {
327
- response2 = interceptedResponse;
328
- }
329
- } catch (error) {
330
- console.error("Error in interceptor:", error);
331
- }
332
- }
333
- return response2;
334
- }
335
- /**
336
- * Redirects to the specified URL (terminal method)
337
- *
338
- * @param url URL to redirect to
339
- * @param statusCode HTTP status code (default: 302)
340
- * @returns Promise<Response> The final Response object
341
- */
342
- async redirect(url, statusCode = 302) {
343
- this.statusCode = statusCode;
344
- this.responseHeaders["Location"] = url;
345
- return this.buildResponse();
346
- }
347
- /**
348
- * Creates a success response with status 200
349
- * @param body Response body
350
- * @returns Promise<Response> The final Response object
351
- */
352
- async success(body = {}) {
353
- return this.status(200).json(body);
354
- }
355
- /**
356
- * Creates an error response with status 400
357
- * @param message Error message
358
- * @param details Additional error details
359
- * @returns Promise<Response> The final Response object
360
- */
361
- async badRequest(message, details = {}) {
362
- return this.status(400).json({
363
- error: message,
364
- ...details
365
- });
366
- }
367
- /**
368
- * Creates an error response with status 403
369
- * @param message Error message
370
- * @returns Promise<Response> The final Response object
371
- */
372
- async notPermitted(message = "Not permitted") {
373
- return this.status(403).json({
374
- error: message
375
- });
376
- }
377
- /**
378
- * Creates an error response with status 401
379
- * @param message Error message
380
- * @returns Promise<Response> The final Response object
381
- */
382
- async unauthorized(message = "Unauthorized") {
383
- return this.status(401).json({
384
- error: message
385
- });
386
- }
387
- /**
388
- * Creates an error response with status 404
389
- * @param message Error message
390
- * @returns Promise<Response> The final Response object
391
- */
392
- async notFound(message = "Not found") {
393
- return this.status(404).json({
394
- error: message
395
- });
396
- }
397
- /**
398
- * Creates an error response with status 500
399
- * @param message Error message
400
- * @returns Promise<Response> The final Response object
401
- */
402
- async serverError(message = "Internal Server Error") {
403
- return this.status(500).json({
404
- error: message
405
- });
406
- }
407
- };
408
-
409
- // src/request/cors.ts
410
- function cors(res, options = {}) {
411
- const newHeaders = new Headers(res.headers);
412
- const requestOrigin = options.origin || "*";
413
- newHeaders.set("Access-Control-Allow-Origin", requestOrigin);
414
- if (options.credentials) {
415
- newHeaders.set("Access-Control-Allow-Credentials", "true");
416
- }
417
- if (options.exposedHeaders && options.exposedHeaders.length) {
418
- newHeaders.set("Access-Control-Expose-Headers", options.exposedHeaders.join(", "));
419
- }
420
- if (options.methods && options.methods.length) {
421
- newHeaders.set("Access-Control-Allow-Methods", options.methods.join(", "));
422
- } else {
423
- newHeaders.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
424
- }
425
- if (options.allowedHeaders && options.allowedHeaders.length) {
426
- newHeaders.set("Access-Control-Allow-Headers", options.allowedHeaders.join(", "));
427
- } else {
428
- newHeaders.set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
429
- }
430
- if (options.maxAge) {
431
- newHeaders.set("Access-Control-Max-Age", options.maxAge.toString());
432
- } else {
433
- newHeaders.set("Access-Control-Max-Age", "86400");
434
- }
435
- return new Response(res.body, {
436
- status: res.status,
437
- headers: newHeaders
438
- });
439
- }
440
- __name(cors, "cors");
441
- function createCorsInterceptor(options = {}) {
442
- return (res) => cors(res, options);
443
- }
444
- __name(createCorsInterceptor, "createCorsInterceptor");
445
-
446
- // src/server.ts
447
- var Message = z.object({
448
- action: z.string(),
449
- value: z.any()
450
- });
451
- var Server = class {
452
- static {
453
- __name(this, "Server");
454
- }
455
- room;
456
- subRoom;
457
- rooms;
458
- /**
459
- * @constructor
460
- * @param {Party.Room} room - The room object representing the current game or application instance.
461
- *
462
- * @example
463
- * ```typescript
464
- * const server = new MyServer(new ServerIo("game"));
465
- * ```
466
- */
467
- constructor(room) {
468
- this.room = room;
469
- this.subRoom = null;
470
- this.rooms = [];
471
- }
472
- /**
473
- * @readonly
474
- * @property {boolean} isHibernate - Indicates whether the server is in hibernate mode.
475
- *
476
- * @example
477
- * ```typescript
478
- * if (!server.isHibernate) {
479
- * console.log("Server is active");
480
- * }
481
- * ```
482
- */
483
- get isHibernate() {
484
- return !!this["options"]?.hibernate;
485
- }
486
- get roomStorage() {
487
- return this.room.storage;
488
- }
489
- async send(conn, obj, subRoom) {
490
- obj = structuredClone(obj);
491
- if (subRoom.interceptorPacket) {
492
- const signal2 = this.getUsersProperty(subRoom);
493
- const { publicId } = conn.state;
494
- const user = signal2?.()[publicId];
495
- obj = await awaitReturn(subRoom["interceptorPacket"]?.(user, obj, conn));
496
- if (obj === null) return;
497
- }
498
- conn.send(JSON.stringify(obj));
499
- }
500
- broadcast(obj, subRoom) {
501
- for (let conn of this.room.getConnections()) {
502
- this.send(conn, obj, subRoom);
503
- }
504
- }
505
- /**
506
- * @method onStart
507
- * @async
508
- * @description Initializes the server and creates the initial room if not in hibernate mode.
509
- * @returns {Promise<void>}
510
- *
511
- * @example
512
- * ```typescript
513
- * async function initServer() {
514
- * await server.onStart();
515
- * console.log("Server started");
516
- * }
517
- * ```
518
- */
519
- async onStart() {
520
- if (!this.isHibernate) {
521
- this.subRoom = await this.createRoom();
522
- }
523
- }
524
- async garbageCollector(options) {
525
- const subRoom = await this.getSubRoom();
526
- if (!subRoom) return;
527
- const activeConnections = [
528
- ...this.room.getConnections()
529
- ];
530
- const activePrivateIds = new Set(activeConnections.map((conn) => conn.id));
531
- try {
532
- const sessions = await this.room.storage.list();
533
- const users = this.getUsersProperty(subRoom);
534
- const usersPropName = this.getUsersPropName(subRoom);
535
- const validPublicIds = /* @__PURE__ */ new Set();
536
- const expiredPublicIds = /* @__PURE__ */ new Set();
537
- const SESSION_EXPIRY_TIME = options.sessionExpiryTime;
538
- const now = Date.now();
539
- for (const [key, session] of sessions) {
540
- if (!key.startsWith("session:")) continue;
541
- const privateId = key.replace("session:", "");
542
- const typedSession = session;
543
- if (!activePrivateIds.has(privateId) && !typedSession.connected && now - typedSession.created > SESSION_EXPIRY_TIME) {
544
- await this.deleteSession(privateId);
545
- expiredPublicIds.add(typedSession.publicId);
546
- } else if (typedSession && typedSession.publicId) {
547
- validPublicIds.add(typedSession.publicId);
548
- }
549
- }
550
- if (users && usersPropName) {
551
- const currentUsers = users();
552
- for (const publicId in currentUsers) {
553
- if (expiredPublicIds.has(publicId) && !validPublicIds.has(publicId)) {
554
- delete currentUsers[publicId];
555
- await this.room.storage.delete(`${usersPropName}.${publicId}`);
556
- }
557
- }
558
- }
559
- } catch (error) {
560
- console.error("Error in garbage collector:", error);
561
- }
562
- }
563
- /**
564
- * @method createRoom
565
- * @private
566
- * @async
567
- * @param {CreateRoomOptions} [options={}] - Options for creating the room.
568
- * @returns {Promise<Object>} The created room instance.
569
- *
570
- * @example
571
- * ```typescript
572
- * // This method is private and called internally
573
- * async function internalCreateRoom() {
574
- * const room = await this.createRoom({ getMemoryAll: true });
575
- * console.log("Room created:", room);
576
- * }
577
- * ```
578
- */
579
- async createRoom(options = {}) {
580
- let instance;
581
- let init = true;
582
- let initPersist = true;
583
- for (let room of this.rooms) {
584
- const params = extractParams(room.path, this.room.id);
585
- if (params) {
586
- instance = new room(this.room, params);
6
+ function createConnection(options, roomInstance) {
7
+ const conn = new PartySocket(options);
8
+ conn.addEventListener("message", (event) => {
9
+ const object = JSON.parse(event.data);
10
+ switch (object.type) {
11
+ case "sync":
12
+ load(roomInstance, object.value, true);
587
13
  break;
588
- }
589
- }
590
- if (!instance) {
591
- return null;
592
- }
593
- const loadMemory = /* @__PURE__ */ __name(async () => {
594
- const root = await this.room.storage.get(".");
595
- const memory = await this.room.storage.list();
596
- const tmpObject = root || {};
597
- for (let [key, value] of memory) {
598
- if (key.startsWith("session:")) {
599
- continue;
600
- }
601
- if (key == ".") {
602
- continue;
603
- }
604
- dset(tmpObject, key, value);
605
- }
606
- load(instance, tmpObject, true);
607
- }, "loadMemory");
608
- instance.$memoryAll = {};
609
- instance.$send = (conn, obj) => {
610
- return this.send(conn, obj, instance);
611
- };
612
- instance.$broadcast = (obj) => {
613
- return this.broadcast(obj, instance);
614
- };
615
- const syncCb = /* @__PURE__ */ __name((values) => {
616
- if (options.getMemoryAll) {
617
- buildObject(values, instance.$memoryAll);
618
- }
619
- if (init && this.isHibernate) {
620
- init = false;
621
- return;
622
- }
623
- const packet = buildObject(values, instance.$memoryAll);
624
- this.broadcast({
625
- type: "sync",
626
- value: packet
627
- }, instance);
628
- values.clear();
629
- }, "syncCb");
630
- const persistCb = /* @__PURE__ */ __name(async (values) => {
631
- if (initPersist) {
632
- values.clear();
633
- return;
634
- }
635
- for (let [path, value] of values) {
636
- const _instance = path == "." ? instance : getByPath(instance, path);
637
- const itemValue = createStatesSnapshot(_instance);
638
- if (value == DELETE_TOKEN) {
639
- await this.room.storage.delete(path);
640
- } else {
641
- await this.room.storage.put(path, itemValue);
642
- }
643
- }
644
- values.clear();
645
- }, "persistCb");
646
- syncClass(instance, {
647
- onSync: throttle(syncCb, instance["throttleSync"] ?? 500),
648
- onPersist: throttle(persistCb, instance["throttleStorage"] ?? 2e3)
649
- });
650
- await loadMemory();
651
- initPersist = false;
652
- return instance;
653
- }
654
- /**
655
- * @method getSubRoom
656
- * @private
657
- * @async
658
- * @param {Object} [options={}] - Options for getting the sub-room.
659
- * @returns {Promise<Object>} The sub-room instance.
660
- *
661
- * @example
662
- * ```typescript
663
- * // This method is private and called internally
664
- * async function internalGetSubRoom() {
665
- * const subRoom = await this.getSubRoom();
666
- * console.log("Sub-room retrieved:", subRoom);
667
- * }
668
- * ```
669
- */
670
- async getSubRoom(options = {}) {
671
- let subRoom;
672
- if (this.isHibernate) {
673
- subRoom = await this.createRoom(options);
674
- } else {
675
- subRoom = this.subRoom;
676
- }
677
- return subRoom;
678
- }
679
- /**
680
- * @method getUsersProperty
681
- * @private
682
- * @param {Object} subRoom - The sub-room instance.
683
- * @returns {Object|null} The users property of the sub-room, or null if not found.
684
- *
685
- * @example
686
- * ```typescript
687
- * // This method is private and called internally
688
- * function internalGetUsers(subRoom) {
689
- * const users = this.getUsersProperty(subRoom);
690
- * console.log("Users:", users);
691
- * }
692
- * ```
693
- */
694
- getUsersProperty(subRoom) {
695
- const meta = subRoom.constructor["_propertyMetadata"];
696
- const propId = meta?.get("users");
697
- if (propId) {
698
- return subRoom[propId];
699
- }
700
- return null;
701
- }
702
- getUsersPropName(subRoom) {
703
- if (!subRoom) return null;
704
- const metadata = subRoom.constructor._propertyMetadata;
705
- if (!metadata) return null;
706
- return metadata.get("users");
707
- }
708
- /**
709
- * Retrieves the connection status property from a user object.
710
- *
711
- * @param {any} user - The user object to get the connection property from.
712
- * @returns {Function|null} - The connection property signal function or null if not found.
713
- * @private
714
- */
715
- getUserConnectionProperty(user) {
716
- if (!user) return null;
717
- const metadata = user.constructor._propertyMetadata;
718
- if (!metadata) return null;
719
- const connectedPropName = metadata.get("connected");
720
- if (!connectedPropName) return null;
721
- return user[connectedPropName];
722
- }
723
- /**
724
- * Updates a user's connection status in the signal.
725
- *
726
- * @param {any} user - The user object to update.
727
- * @param {boolean} isConnected - The new connection status.
728
- * @returns {boolean} - Whether the update was successful.
729
- * @private
730
- */
731
- updateUserConnectionStatus(user, isConnected) {
732
- const connectionSignal = this.getUserConnectionProperty(user);
733
- if (connectionSignal) {
734
- connectionSignal.set(isConnected);
735
- return true;
736
- }
737
- return false;
738
- }
739
- /**
740
- * @method getSession
741
- * @private
742
- * @param {string} privateId - The private ID of the session.
743
- * @returns {Promise<Object|null>} The session object, or null if not found.
744
- *
745
- * @example
746
- * ```typescript
747
- * const session = await server.getSession("privateId");
748
- * console.log(session);
749
- * ```
750
- */
751
- async getSession(privateId) {
752
- if (!privateId) return null;
753
- try {
754
- const session = await this.room.storage.get(`session:${privateId}`);
755
- return session;
756
- } catch (e) {
757
- return null;
758
- }
759
- }
760
- async saveSession(privateId, data) {
761
- const sessionData = {
762
- ...data,
763
- created: data.created || Date.now(),
764
- connected: data.connected !== void 0 ? data.connected : true
765
- };
766
- await this.room.storage.put(`session:${privateId}`, sessionData);
767
- }
768
- async updateSessionConnection(privateId, connected) {
769
- const session = await this.getSession(privateId);
770
- if (session) {
771
- await this.saveSession(privateId, {
772
- ...session,
773
- connected
774
- });
775
- }
776
- }
777
- /**
778
- * @method deleteSession
779
- * @private
780
- * @param {string} privateId - The private ID of the session to delete.
781
- * @returns {Promise<void>}
782
- *
783
- * @example
784
- * ```typescript
785
- * await server.deleteSession("privateId");
786
- * ```
787
- */
788
- async deleteSession(privateId) {
789
- await this.room.storage.delete(`session:${privateId}`);
790
- }
791
- async onConnectClient(conn, ctx) {
792
- const subRoom = await this.getSubRoom({
793
- getMemoryAll: true
794
- });
795
- if (!subRoom) {
796
- conn.close();
797
- return;
798
- }
799
- const sessionExpiryTime = subRoom.constructor.sessionExpiryTime;
800
- await this.garbageCollector({
801
- sessionExpiryTime
802
- });
803
- const roomGuards = subRoom.constructor["_roomGuards"] || [];
804
- for (const guard of roomGuards) {
805
- const isAuthorized = await guard(conn, ctx);
806
- if (!isAuthorized) {
807
- conn.close();
808
- return;
809
- }
810
- }
811
- const existingSession = await this.getSession(conn.id);
812
- const publicId = existingSession?.publicId || generateShortUUID$1();
813
- let user = null;
814
- const signal2 = this.getUsersProperty(subRoom);
815
- const usersPropName = this.getUsersPropName(subRoom);
816
- if (signal2) {
817
- const { classType } = signal2.options;
818
- if (!existingSession?.publicId) {
819
- user = isClass(classType) ? new classType() : classType(conn, ctx);
820
- signal2()[publicId] = user;
821
- const snapshot = createStatesSnapshot(user);
822
- this.room.storage.put(`${usersPropName}.${publicId}`, snapshot);
823
- } else {
824
- user = signal2()[existingSession.publicId];
825
- }
826
- if (!existingSession) {
827
- await this.saveSession(conn.id, {
828
- publicId
829
- });
830
- } else {
831
- await this.updateSessionConnection(conn.id, true);
832
- }
833
- }
834
- this.updateUserConnectionStatus(user, true);
835
- await awaitReturn(subRoom["onJoin"]?.(user, conn, ctx));
836
- conn.setState({
837
- ...conn.state,
838
- publicId
839
- });
840
- this.send(conn, {
841
- type: "sync",
842
- value: {
843
- pId: publicId,
844
- ...subRoom.$memoryAll
845
- }
846
- }, subRoom);
847
- }
848
- /**
849
- * @method onConnect
850
- * @async
851
- * @param {Party.Connection} conn - The connection object for the new user.
852
- * @param {Party.ConnectionContext} ctx - The context of the connection.
853
- * @description Handles a new user connection, creates a user object, and sends initial sync data.
854
- * @returns {Promise<void>}
855
- *
856
- * @example
857
- * ```typescript
858
- * server.onConnect = async (conn, ctx) => {
859
- * await server.onConnect(conn, ctx);
860
- * console.log("New user connected:", conn.id);
861
- * };
862
- * ```
863
- */
864
- async onConnect(conn, ctx) {
865
- if (ctx.request?.headers.has("x-shard-id")) {
866
- this.onConnectShard(conn, ctx);
867
- } else {
868
- await this.onConnectClient(conn, ctx);
869
- }
870
- }
871
- /**
872
- * @method onConnectShard
873
- * @private
874
- * @param {Party.Connection} conn - The connection object for the new shard.
875
- * @param {Party.ConnectionContext} ctx - The context of the shard connection.
876
- * @description Handles a new shard connection, setting up the necessary state.
877
- * @returns {void}
878
- */
879
- onConnectShard(conn, ctx) {
880
- const shardId = ctx.request?.headers.get("x-shard-id") || "unknown-shard";
881
- conn.setState({
882
- shard: true,
883
- shardId,
884
- clients: /* @__PURE__ */ new Map()
885
- // Track clients connected through this shard
886
- });
887
- }
888
- /**
889
- * @method onMessage
890
- * @async
891
- * @param {string} message - The message received from a user or shard.
892
- * @param {Party.Connection} sender - The connection object of the sender.
893
- * @description Processes incoming messages, handling differently based on if sender is shard or client.
894
- * @returns {Promise<void>}
895
- */
896
- async onMessage(message, sender) {
897
- if (sender.state && sender.state.shard) {
898
- await this.handleShardMessage(message, sender);
899
- return;
900
14
  }
901
- let json;
902
- try {
903
- json = JSON.parse(message);
904
- } catch (e) {
905
- return;
906
- }
907
- const result = Message.safeParse(json);
908
- if (!result.success) {
909
- return;
910
- }
911
- const subRoom = await this.getSubRoom();
912
- if (!subRoom) {
913
- console.warn("Room not found");
914
- return;
915
- }
916
- const roomGuards = subRoom.constructor["_roomGuards"] || [];
917
- for (const guard of roomGuards) {
918
- const isAuthorized = await guard(sender, result.data.value, this.room);
919
- if (!isAuthorized) {
920
- return;
921
- }
922
- }
923
- const actions = subRoom.constructor["_actionMetadata"];
924
- if (actions) {
925
- const signal2 = this.getUsersProperty(subRoom);
926
- const { publicId } = sender.state;
927
- const user = signal2?.()[publicId];
928
- const actionName = actions.get(result.data.action);
929
- if (actionName) {
930
- const guards = subRoom.constructor["_actionGuards"]?.get(actionName.key) || [];
931
- for (const guard of guards) {
932
- const isAuthorized = await guard(sender, result.data.value);
933
- if (!isAuthorized) {
934
- return;
935
- }
936
- }
937
- if (actionName.bodyValidation) {
938
- const bodyResult = actionName.bodyValidation.safeParse(result.data.value);
939
- if (!bodyResult.success) {
940
- return;
941
- }
942
- }
943
- await awaitReturn(subRoom[actionName.key](user, result.data.value, sender));
944
- }
945
- }
946
- }
947
- /**
948
- * @method handleShardMessage
949
- * @private
950
- * @async
951
- * @param {string} message - The message received from a shard.
952
- * @param {Party.Connection} shardConnection - The connection object of the shard.
953
- * @description Processes messages from shards, extracting client information.
954
- * @returns {Promise<void>}
955
- */
956
- async handleShardMessage(message, shardConnection) {
957
- let parsedMessage;
958
- try {
959
- parsedMessage = JSON.parse(message);
960
- } catch (e) {
961
- console.error("Error parsing shard message:", e);
962
- return;
963
- }
964
- const shardState = shardConnection.state;
965
- shardState.clients;
966
- switch (parsedMessage.type) {
967
- case "shard.clientConnected":
968
- await this.handleShardClientConnect(parsedMessage, shardConnection);
969
- break;
970
- case "shard.clientMessage":
971
- await this.handleShardClientMessage(parsedMessage, shardConnection);
972
- break;
973
- case "shard.clientDisconnected":
974
- await this.handleShardClientDisconnect(parsedMessage, shardConnection);
975
- break;
976
- default:
977
- console.warn(`Unknown shard message type: ${parsedMessage.type}`);
978
- }
979
- }
980
- /**
981
- * @method handleShardClientConnect
982
- * @private
983
- * @async
984
- * @param {Object} message - The client connection message from a shard.
985
- * @param {Party.Connection} shardConnection - The connection object of the shard.
986
- * @description Handles a new client connection via a shard.
987
- * @returns {Promise<void>}
988
- */
989
- async handleShardClientConnect(message, shardConnection) {
990
- const { privateId, requestInfo } = message;
991
- const shardState = shardConnection.state;
992
- const virtualContext = {
993
- request: requestInfo ? {
994
- headers: new Headers(requestInfo.headers),
995
- method: requestInfo.method,
996
- url: requestInfo.url
997
- } : void 0
998
- };
999
- const virtualConnection = {
1000
- id: privateId,
1001
- send: /* @__PURE__ */ __name((data) => {
1002
- shardConnection.send(JSON.stringify({
1003
- targetClientId: privateId,
1004
- data
1005
- }));
1006
- }, "send"),
1007
- state: {},
1008
- setState: /* @__PURE__ */ __name((state) => {
1009
- const clients = shardState.clients;
1010
- const currentState = clients.get(privateId) || {};
1011
- const mergedState = Object.assign({}, currentState, state);
1012
- clients.set(privateId, mergedState);
1013
- virtualConnection.state = clients.get(privateId);
1014
- return virtualConnection.state;
1015
- }, "setState"),
1016
- close: /* @__PURE__ */ __name(() => {
1017
- shardConnection.send(JSON.stringify({
1018
- type: "shard.closeClient",
1019
- privateId
1020
- }));
1021
- if (shardState.clients) {
1022
- shardState.clients.delete(privateId);
15
+ });
16
+ return {
17
+ emit: /* @__PURE__ */ __name((key, value) => {
18
+ conn.send(JSON.stringify({
19
+ action: key,
20
+ value
21
+ }));
22
+ }, "emit"),
23
+ on: /* @__PURE__ */ __name((key, cb) => {
24
+ conn.addEventListener("message", (event) => {
25
+ const object = JSON.parse(event.data);
26
+ if (object.type === key) {
27
+ cb(object.value);
1023
28
  }
1024
- }, "close")
1025
- };
1026
- if (!shardState.clients.has(privateId)) {
1027
- shardState.clients.set(privateId, {});
1028
- }
1029
- await this.onConnectClient(virtualConnection, virtualContext);
1030
- }
1031
- /**
1032
- * @method handleShardClientMessage
1033
- * @private
1034
- * @async
1035
- * @param {Object} message - The client message from a shard.
1036
- * @param {Party.Connection} shardConnection - The connection object of the shard.
1037
- * @description Handles a message from a client via a shard.
1038
- * @returns {Promise<void>}
1039
- */
1040
- async handleShardClientMessage(message, shardConnection) {
1041
- const { privateId, publicId, payload } = message;
1042
- const shardState = shardConnection.state;
1043
- const clients = shardState.clients;
1044
- if (!clients.has(privateId)) {
1045
- console.warn(`Received message from unknown client ${privateId}, creating virtual connection`);
1046
- clients.set(privateId, {
1047
- publicId
1048
29
  });
1049
- }
1050
- const virtualConnection = {
1051
- id: privateId,
1052
- send: /* @__PURE__ */ __name((data) => {
1053
- shardConnection.send(JSON.stringify({
1054
- targetClientId: privateId,
1055
- data
1056
- }));
1057
- }, "send"),
1058
- state: clients.get(privateId),
1059
- setState: /* @__PURE__ */ __name((state) => {
1060
- const currentState = clients.get(privateId) || {};
1061
- const mergedState = Object.assign({}, currentState, state);
1062
- clients.set(privateId, mergedState);
1063
- virtualConnection.state = clients.get(privateId);
1064
- return virtualConnection.state;
1065
- }, "setState"),
1066
- close: /* @__PURE__ */ __name(() => {
1067
- shardConnection.send(JSON.stringify({
1068
- type: "shard.closeClient",
1069
- privateId
1070
- }));
1071
- if (shardState.clients) {
1072
- shardState.clients.delete(privateId);
1073
- }
1074
- }, "close")
1075
- };
1076
- const payloadString = typeof payload === "string" ? payload : JSON.stringify(payload);
1077
- await this.onMessage(payloadString, virtualConnection);
1078
- }
1079
- /**
1080
- * @method handleShardClientDisconnect
1081
- * @private
1082
- * @async
1083
- * @param {Object} message - The client disconnection message from a shard.
1084
- * @param {Party.Connection} shardConnection - The connection object of the shard.
1085
- * @description Handles a client disconnection via a shard.
1086
- * @returns {Promise<void>}
1087
- */
1088
- async handleShardClientDisconnect(message, shardConnection) {
1089
- const { privateId, publicId } = message;
1090
- const shardState = shardConnection.state;
1091
- const clients = shardState.clients;
1092
- const clientState = clients.get(privateId);
1093
- if (!clientState) {
1094
- console.warn(`Disconnection for unknown client ${privateId}`);
1095
- return;
1096
- }
1097
- const virtualConnection = {
1098
- id: privateId,
1099
- send: /* @__PURE__ */ __name(() => {
1100
- }, "send"),
1101
- state: clientState,
1102
- setState: /* @__PURE__ */ __name(() => {
1103
- return {};
1104
- }, "setState"),
1105
- close: /* @__PURE__ */ __name(() => {
1106
- }, "close")
1107
- };
1108
- await this.onClose(virtualConnection);
1109
- clients.delete(privateId);
1110
- }
1111
- /**
1112
- * @method onClose
1113
- * @async
1114
- * @param {Party.Connection} conn - The connection object of the disconnecting user.
1115
- * @description Handles user disconnection, removing them from the room and triggering the onLeave event.
1116
- * @returns {Promise<void>}
1117
- *
1118
- * @example
1119
- * ```typescript
1120
- * server.onClose = async (conn) => {
1121
- * await server.onClose(conn);
1122
- * console.log("User disconnected:", conn.id);
1123
- * };
1124
- * ```
1125
- */
1126
- async onClose(conn) {
1127
- const subRoom = await this.getSubRoom();
1128
- if (!subRoom) {
1129
- return;
1130
- }
1131
- const signal2 = this.getUsersProperty(subRoom);
1132
- if (!conn.state) {
1133
- return;
1134
- }
1135
- const privateId = conn.id;
1136
- const { publicId } = conn.state;
1137
- const user = signal2?.()[publicId];
1138
- if (!user) return;
1139
- await this.updateSessionConnection(privateId, false);
1140
- const connectionUpdated = this.updateUserConnectionStatus(user, false);
1141
- await awaitReturn(subRoom["onLeave"]?.(user, conn));
1142
- if (!connectionUpdated) {
1143
- this.broadcast({
1144
- type: "user_disconnected",
1145
- value: {
1146
- publicId
1147
- }
1148
- }, subRoom);
1149
- }
1150
- }
1151
- async onAlarm() {
1152
- const subRoom = await this.getSubRoom();
1153
- await awaitReturn(subRoom["onAlarm"]?.(subRoom));
1154
- }
1155
- async onError(connection, error) {
1156
- const subRoom = await this.getSubRoom();
1157
- await awaitReturn(subRoom["onError"]?.(connection, error));
1158
- }
1159
- /**
1160
- * @method onRequest
1161
- * @async
1162
- * @param {Party.Request} req - The HTTP request to handle
1163
- * @description Handles HTTP requests, either directly from clients or forwarded by shards
1164
- * @returns {Promise<Response>} The response to return to the client
1165
- */
1166
- async onRequest(req) {
1167
- const isFromShard = req.headers.has("x-forwarded-by-shard");
1168
- const shardId = req.headers.get("x-shard-id");
1169
- const res = new ServerResponse([
1170
- createCorsInterceptor()
1171
- ]);
1172
- if (req.method === "OPTIONS") {
1173
- return res.status(200).send({});
1174
- }
1175
- if (isFromShard) {
1176
- return this.handleShardRequest(req, res, shardId);
1177
- }
1178
- return this.handleDirectRequest(req, res);
1179
- }
1180
- /**
1181
- * @method handleDirectRequest
1182
- * @private
1183
- * @async
1184
- * @param {Party.Request} req - The HTTP request received directly from a client
1185
- * @description Processes requests received directly from clients
1186
- * @returns {Promise<Response>} The response to return to the client
1187
- */
1188
- async handleDirectRequest(req, res) {
1189
- const subRoom = await this.getSubRoom();
1190
- if (!subRoom) {
1191
- return res.notFound();
1192
- }
1193
- const response2 = await this.tryMatchRequestHandler(req, res, subRoom);
1194
- if (response2) {
1195
- return response2;
1196
- }
1197
- const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(req, res));
1198
- if (!legacyResponse) {
1199
- return res.notFound();
1200
- }
1201
- if (legacyResponse instanceof Response) {
1202
- return legacyResponse;
1203
- }
1204
- return res.success(legacyResponse);
1205
- }
1206
- /**
1207
- * @method tryMatchRequestHandler
1208
- * @private
1209
- * @async
1210
- * @param {Party.Request} req - The HTTP request to handle
1211
- * @param {Object} subRoom - The room instance
1212
- * @description Attempts to match the request to a registered @Request handler
1213
- * @returns {Promise<Response | null>} The response or null if no handler matched
1214
- */
1215
- async tryMatchRequestHandler(req, res, subRoom) {
1216
- const requestHandlers = subRoom.constructor["_requestMetadata"];
1217
- if (!requestHandlers) {
1218
- return null;
1219
- }
1220
- const url = new URL(req.url);
1221
- const method = req.method;
1222
- let pathname = url.pathname;
1223
- pathname = "/" + pathname.split("/").slice(4).join("/");
1224
- for (const [routeKey, handler] of requestHandlers.entries()) {
1225
- const firstColonIndex = routeKey.indexOf(":");
1226
- const handlerMethod = routeKey.substring(0, firstColonIndex);
1227
- const handlerPath = routeKey.substring(firstColonIndex + 1);
1228
- if (handlerMethod !== method) {
1229
- continue;
1230
- }
1231
- if (this.pathMatches(pathname, handlerPath)) {
1232
- const params = this.extractPathParams(pathname, handlerPath);
1233
- const guards = subRoom.constructor["_actionGuards"]?.get(handler.key) || [];
1234
- for (const guard of guards) {
1235
- const isAuthorized = await guard(null, req, this.room);
1236
- if (isAuthorized instanceof Response) {
1237
- return isAuthorized;
1238
- }
1239
- if (!isAuthorized) {
1240
- return res.notPermitted();
1241
- }
1242
- }
1243
- let bodyData = null;
1244
- if (handler.bodyValidation && [
1245
- "POST",
1246
- "PUT",
1247
- "PATCH"
1248
- ].includes(method)) {
1249
- try {
1250
- const contentType = req.headers.get("content-type") || "";
1251
- if (contentType.includes("application/json")) {
1252
- const body = await req.json();
1253
- const validation = handler.bodyValidation.safeParse(body);
1254
- if (!validation.success) {
1255
- return res.badRequest("Invalid request body", {
1256
- details: validation.error
1257
- });
1258
- }
1259
- bodyData = validation.data;
1260
- }
1261
- } catch (error) {
1262
- return res.badRequest("Failed to parse request body");
1263
- }
1264
- }
1265
- try {
1266
- req["data"] = bodyData;
1267
- req["params"] = params;
1268
- const result = await awaitReturn(subRoom[handler.key](req, res));
1269
- if (result instanceof Response) {
1270
- return result;
1271
- }
1272
- return res.success(result);
1273
- } catch (error) {
1274
- console.error("Error executing request handler:", error);
1275
- return res.serverError();
1276
- }
1277
- }
1278
- }
1279
- return null;
1280
- }
1281
- /**
1282
- * @method pathMatches
1283
- * @private
1284
- * @param {string} requestPath - The path from the request
1285
- * @param {string} handlerPath - The path pattern from the handler
1286
- * @description Checks if a request path matches a handler path pattern
1287
- * @returns {boolean} True if the paths match
1288
- */
1289
- pathMatches(requestPath, handlerPath) {
1290
- const pathRegexString = handlerPath.replace(/\//g, "\\/").replace(/:([^\/]+)/g, "([^/]+)");
1291
- const pathRegex = new RegExp(`^${pathRegexString}$`);
1292
- return pathRegex.test(requestPath);
1293
- }
1294
- /**
1295
- * @method extractPathParams
1296
- * @private
1297
- * @param {string} requestPath - The path from the request
1298
- * @param {string} handlerPath - The path pattern from the handler
1299
- * @description Extracts path parameters from the request path based on the handler pattern
1300
- * @returns {Object} An object containing the path parameters
1301
- */
1302
- extractPathParams(requestPath, handlerPath) {
1303
- const params = {};
1304
- const paramNames = [];
1305
- handlerPath.split("/").forEach((segment) => {
1306
- if (segment.startsWith(":")) {
1307
- paramNames.push(segment.substring(1));
1308
- }
1309
- });
1310
- const pathRegexString = handlerPath.replace(/\//g, "\\/").replace(/:([^\/]+)/g, "([^/]+)");
1311
- const pathRegex = new RegExp(`^${pathRegexString}$`);
1312
- const matches = requestPath.match(pathRegex);
1313
- if (matches && matches.length > 1) {
1314
- for (let i = 0; i < paramNames.length; i++) {
1315
- params[paramNames[i]] = matches[i + 1];
1316
- }
1317
- }
1318
- return params;
1319
- }
1320
- /**
1321
- * @method handleShardRequest
1322
- * @private
1323
- * @async
1324
- * @param {Party.Request} req - The HTTP request forwarded by a shard
1325
- * @param {string | null} shardId - The ID of the shard that forwarded the request
1326
- * @description Processes requests forwarded by shards, preserving client context
1327
- * @returns {Promise<Response>} The response to return to the shard (which will forward it to the client)
1328
- */
1329
- async handleShardRequest(req, res, shardId) {
1330
- const subRoom = await this.getSubRoom();
1331
- if (!subRoom) {
1332
- return res.notFound();
1333
- }
1334
- const originalClientIp = req.headers.get("x-original-client-ip");
1335
- const enhancedReq = this.createEnhancedRequest(req, originalClientIp);
1336
- try {
1337
- const response2 = await this.tryMatchRequestHandler(enhancedReq, res, subRoom);
1338
- if (response2) {
1339
- return response2;
1340
- }
1341
- const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(enhancedReq, res));
1342
- if (!legacyResponse) {
1343
- return res.notFound();
1344
- }
1345
- if (legacyResponse instanceof Response) {
1346
- return legacyResponse;
1347
- }
1348
- return res.success(legacyResponse);
1349
- } catch (error) {
1350
- console.error(`Error processing request from shard ${shardId}:`, error);
1351
- return res.serverError();
1352
- }
1353
- }
1354
- /**
1355
- * @method createEnhancedRequest
1356
- * @private
1357
- * @param {Party.Request} originalReq - The original request received from the shard
1358
- * @param {string | null} originalClientIp - The original client IP, if available
1359
- * @description Creates an enhanced request object that preserves the original client context
1360
- * @returns {Party.Request} The enhanced request object
1361
- */
1362
- createEnhancedRequest(originalReq, originalClientIp) {
1363
- const clonedReq = originalReq.clone();
1364
- clonedReq.viaShard = true;
1365
- if (originalClientIp) {
1366
- clonedReq.originalClientIp = originalClientIp;
1367
- }
1368
- return clonedReq;
1369
- }
1370
- };
1371
-
1372
- // src/shard.ts
1373
- var Shard = class {
1374
- static {
1375
- __name(this, "Shard");
1376
- }
1377
- room;
1378
- ws;
1379
- connectionMap;
1380
- mainServerStub;
1381
- worldUrl;
1382
- worldId;
1383
- lastReportedConnections;
1384
- statsInterval;
1385
- statsIntervalId;
1386
- constructor(room) {
1387
- this.room = room;
1388
- this.connectionMap = /* @__PURE__ */ new Map();
1389
- this.worldUrl = null;
1390
- this.worldId = "default";
1391
- this.lastReportedConnections = 0;
1392
- this.statsInterval = 3e4;
1393
- this.statsIntervalId = null;
1394
- }
1395
- async onStart() {
1396
- const roomId = this.room.id.split(":")[0];
1397
- const roomStub = this.room.context.parties.main.get(roomId);
1398
- if (!roomStub) {
1399
- console.warn("No room room stub found in main party context");
1400
- return;
1401
- }
1402
- this.mainServerStub = roomStub;
1403
- this.ws = await roomStub.socket({
1404
- headers: {
1405
- "x-shard-id": this.room.id
1406
- }
1407
- });
1408
- this.ws.addEventListener("message", (event) => {
1409
- try {
1410
- const message = JSON.parse(event.data);
1411
- if (message.targetClientId) {
1412
- const clientConn = this.connectionMap.get(message.targetClientId);
1413
- if (clientConn) {
1414
- delete message.targetClientId;
1415
- clientConn.send(message.data);
1416
- }
1417
- } else {
1418
- this.room.broadcast(event.data);
30
+ }, "on"),
31
+ off: /* @__PURE__ */ __name((key, cb) => {
32
+ conn.removeEventListener("message", (event) => {
33
+ const object = JSON.parse(event.data);
34
+ if (object.type === key) {
35
+ cb(object.value);
1419
36
  }
1420
- } catch (error) {
1421
- console.error("Error processing message from main server:", error);
1422
- }
1423
- });
1424
- await this.updateWorldStats();
1425
- this.startPeriodicStatsUpdates();
1426
- }
1427
- startPeriodicStatsUpdates() {
1428
- if (!this.worldUrl) {
1429
- return;
1430
- }
1431
- if (this.statsIntervalId) {
1432
- clearInterval(this.statsIntervalId);
1433
- }
1434
- this.statsIntervalId = setInterval(() => {
1435
- this.updateWorldStats().catch((error) => {
1436
- console.error("Error in periodic stats update:", error);
1437
37
  });
1438
- }, this.statsInterval);
1439
- }
1440
- stopPeriodicStatsUpdates() {
1441
- if (this.statsIntervalId) {
1442
- clearInterval(this.statsIntervalId);
1443
- this.statsIntervalId = null;
1444
- }
1445
- }
1446
- onConnect(conn, ctx) {
1447
- this.connectionMap.set(conn.id, conn);
1448
- const headers = {};
1449
- if (ctx.request?.headers) {
1450
- ctx.request.headers.forEach((value, key) => {
1451
- headers[key] = value;
1452
- });
1453
- }
1454
- const requestInfo = ctx.request ? {
1455
- headers,
1456
- url: ctx.request.url,
1457
- method: ctx.request.method
1458
- } : null;
1459
- this.ws.send(JSON.stringify({
1460
- type: "shard.clientConnected",
1461
- privateId: conn.id,
1462
- requestInfo
1463
- }));
1464
- this.updateWorldStats();
1465
- }
1466
- onMessage(message, sender) {
1467
- try {
1468
- const parsedMessage = typeof message === "string" ? JSON.parse(message) : message;
1469
- const wrappedMessage = JSON.stringify({
1470
- type: "shard.clientMessage",
1471
- privateId: sender.id,
1472
- publicId: sender.state?.publicId,
1473
- payload: parsedMessage
1474
- });
1475
- this.ws.send(wrappedMessage);
1476
- } catch (error) {
1477
- console.error("Error forwarding message to main server:", error);
1478
- }
1479
- }
1480
- onClose(conn) {
1481
- this.connectionMap.delete(conn.id);
1482
- this.ws.send(JSON.stringify({
1483
- type: "shard.clientDisconnected",
1484
- privateId: conn.id,
1485
- publicId: conn.state?.publicId
1486
- }));
1487
- this.updateWorldStats();
1488
- }
1489
- async updateWorldStats() {
1490
- const currentConnections = this.connectionMap.size;
1491
- if (currentConnections === this.lastReportedConnections) {
1492
- return true;
1493
- }
38
+ }, "off"),
39
+ close: /* @__PURE__ */ __name(() => conn.close(), "close"),
40
+ conn
41
+ };
42
+ }
43
+ __name(createConnection, "createConnection");
44
+ async function connectionRoom(options, roomInstance) {
45
+ return createConnection(options, roomInstance);
46
+ }
47
+ __name(connectionRoom, "connectionRoom");
48
+ async function connectionWorld(options, roomInstance) {
49
+ const shardInfo = await getOptimalShard(options);
50
+ const result = createConnection({
51
+ ...options,
52
+ party: "shard",
53
+ room: shardInfo.url
54
+ }, roomInstance);
55
+ return {
56
+ ...result,
57
+ shardInfo
58
+ };
59
+ }
60
+ __name(connectionWorld, "connectionWorld");
61
+ async function getOptimalShard(worldOptions) {
62
+ const {
63
+ host,
64
+ room,
65
+ worldId = "world-default",
66
+ retryCount = 3,
67
+ retryDelay = 1e3,
68
+ autoCreate = true
69
+ // Default to true for auto-creation
70
+ } = worldOptions;
71
+ let attempts = 0;
72
+ const url = new URL(`${host}/parties/world/${encodeURIComponent(worldId)}/connect`);
73
+ const requestUrl = url.toString();
74
+ while (attempts < retryCount) {
1494
75
  try {
1495
- const worldRoom = this.room.context.parties.world.get("world-default");
1496
- const response2 = await worldRoom.fetch("/update-shard", {
76
+ const response = await fetch(requestUrl, {
1497
77
  method: "POST",
1498
78
  headers: {
1499
- "Content-Type": "application/json",
1500
- "x-access-shard": this.room.env.SHARD_SECRET
79
+ "Content-Type": "application/json"
1501
80
  },
1502
81
  body: JSON.stringify({
1503
- shardId: this.room.id,
1504
- connections: currentConnections
82
+ roomId: room,
83
+ autoCreate
1505
84
  })
1506
85
  });
1507
- if (!response2.ok) {
1508
- const errorData = await response2.json().catch(() => ({
86
+ if (!response.ok) {
87
+ const errorData = await response.json().catch(() => ({
1509
88
  error: "Unknown error"
1510
89
  }));
1511
- console.error(`Failed to update World stats: ${response2.status} - ${errorData.error || "Unknown error"}`);
1512
- return false;
90
+ throw new Error(`World service returned ${response.status}: ${errorData.error || "Unknown error"}`);
1513
91
  }
1514
- this.lastReportedConnections = currentConnections;
1515
- return true;
1516
- } catch (error) {
1517
- console.error("Error updating World stats:", error);
1518
- return false;
1519
- }
1520
- }
1521
- /**
1522
- * @method onRequest
1523
- * @async
1524
- * @param {Party.Request} req - The HTTP request to handle
1525
- * @description Forwards HTTP requests to the main server, preserving client context
1526
- * @returns {Promise<Response>} The response from the main server
1527
- */
1528
- async onRequest(req) {
1529
- if (!this.mainServerStub) {
1530
- return response(503, {
1531
- error: "Shard not connected to main server"
1532
- });
1533
- }
1534
- try {
1535
- const url = new URL(req.url);
1536
- const path = url.pathname;
1537
- const method = req.method;
1538
- let body = null;
1539
- if (method !== "GET" && method !== "HEAD") {
1540
- body = await req.text();
92
+ const data = await response.json();
93
+ if (!data.url || !data.shardId) {
94
+ throw new Error("Invalid response from World service: missing url or shardId");
1541
95
  }
1542
- const headers = new Headers();
1543
- req.headers.forEach((value, key) => {
1544
- headers.set(key, value);
1545
- });
1546
- headers.set("x-shard-id", this.room.id);
1547
- headers.set("x-forwarded-by-shard", "true");
1548
- const clientIp = req.headers.get("x-forwarded-for") || "unknown";
1549
- if (clientIp) {
1550
- headers.set("x-original-client-ip", clientIp);
1551
- }
1552
- const requestInit = {
1553
- method,
1554
- headers,
1555
- body
96
+ return {
97
+ shardId: data.shardId,
98
+ url: data.url
1556
99
  };
1557
- const response2 = await this.mainServerStub.fetch(path, requestInit);
1558
- return response2;
1559
100
  } catch (error) {
1560
- return response(500, {
1561
- error: "Error forwarding request"
1562
- });
1563
- }
1564
- }
1565
- /**
1566
- * @method onAlarm
1567
- * @async
1568
- * @description Executed periodically, used to perform maintenance tasks
1569
- */
1570
- async onAlarm() {
1571
- await this.updateWorldStats();
1572
- }
1573
- };
1574
-
1575
- // src/testing.ts
1576
- async function testRoom(Room3, options = {}) {
1577
- const createServer = /* @__PURE__ */ __name((io2) => {
1578
- const server2 = new Server(io2);
1579
- server2.rooms = [
1580
- Room3
1581
- ];
1582
- return server2;
1583
- }, "createServer");
1584
- const isShard = options.shard || false;
1585
- const io = new ServerIo(Room3.path, isShard ? {
1586
- parties: {
1587
- game: createServer
1588
- },
1589
- env: options.env
1590
- } : {
1591
- env: options.env
1592
- });
1593
- Room3.prototype.throttleSync = 0;
1594
- Room3.prototype.throttleStorage = 0;
1595
- Room3.prototype.options = options;
1596
- let server;
1597
- if (options.shard) {
1598
- const shardServer = new Shard(io);
1599
- shardServer.subRoom = null;
1600
- server = shardServer;
1601
- for (const lobby of io.context.parties.main.values()) {
1602
- await lobby.server.onStart();
1603
- }
1604
- } else {
1605
- server = await createServer(io);
1606
- }
1607
- await server.onStart();
1608
- return {
1609
- server,
1610
- room: server.subRoom,
1611
- createClient: /* @__PURE__ */ __name(async (id2) => {
1612
- const client = await io.connection(server, id2);
1613
- return client;
1614
- }, "createClient")
1615
- };
1616
- }
1617
- __name(testRoom, "testRoom");
1618
- async function request(room, path, options = {
1619
- method: "GET"
1620
- }) {
1621
- const url = new URL("http://localhost" + path);
1622
- const request1 = new Request(url.toString(), options);
1623
- const response2 = await room.onRequest(request1);
1624
- return response2;
1625
- }
1626
- __name(request, "request");
1627
-
1628
- // src/mock.ts
1629
- var MockPartyClient = class {
1630
- static {
1631
- __name(this, "MockPartyClient");
1632
- }
1633
- server;
1634
- events;
1635
- id;
1636
- conn;
1637
- constructor(server, id2) {
1638
- this.server = server;
1639
- this.events = /* @__PURE__ */ new Map();
1640
- this.id = id2 || generateShortUUID();
1641
- this.conn = new MockConnection(this);
1642
- }
1643
- addEventListener(event, cb) {
1644
- if (!this.events.has(event)) {
1645
- this.events.set(event, []);
1646
- }
1647
- this.events.get(event).push(cb);
1648
- }
1649
- removeEventListener(event, cb) {
1650
- if (!this.events.has(event)) return;
1651
- const callbacks = this.events.get(event);
1652
- const index = callbacks.indexOf(cb);
1653
- if (index !== -1) {
1654
- callbacks.splice(index, 1);
1655
- }
1656
- if (callbacks.length === 0) {
1657
- this.events.delete(event);
1658
- }
1659
- }
1660
- _trigger(event, data) {
1661
- const callbacks = this.events.get(event);
1662
- if (callbacks) {
1663
- for (const cb of callbacks) {
1664
- cb(data);
101
+ attempts++;
102
+ if (attempts >= retryCount) {
103
+ throw error;
1665
104
  }
105
+ console.warn(`Failed to get shard (attempt ${attempts}/${retryCount}). Retrying in ${retryDelay}ms...`);
106
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
1666
107
  }
1667
108
  }
1668
- send(data) {
1669
- return this.server.onMessage(JSON.stringify(data), this.conn);
1670
- }
1671
- };
1672
- var MockLobby = class MockLobby2 {
1673
- static {
1674
- __name(this, "MockLobby");
1675
- }
1676
- server;
1677
- constructor(server) {
1678
- this.server = server;
1679
- }
1680
- socket() {
1681
- return new MockPartyClient(this.server);
1682
- }
1683
- fetch(url, options) {
1684
- return request(this.server, url, options);
1685
- }
1686
- };
1687
- var MockContext = class MockContext2 {
1688
- static {
1689
- __name(this, "MockContext");
1690
- }
1691
- room;
1692
- parties;
1693
- constructor(room, options = {}) {
1694
- this.room = room;
1695
- this.parties = {
1696
- main: /* @__PURE__ */ new Map()
1697
- };
1698
- const parties = options.parties || {};
1699
- for (let lobbyId in parties) {
1700
- this.parties.main.set(lobbyId, new MockLobby(parties[lobbyId](room)));
1701
- }
1702
- }
1703
- };
1704
- var MockPartyRoom = class MockPartyRoom2 {
1705
- static {
1706
- __name(this, "MockPartyRoom");
1707
- }
1708
- id;
1709
- clients;
1710
- storage;
1711
- context;
1712
- env;
1713
- constructor(id2, options = {}) {
1714
- this.id = id2;
1715
- this.clients = /* @__PURE__ */ new Map();
1716
- this.storage = new Storage();
1717
- this.env = {};
1718
- this.id = id2 || generateShortUUID();
1719
- this.context = new MockContext(this, {
1720
- parties: options.parties || {}
1721
- });
1722
- this.env = options.env || {};
1723
- }
1724
- async connection(server, id2) {
1725
- const socket = new MockPartyClient(server, id2);
1726
- const url = new URL("http://localhost");
1727
- const request2 = new Request(url.toString(), {
1728
- method: "GET",
1729
- headers: {
1730
- "Content-Type": "application/json"
1731
- }
1732
- });
1733
- await server.onConnect(socket.conn, {
1734
- request: request2
1735
- });
1736
- this.clients.set(socket.id, socket);
1737
- return socket;
1738
- }
1739
- broadcast(data) {
1740
- this.clients.forEach((client) => {
1741
- client._trigger("message", data);
1742
- });
1743
- }
1744
- getConnection(id2) {
1745
- return this.clients.get(id2);
1746
- }
1747
- getConnections() {
1748
- return Array.from(this.clients.values()).map((client) => client.conn);
1749
- }
1750
- clear() {
1751
- this.clients.clear();
1752
- }
1753
- };
1754
- var MockConnection = class {
1755
- static {
1756
- __name(this, "MockConnection");
1757
- }
1758
- client;
1759
- server;
1760
- id;
1761
- constructor(client) {
1762
- this.client = client;
1763
- this.state = {};
1764
- this.server = client.server;
1765
- this.id = client.id;
1766
- }
1767
- state;
1768
- setState(value) {
1769
- this.state = value;
1770
- }
1771
- send(data) {
1772
- this.client._trigger("message", data);
1773
- }
1774
- close() {
1775
- this.server.onClose(this);
1776
- }
1777
- };
1778
- var ServerIo = MockPartyRoom;
1779
- var ClientIo = MockPartyClient;
1780
-
1781
- // src/jwt.ts
1782
- var JWTAuth = class {
1783
- static {
1784
- __name(this, "JWTAuth");
1785
- }
1786
- secret;
1787
- encoder;
1788
- decoder;
1789
- /**
1790
- * Constructor for the JWTAuth class
1791
- * @param {string} secret - The secret key used for signing and verifying tokens
1792
- */
1793
- constructor(secret) {
1794
- if (!secret || typeof secret !== "string") {
1795
- throw new Error("Secret is required and must be a string");
1796
- }
1797
- this.secret = secret;
1798
- this.encoder = new TextEncoder();
1799
- this.decoder = new TextDecoder();
1800
- }
1801
- /**
1802
- * Convert the secret to a CryptoKey for HMAC operations
1803
- * @returns {Promise<CryptoKey>} - The CryptoKey for HMAC operations
1804
- */
1805
- async getSecretKey() {
1806
- const keyData = this.encoder.encode(this.secret);
1807
- return await crypto.subtle.importKey(
1808
- "raw",
1809
- keyData,
1810
- {
1811
- name: "HMAC",
1812
- hash: {
1813
- name: "SHA-256"
1814
- }
1815
- },
1816
- false,
1817
- [
1818
- "sign",
1819
- "verify"
1820
- ]
1821
- // key usages
1822
- );
1823
- }
1824
- /**
1825
- * Base64Url encode a buffer
1826
- * @param {ArrayBuffer} buffer - The buffer to encode
1827
- * @returns {string} - The base64url encoded string
1828
- */
1829
- base64UrlEncode(buffer) {
1830
- const base64 = btoa(String.fromCharCode(...new Uint8Array(buffer)));
1831
- return base64.replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
1832
- }
1833
- /**
1834
- * Base64Url decode a string
1835
- * @param {string} base64Url - The base64url encoded string
1836
- * @returns {ArrayBuffer} - The decoded buffer
1837
- */
1838
- base64UrlDecode(base64Url) {
1839
- const padding = "=".repeat((4 - base64Url.length % 4) % 4);
1840
- const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/") + padding;
1841
- const rawData = atob(base64);
1842
- const buffer = new Uint8Array(rawData.length);
1843
- for (let i = 0; i < rawData.length; i++) {
1844
- buffer[i] = rawData.charCodeAt(i);
1845
- }
1846
- return buffer.buffer;
1847
- }
1848
- /**
1849
- * Sign a payload and create a JWT token
1850
- * @param {JWTPayload} payload - The payload to include in the token
1851
- * @param {JWTOptions} [options={}] - Options for the token
1852
- * @param {string | number} [options.expiresIn='1h'] - Token expiration time
1853
- * @returns {Promise<string>} - The JWT token
1854
- */
1855
- async sign(payload, options = {}) {
1856
- if (!payload || typeof payload !== "object") {
1857
- throw new Error("Payload must be an object");
1858
- }
1859
- const expiresIn = options.expiresIn || "1h";
1860
- let exp;
1861
- if (typeof expiresIn === "number") {
1862
- exp = Math.floor(Date.now() / 1e3) + expiresIn;
1863
- } else if (typeof expiresIn === "string") {
1864
- const match = expiresIn.match(/^(\d+)([smhd])$/);
1865
- if (match) {
1866
- const value = parseInt(match[1]);
1867
- const unit = match[2];
1868
- const seconds = {
1869
- "s": value,
1870
- "m": value * 60,
1871
- "h": value * 60 * 60,
1872
- "d": value * 60 * 60 * 24
1873
- }[unit];
1874
- exp = Math.floor(Date.now() / 1e3) + seconds;
1875
- } else {
1876
- throw new Error('Invalid expiresIn format. Use a number (seconds) or a string like "1h", "30m", etc.');
1877
- }
1878
- }
1879
- const fullPayload = {
1880
- ...payload,
1881
- iat: Math.floor(Date.now() / 1e3),
1882
- exp
1883
- };
1884
- const header = {
1885
- alg: "HS256",
1886
- typ: "JWT"
1887
- };
1888
- const encodedHeader = this.base64UrlEncode(this.encoder.encode(JSON.stringify(header)));
1889
- const encodedPayload = this.base64UrlEncode(this.encoder.encode(JSON.stringify(fullPayload)));
1890
- const signatureBase = `${encodedHeader}.${encodedPayload}`;
1891
- const key = await this.getSecretKey();
1892
- const signature = await crypto.subtle.sign({
1893
- name: "HMAC"
1894
- }, key, this.encoder.encode(signatureBase));
1895
- const encodedSignature = this.base64UrlEncode(signature);
1896
- return `${signatureBase}.${encodedSignature}`;
1897
- }
1898
- /**
1899
- * Verify a JWT token and return the decoded payload
1900
- * @param {string} token - The JWT token to verify
1901
- * @returns {Promise<JWTPayload>} - The decoded payload if verification succeeds
1902
- * @throws {Error} - If verification fails
1903
- */
1904
- async verify(token) {
1905
- if (!token || typeof token !== "string") {
1906
- throw new Error("Token is required and must be a string");
1907
- }
1908
- const parts = token.split(".");
1909
- if (parts.length !== 3) {
1910
- throw new Error("Invalid token format");
1911
- }
1912
- const [encodedHeader, encodedPayload, encodedSignature] = parts;
1913
- try {
1914
- const header = JSON.parse(this.decoder.decode(this.base64UrlDecode(encodedHeader)));
1915
- const payload = JSON.parse(this.decoder.decode(this.base64UrlDecode(encodedPayload)));
1916
- if (header.alg !== "HS256") {
1917
- throw new Error(`Unsupported algorithm: ${header.alg}`);
1918
- }
1919
- const now = Math.floor(Date.now() / 1e3);
1920
- if (payload.exp && payload.exp < now) {
1921
- throw new Error("Token has expired");
1922
- }
1923
- const key = await this.getSecretKey();
1924
- const signatureBase = `${encodedHeader}.${encodedPayload}`;
1925
- const signature = this.base64UrlDecode(encodedSignature);
1926
- const isValid = await crypto.subtle.verify({
1927
- name: "HMAC"
1928
- }, key, signature, this.encoder.encode(signatureBase));
1929
- if (!isValid) {
1930
- throw new Error("Invalid signature");
1931
- }
1932
- return payload;
1933
- } catch (error) {
1934
- if (error instanceof Error) {
1935
- throw new Error(`Token verification failed: ${error.message}`);
1936
- }
1937
- throw new Error("Token verification failed: Unknown error");
1938
- }
1939
- }
1940
- };
1941
-
1942
- // src/world.guard.ts
1943
- var guardManageWorld = /* @__PURE__ */ __name(async (_, req, room) => {
1944
- const tokenShard = req.headers.get("x-access-shard");
1945
- if (tokenShard) {
1946
- if (tokenShard !== room.env.SHARD_SECRET) {
1947
- return false;
1948
- }
1949
- return true;
1950
- }
1951
- const url = new URL(req.url);
1952
- const token = req.headers.get("Authorization") ?? url.searchParams.get("world-auth-token");
1953
- if (!token) {
1954
- return false;
1955
- }
1956
- const jwt = new JWTAuth(room.env.AUTH_JWT_SECRET);
1957
- try {
1958
- const payload = await jwt.verify(token);
1959
- if (!payload) {
1960
- return false;
1961
- }
1962
- } catch (error) {
1963
- return false;
1964
- }
1965
- return true;
1966
- }, "guardManageWorld");
1967
-
1968
- // src/world.ts
1969
- function _ts_decorate(decorators, target, key, desc) {
1970
- var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1971
- if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
1972
- 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;
1973
- return c > 3 && r && Object.defineProperty(target, key, r), r;
1974
- }
1975
- __name(_ts_decorate, "_ts_decorate");
1976
- function _ts_metadata(k, v) {
1977
- if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
109
+ throw new Error("Failed to get shard after all retry attempts");
1978
110
  }
1979
- __name(_ts_metadata, "_ts_metadata");
1980
- var MAX_PLAYERS_PER_SHARD = 75;
1981
- z.object({
1982
- name: z.string(),
1983
- balancingStrategy: z.enum([
1984
- "round-robin",
1985
- "least-connections",
1986
- "random"
1987
- ]),
1988
- public: z.boolean(),
1989
- maxPlayersPerShard: z.number().int().positive(),
1990
- minShards: z.number().int().min(0),
1991
- maxShards: z.number().int().positive().optional()
1992
- });
1993
- z.object({
1994
- shardId: z.string(),
1995
- roomId: z.string(),
1996
- url: z.string().url(),
1997
- maxConnections: z.number().int().positive()
1998
- });
1999
- z.object({
2000
- connections: z.number().int().min(0),
2001
- status: z.enum([
2002
- "active",
2003
- "maintenance",
2004
- "draining"
2005
- ]).optional()
2006
- });
2007
- z.object({
2008
- roomId: z.string(),
2009
- targetShardCount: z.number().int().positive(),
2010
- shardTemplate: z.object({
2011
- urlTemplate: z.string(),
2012
- maxConnections: z.number().int().positive()
2013
- }).optional()
2014
- });
2015
- var RoomConfig = class RoomConfig2 {
2016
- static {
2017
- __name(this, "RoomConfig");
2018
- }
2019
- id;
2020
- name = signal("");
2021
- balancingStrategy = signal("round-robin");
2022
- public = signal(true);
2023
- maxPlayersPerShard = signal(MAX_PLAYERS_PER_SHARD);
2024
- minShards = signal(1);
2025
- maxShards = signal(void 0);
2026
- };
2027
- _ts_decorate([
2028
- id(),
2029
- _ts_metadata("design:type", String)
2030
- ], RoomConfig.prototype, "id", void 0);
2031
- _ts_decorate([
2032
- sync()
2033
- ], RoomConfig.prototype, "name", void 0);
2034
- _ts_decorate([
2035
- sync()
2036
- ], RoomConfig.prototype, "balancingStrategy", void 0);
2037
- _ts_decorate([
2038
- sync()
2039
- ], RoomConfig.prototype, "public", void 0);
2040
- _ts_decorate([
2041
- sync()
2042
- ], RoomConfig.prototype, "maxPlayersPerShard", void 0);
2043
- _ts_decorate([
2044
- sync()
2045
- ], RoomConfig.prototype, "minShards", void 0);
2046
- _ts_decorate([
2047
- sync()
2048
- ], RoomConfig.prototype, "maxShards", void 0);
2049
- var ShardInfo = class ShardInfo2 {
2050
- static {
2051
- __name(this, "ShardInfo");
2052
- }
2053
- id;
2054
- roomId = signal("");
2055
- url = signal("");
2056
- currentConnections = signal(0);
2057
- maxConnections = signal(MAX_PLAYERS_PER_SHARD);
2058
- status = signal("active");
2059
- lastHeartbeat = signal(0);
2060
- };
2061
- _ts_decorate([
2062
- id(),
2063
- _ts_metadata("design:type", String)
2064
- ], ShardInfo.prototype, "id", void 0);
2065
- _ts_decorate([
2066
- sync()
2067
- ], ShardInfo.prototype, "roomId", void 0);
2068
- _ts_decorate([
2069
- sync()
2070
- ], ShardInfo.prototype, "url", void 0);
2071
- _ts_decorate([
2072
- sync({
2073
- persist: false
2074
- })
2075
- ], ShardInfo.prototype, "currentConnections", void 0);
2076
- _ts_decorate([
2077
- sync()
2078
- ], ShardInfo.prototype, "maxConnections", void 0);
2079
- _ts_decorate([
2080
- sync()
2081
- ], ShardInfo.prototype, "status", void 0);
2082
- _ts_decorate([
2083
- sync()
2084
- ], ShardInfo.prototype, "lastHeartbeat", void 0);
2085
- var WorldRoom = class {
2086
- static {
2087
- __name(this, "WorldRoom");
2088
- }
2089
- room;
2090
- // Synchronized state
2091
- rooms;
2092
- shards;
2093
- // Only persisted state (not synced to clients)
2094
- rrCounters;
2095
- // Configuration
2096
- defaultShardUrlTemplate;
2097
- defaultMaxConnectionsPerShard;
2098
- constructor(room) {
2099
- this.room = room;
2100
- this.rooms = signal({});
2101
- this.shards = signal({});
2102
- this.rrCounters = signal({});
2103
- this.defaultShardUrlTemplate = signal("{shardId}");
2104
- this.defaultMaxConnectionsPerShard = signal(MAX_PLAYERS_PER_SHARD);
2105
- const { AUTH_JWT_SECRET, SHARD_SECRET } = this.room.env;
2106
- if (!AUTH_JWT_SECRET) {
2107
- throw new Error("AUTH_JWT_SECRET env variable is not set");
2108
- }
2109
- if (!SHARD_SECRET) {
2110
- throw new Error("SHARD_SECRET env variable is not set");
2111
- }
2112
- }
2113
- async onJoin(user, conn, ctx) {
2114
- const canConnect = await guardManageWorld(user, ctx.request, this.room);
2115
- conn.setState({
2116
- ...conn.state,
2117
- isAdmin: canConnect
2118
- });
2119
- }
2120
- interceptorPacket(_, obj, conn) {
2121
- if (!conn.state["isAdmin"]) {
2122
- return null;
2123
- }
2124
- return obj;
2125
- }
2126
- // Helper methods
2127
- cleanupInactiveShards() {
2128
- const now = Date.now();
2129
- const timeout = 5 * 60 * 1e3;
2130
- const shardsValue = this.shards();
2131
- Object.values(shardsValue).forEach((shard) => {
2132
- if (now - shard.lastHeartbeat() > timeout) {
2133
- delete this.shards()[shard.id];
2134
- }
2135
- });
2136
- setTimeout(() => this.cleanupInactiveShards(), 6e4);
2137
- }
2138
- // Actions
2139
- async registerRoom(req) {
2140
- const roomConfig = await req.json();
2141
- const roomId = roomConfig.name;
2142
- if (!this.rooms()[roomId]) {
2143
- const newRoom = new RoomConfig();
2144
- newRoom.id = roomId;
2145
- newRoom.name.set(roomConfig.name);
2146
- newRoom.balancingStrategy.set(roomConfig.balancingStrategy);
2147
- newRoom.public.set(roomConfig.public);
2148
- newRoom.maxPlayersPerShard.set(roomConfig.maxPlayersPerShard);
2149
- newRoom.minShards.set(roomConfig.minShards);
2150
- newRoom.maxShards.set(roomConfig.maxShards);
2151
- this.rooms()[roomId] = newRoom;
2152
- if (roomConfig.minShards > 0) {
2153
- for (let i = 0; i < roomConfig.minShards; i++) {
2154
- await this.createShard(roomId);
2155
- }
2156
- }
2157
- } else {
2158
- const room = this.rooms()[roomId];
2159
- room.balancingStrategy.set(roomConfig.balancingStrategy);
2160
- room.public.set(roomConfig.public);
2161
- room.maxPlayersPerShard.set(roomConfig.maxPlayersPerShard);
2162
- room.minShards.set(roomConfig.minShards);
2163
- room.maxShards.set(roomConfig.maxShards);
2164
- }
2165
- }
2166
- async updateShardStats(req, res) {
2167
- const body = await req.json();
2168
- const { shardId, connections, status } = body;
2169
- const shard = this.shards()[shardId];
2170
- if (!shard) {
2171
- return res.notFound(`Shard ${shardId} not found`);
2172
- }
2173
- shard.currentConnections.set(connections);
2174
- if (status) {
2175
- shard.status.set(status);
2176
- }
2177
- shard.lastHeartbeat.set(Date.now());
2178
- }
2179
- async scaleRoom(req, res) {
2180
- const data = await req.json();
2181
- const { targetShardCount, shardTemplate, roomId } = data;
2182
- const room = this.rooms()[roomId];
2183
- if (!room) {
2184
- return res.notFound(`Room ${roomId} does not exist`);
2185
- }
2186
- const roomShards = Object.values(this.shards()).filter((shard) => shard.roomId() === roomId);
2187
- const previousShardCount = roomShards.length;
2188
- if (room.maxShards() !== void 0 && targetShardCount > room.maxShards()) {
2189
- return res.badRequest(`Cannot scale beyond maximum allowed shards (${room.maxShards()})`, {
2190
- roomId,
2191
- currentShardCount: previousShardCount
2192
- });
2193
- }
2194
- if (targetShardCount < previousShardCount) {
2195
- const shardsToRemove = [
2196
- ...roomShards
2197
- ].sort((a, b) => {
2198
- if (a.status() === "draining" && b.status() !== "draining") return -1;
2199
- if (a.status() !== "draining" && b.status() === "draining") return 1;
2200
- return a.currentConnections() - b.currentConnections();
2201
- }).slice(0, previousShardCount - targetShardCount);
2202
- roomShards.filter((shard) => !shardsToRemove.some((s) => s.id === shard.id));
2203
- for (const shard of shardsToRemove) {
2204
- delete this.shards()[shard.id];
2205
- }
2206
- return;
2207
- }
2208
- if (targetShardCount > previousShardCount) {
2209
- for (let i = 0; i < targetShardCount - previousShardCount; i++) {
2210
- await this.createShard(roomId, shardTemplate?.urlTemplate, shardTemplate?.maxConnections);
2211
- }
2212
- }
2213
- }
2214
- async connect(req, res) {
2215
- try {
2216
- let data;
2217
- try {
2218
- const body = await req.text();
2219
- if (!body || body.trim() === "") {
2220
- return res.badRequest("Request body is empty");
2221
- }
2222
- data = JSON.parse(body);
2223
- } catch (parseError) {
2224
- return res.badRequest("Invalid JSON in request body");
2225
- }
2226
- if (!data.roomId) {
2227
- return res.badRequest("roomId parameter is required");
2228
- }
2229
- const autoCreate = data.autoCreate !== void 0 ? data.autoCreate : true;
2230
- const result = await this.findOptimalShard(data.roomId, autoCreate);
2231
- if ("error" in result) {
2232
- return res.notFound(result.error);
2233
- }
2234
- return res.success({
2235
- success: true,
2236
- shardId: result.shardId,
2237
- url: result.url
2238
- });
2239
- } catch (error) {
2240
- console.error("Error connecting to shard:", error);
2241
- return res.serverError();
2242
- }
2243
- }
2244
- async findOptimalShard(roomId, autoCreate = true) {
2245
- let room = this.rooms()[roomId];
2246
- if (!room) {
2247
- if (autoCreate) {
2248
- const mockRequest = {
2249
- json: /* @__PURE__ */ __name(async () => ({
2250
- name: roomId,
2251
- balancingStrategy: "round-robin",
2252
- public: true,
2253
- maxPlayersPerShard: this.defaultMaxConnectionsPerShard(),
2254
- minShards: 1,
2255
- maxShards: void 0
2256
- }), "json")
2257
- };
2258
- await this.registerRoom(mockRequest);
2259
- room = this.rooms()[roomId];
2260
- if (!room) {
2261
- return {
2262
- error: `Failed to create room ${roomId}`
2263
- };
2264
- }
2265
- } else {
2266
- return {
2267
- error: `Room ${roomId} does not exist`
2268
- };
2269
- }
2270
- }
2271
- const roomShards = Object.values(this.shards()).filter((shard) => shard.roomId() === roomId);
2272
- if (roomShards.length === 0) {
2273
- if (autoCreate) {
2274
- const newShard = await this.createShard(roomId);
2275
- if (newShard) {
2276
- return {
2277
- shardId: newShard.id,
2278
- url: newShard.url()
2279
- };
2280
- } else {
2281
- return {
2282
- error: `Failed to create shard for room ${roomId}`
2283
- };
2284
- }
2285
- } else {
2286
- return {
2287
- error: `No shards available for room ${roomId}`
2288
- };
2289
- }
2290
- }
2291
- const activeShards = roomShards.filter((shard) => shard && shard.status() === "active");
2292
- if (activeShards.length === 0) {
2293
- return {
2294
- error: `No active shards available for room ${roomId}`
2295
- };
2296
- }
2297
- const balancingStrategy = room.balancingStrategy();
2298
- let selectedShard;
2299
- switch (balancingStrategy) {
2300
- case "least-connections":
2301
- selectedShard = activeShards.reduce((min, shard) => shard.currentConnections() < min.currentConnections() ? shard : min, activeShards[0]);
2302
- break;
2303
- case "random":
2304
- selectedShard = activeShards[Math.floor(Math.random() * activeShards.length)];
2305
- break;
2306
- case "round-robin":
2307
- default:
2308
- const counter = this.rrCounters()[roomId] || 0;
2309
- const nextCounter = (counter + 1) % activeShards.length;
2310
- this.rrCounters()[roomId] = nextCounter;
2311
- selectedShard = activeShards[counter];
2312
- break;
2313
- }
2314
- return {
2315
- shardId: selectedShard.id,
2316
- url: selectedShard.url()
2317
- };
2318
- }
2319
- // Private methods
2320
- async createShard(roomId, urlTemplate, maxConnections) {
2321
- const room = this.rooms()[roomId];
2322
- if (!room) {
2323
- console.error(`Cannot create shard for non-existent room: ${roomId}`);
2324
- return null;
2325
- }
2326
- const shardId = `${roomId}:${Date.now()}-${Math.floor(Math.random() * 1e4)}`;
2327
- const template = urlTemplate || this.defaultShardUrlTemplate();
2328
- const url = template.replace("{shardId}", shardId).replace("{roomId}", roomId);
2329
- const max = maxConnections || room.maxPlayersPerShard();
2330
- const newShard = new ShardInfo();
2331
- newShard.id = shardId;
2332
- newShard.roomId.set(roomId);
2333
- newShard.url.set(url);
2334
- newShard.maxConnections.set(max);
2335
- newShard.currentConnections.set(0);
2336
- newShard.status.set("active");
2337
- newShard.lastHeartbeat.set(Date.now());
2338
- this.shards()[shardId] = newShard;
2339
- return newShard;
2340
- }
2341
- };
2342
- _ts_decorate([
2343
- sync(RoomConfig)
2344
- ], WorldRoom.prototype, "rooms", void 0);
2345
- _ts_decorate([
2346
- sync(ShardInfo)
2347
- ], WorldRoom.prototype, "shards", void 0);
2348
- _ts_decorate([
2349
- persist()
2350
- ], WorldRoom.prototype, "rrCounters", void 0);
2351
- _ts_decorate([
2352
- Request2({
2353
- path: "register-room",
2354
- method: "POST"
2355
- }),
2356
- Guard([
2357
- guardManageWorld
2358
- ]),
2359
- _ts_metadata("design:type", Function),
2360
- _ts_metadata("design:paramtypes", [
2361
- Object
2362
- ]),
2363
- _ts_metadata("design:returntype", Promise)
2364
- ], WorldRoom.prototype, "registerRoom", null);
2365
- _ts_decorate([
2366
- Request2({
2367
- path: "update-shard",
2368
- method: "POST"
2369
- }),
2370
- Guard([
2371
- guardManageWorld
2372
- ]),
2373
- _ts_metadata("design:type", Function),
2374
- _ts_metadata("design:paramtypes", [
2375
- Object ,
2376
- typeof ServerResponse === "undefined" ? Object : ServerResponse
2377
- ]),
2378
- _ts_metadata("design:returntype", Promise)
2379
- ], WorldRoom.prototype, "updateShardStats", null);
2380
- _ts_decorate([
2381
- Request2({
2382
- path: "scale-room",
2383
- method: "POST"
2384
- }),
2385
- Guard([
2386
- guardManageWorld
2387
- ]),
2388
- _ts_metadata("design:type", Function),
2389
- _ts_metadata("design:paramtypes", [
2390
- Object ,
2391
- typeof ServerResponse === "undefined" ? Object : ServerResponse
2392
- ]),
2393
- _ts_metadata("design:returntype", Promise)
2394
- ], WorldRoom.prototype, "scaleRoom", null);
2395
- _ts_decorate([
2396
- Request2({
2397
- path: "connect",
2398
- method: "POST"
2399
- }),
2400
- _ts_metadata("design:type", Function),
2401
- _ts_metadata("design:paramtypes", [
2402
- Object ,
2403
- typeof ServerResponse === "undefined" ? Object : ServerResponse
2404
- ]),
2405
- _ts_metadata("design:returntype", Promise)
2406
- ], WorldRoom.prototype, "connect", null);
2407
- WorldRoom = _ts_decorate([
2408
- Room({
2409
- path: "world-{worldId}",
2410
- maxUsers: 100,
2411
- throttleStorage: 2e3,
2412
- throttleSync: 500
2413
- }),
2414
- _ts_metadata("design:type", Function),
2415
- _ts_metadata("design:paramtypes", [
2416
- Object
2417
- ])
2418
- ], WorldRoom);
111
+ __name(getOptimalShard, "getOptimalShard");
2419
112
 
2420
- export { Action, ClientIo, Guard, MockConnection, Request2 as Request, Room, RoomGuard, Server, ServerIo, ServerResponse, Shard, WorldRoom, request, testRoom };
113
+ export { PartySocket, connectionRoom, connectionWorld };
2421
114
  //# sourceMappingURL=index21.js.map