@rpgjs/client 5.0.0-alpha.9 → 5.0.0-beta.10
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/CHANGELOG.md +37 -0
- package/LICENSE +19 -0
- package/dist/Game/AnimationManager.d.ts +8 -0
- package/dist/Game/AnimationManager.js +35 -0
- package/dist/Game/AnimationManager.js.map +1 -0
- package/dist/Game/AnimationManager.spec.d.ts +1 -0
- package/dist/Game/Event.d.ts +1 -1
- package/dist/Game/Event.js +12 -0
- package/dist/Game/Event.js.map +1 -0
- package/dist/Game/Map.d.ts +31 -2
- package/dist/Game/Map.js +138 -0
- package/dist/Game/Map.js.map +1 -0
- package/dist/Game/Object.d.ts +189 -0
- package/dist/Game/Object.js +255 -0
- package/dist/Game/Object.js.map +1 -0
- package/dist/Game/Player.d.ts +1 -1
- package/dist/Game/Player.js +12 -0
- package/dist/Game/Player.js.map +1 -0
- package/dist/Gui/Gui.d.ts +192 -7
- package/dist/Gui/Gui.js +475 -0
- package/dist/Gui/Gui.js.map +1 -0
- package/dist/Gui/Gui.spec.d.ts +1 -0
- package/dist/Gui/NotificationManager.d.ts +23 -0
- package/dist/Gui/NotificationManager.js +49 -0
- package/dist/Gui/NotificationManager.js.map +1 -0
- package/dist/Resource.d.ts +97 -0
- package/dist/Resource.js +133 -0
- package/dist/Resource.js.map +1 -0
- package/dist/RpgClient.d.ts +295 -13
- package/dist/RpgClientEngine.d.ts +671 -15
- package/dist/RpgClientEngine.js +1442 -0
- package/dist/RpgClientEngine.js.map +1 -0
- package/dist/Sound.d.ts +199 -0
- package/dist/Sound.js +167 -0
- package/dist/Sound.js.map +1 -0
- package/dist/_virtual/_@oxc-project_runtime@0.130.0/helpers/decorate.js +9 -0
- package/dist/_virtual/_@oxc-project_runtime@0.130.0/helpers/decorateMetadata.js +6 -0
- package/dist/components/animations/animation.ce.js +23 -0
- package/dist/components/animations/animation.ce.js.map +1 -0
- package/dist/components/animations/hit.ce.js +64 -0
- package/dist/components/animations/hit.ce.js.map +1 -0
- package/dist/components/animations/index.d.ts +4 -0
- package/dist/components/animations/index.js +11 -0
- package/dist/components/animations/index.js.map +1 -0
- package/dist/components/character.ce.js +572 -0
- package/dist/components/character.ce.js.map +1 -0
- package/dist/components/dynamics/bar.ce.js +96 -0
- package/dist/components/dynamics/bar.ce.js.map +1 -0
- package/dist/components/dynamics/image.ce.js +23 -0
- package/dist/components/dynamics/image.ce.js.map +1 -0
- package/dist/components/dynamics/parse-value.d.ts +4 -0
- package/dist/components/dynamics/parse-value.js +63 -0
- package/dist/components/dynamics/parse-value.js.map +1 -0
- package/dist/components/dynamics/parse-value.spec.d.ts +1 -0
- package/dist/components/dynamics/shape-utils.d.ts +16 -0
- package/dist/components/dynamics/shape-utils.js +73 -0
- package/dist/components/dynamics/shape-utils.js.map +1 -0
- package/dist/components/dynamics/shape-utils.spec.d.ts +1 -0
- package/dist/components/dynamics/shape.ce.js +83 -0
- package/dist/components/dynamics/shape.ce.js.map +1 -0
- package/dist/components/dynamics/text.ce.js +50 -0
- package/dist/components/dynamics/text.ce.js.map +1 -0
- package/dist/components/gui/box.ce.js +26 -0
- package/dist/components/gui/box.ce.js.map +1 -0
- package/dist/components/gui/dialogbox/index.ce.js +198 -0
- package/dist/components/gui/dialogbox/index.ce.js.map +1 -0
- package/dist/components/gui/gameover.ce.js +169 -0
- package/dist/components/gui/gameover.ce.js.map +1 -0
- package/dist/components/gui/hud/hud.ce.js +83 -0
- package/dist/components/gui/hud/hud.ce.js.map +1 -0
- package/dist/components/gui/index.d.ts +15 -3
- package/dist/components/gui/index.js +14 -0
- package/dist/components/gui/menu/equip-menu.ce.js +427 -0
- package/dist/components/gui/menu/equip-menu.ce.js.map +1 -0
- package/dist/components/gui/menu/exit-menu.ce.js +55 -0
- package/dist/components/gui/menu/exit-menu.ce.js.map +1 -0
- package/dist/components/gui/menu/items-menu.ce.js +326 -0
- package/dist/components/gui/menu/items-menu.ce.js.map +1 -0
- package/dist/components/gui/menu/main-menu.ce.js +399 -0
- package/dist/components/gui/menu/main-menu.ce.js.map +1 -0
- package/dist/components/gui/menu/options-menu.ce.js +49 -0
- package/dist/components/gui/menu/options-menu.ce.js.map +1 -0
- package/dist/components/gui/menu/skills-menu.ce.js +102 -0
- package/dist/components/gui/menu/skills-menu.ce.js.map +1 -0
- package/dist/components/gui/mobile/index.d.ts +8 -0
- package/dist/components/gui/mobile/index.js +21 -0
- package/dist/components/gui/mobile/index.js.map +1 -0
- package/dist/components/gui/mobile/mobile.ce.js +79 -0
- package/dist/components/gui/mobile/mobile.ce.js.map +1 -0
- package/dist/components/gui/notification/notification.ce.js +62 -0
- package/dist/components/gui/notification/notification.ce.js.map +1 -0
- package/dist/components/gui/save-load.ce.js +211 -0
- package/dist/components/gui/save-load.ce.js.map +1 -0
- package/dist/components/gui/shop/shop.ce.js +614 -0
- package/dist/components/gui/shop/shop.ce.js.map +1 -0
- package/dist/components/gui/title-screen.ce.js +164 -0
- package/dist/components/gui/title-screen.ce.js.map +1 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/components/index.js +4 -0
- package/dist/components/player-components-utils.d.ts +67 -0
- package/dist/components/player-components-utils.js +162 -0
- package/dist/components/player-components-utils.js.map +1 -0
- package/dist/components/player-components-utils.spec.d.ts +1 -0
- package/dist/components/player-components.ce.js +188 -0
- package/dist/components/player-components.ce.js.map +1 -0
- package/dist/components/prebuilt/hp-bar.ce.js +113 -0
- package/dist/components/prebuilt/hp-bar.ce.js.map +1 -0
- package/dist/components/prebuilt/index.d.ts +19 -0
- package/dist/components/prebuilt/index.js +2 -0
- package/dist/components/prebuilt/light-halo.ce.js +70 -0
- package/dist/components/prebuilt/light-halo.ce.js.map +1 -0
- package/dist/components/scenes/canvas.ce.js +196 -0
- package/dist/components/scenes/canvas.ce.js.map +1 -0
- package/dist/components/scenes/draw-map.ce.js +79 -0
- package/dist/components/scenes/draw-map.ce.js.map +1 -0
- package/dist/components/scenes/event-layer.ce.js +29 -0
- package/dist/components/scenes/event-layer.ce.js.map +1 -0
- package/dist/core/inject.js +18 -0
- package/dist/core/inject.js.map +1 -0
- package/dist/core/setup.js +16 -0
- package/dist/core/setup.js.map +1 -0
- package/dist/decorators/spritesheet.d.ts +1 -0
- package/dist/decorators/spritesheet.js +11 -0
- package/dist/decorators/spritesheet.js.map +1 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.js +45 -14
- package/dist/module.d.ts +43 -4
- package/dist/module.js +179 -0
- package/dist/module.js.map +1 -0
- package/dist/node_modules/.pnpm/@signe_di@3.0.1/node_modules/@signe/di/dist/index.js +167 -0
- package/dist/node_modules/.pnpm/@signe_di@3.0.1/node_modules/@signe/di/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@signe_reactive@3.0.1/node_modules/@signe/reactive/dist/index.js +239 -0
- package/dist/node_modules/.pnpm/@signe_reactive@3.0.1/node_modules/@signe/reactive/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/chunk-EUXUH3YW.js +13 -0
- package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/chunk-EUXUH3YW.js.map +1 -0
- package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/index.js +696 -0
- package/dist/node_modules/.pnpm/@signe_room@3.0.1/node_modules/@signe/room/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/client/index.js +44 -0
- package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/client/index.js.map +1 -0
- package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/index.js +241 -0
- package/dist/node_modules/.pnpm/@signe_sync@3.0.1/node_modules/@signe/sync/dist/index.js.map +1 -0
- package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-HAC622V3.js +115 -0
- package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-HAC622V3.js.map +1 -0
- package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-S74YV6PU.js +401 -0
- package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/chunk-S74YV6PU.js.map +1 -0
- package/dist/node_modules/.pnpm/partysocket@1.1.3/node_modules/partysocket/dist/index.js +2 -0
- package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js +3756 -0
- package/dist/node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.js.map +1 -0
- package/dist/presets/animation.d.ts +31 -0
- package/dist/presets/animation.js +39 -0
- package/dist/presets/animation.js.map +1 -0
- package/dist/presets/faceset.d.ts +30 -0
- package/dist/presets/faceset.js +51 -0
- package/dist/presets/faceset.js.map +1 -0
- package/dist/presets/icon.d.ts +20 -0
- package/dist/presets/icon.js +15 -0
- package/dist/presets/icon.js.map +1 -0
- package/dist/presets/index.d.ts +123 -0
- package/dist/presets/index.js +17 -0
- package/dist/presets/index.js.map +1 -0
- package/dist/presets/lpc.d.ts +89 -0
- package/dist/presets/lpc.js +98 -0
- package/dist/presets/lpc.js.map +1 -0
- package/dist/presets/rmspritesheet.js +42 -0
- package/dist/presets/rmspritesheet.js.map +1 -0
- package/dist/services/AbstractSocket.d.ts +9 -5
- package/dist/services/AbstractSocket.js +11 -0
- package/dist/services/AbstractSocket.js.map +1 -0
- package/dist/services/keyboardControls.d.ts +15 -0
- package/dist/services/keyboardControls.js +23 -0
- package/dist/services/keyboardControls.js.map +1 -0
- package/dist/services/loadMap.d.ts +6 -0
- package/dist/services/loadMap.js +123 -0
- package/dist/services/loadMap.js.map +1 -0
- package/dist/services/mmorpg.d.ts +21 -9
- package/dist/services/mmorpg.js +136 -0
- package/dist/services/mmorpg.js.map +1 -0
- package/dist/services/save.d.ts +19 -0
- package/dist/services/save.js +77 -0
- package/dist/services/save.js.map +1 -0
- package/dist/services/save.spec.d.ts +1 -0
- package/dist/services/standalone.d.ts +67 -7
- package/dist/services/standalone.js +168 -0
- package/dist/services/standalone.js.map +1 -0
- package/dist/utils/getEntityProp.d.ts +39 -0
- package/dist/utils/getEntityProp.js +53 -0
- package/dist/utils/getEntityProp.js.map +1 -0
- package/dist/utils/getEntityProp.spec.d.ts +1 -0
- package/dist/utils/readPropValue.d.ts +2 -0
- package/dist/utils/readPropValue.js +13 -0
- package/dist/utils/readPropValue.js.map +1 -0
- package/package.json +14 -11
- package/src/Game/AnimationManager.spec.ts +30 -0
- package/src/Game/AnimationManager.ts +33 -0
- package/src/Game/Event.ts +1 -1
- package/src/Game/Map.ts +184 -3
- package/src/Game/Object.ts +409 -14
- package/src/Game/Player.ts +1 -1
- package/src/Gui/Gui.spec.ts +273 -0
- package/src/Gui/Gui.ts +566 -23
- package/src/Gui/NotificationManager.ts +69 -0
- package/src/Resource.ts +149 -0
- package/src/RpgClient.ts +309 -14
- package/src/RpgClientEngine.ts +1790 -63
- package/src/Sound.ts +253 -0
- package/src/components/{effects → animations}/animation.ce +3 -6
- package/src/components/{effects → animations}/index.ts +1 -1
- package/src/components/character.ce +801 -59
- package/src/components/dynamics/bar.ce +87 -0
- package/src/components/dynamics/image.ce +20 -0
- package/src/components/dynamics/parse-value.spec.ts +83 -0
- package/src/components/dynamics/parse-value.ts +154 -0
- package/src/components/dynamics/shape-utils.spec.ts +46 -0
- package/src/components/dynamics/shape-utils.ts +61 -0
- package/src/components/dynamics/shape.ce +89 -0
- package/src/components/dynamics/text.ce +68 -0
- package/src/components/gui/box.ce +17 -0
- package/src/components/gui/dialogbox/index.ce +213 -187
- package/src/components/gui/gameover.ce +158 -0
- package/src/components/gui/hud/hud.ce +61 -0
- package/src/components/gui/index.ts +30 -4
- package/src/components/gui/menu/equip-menu.ce +410 -0
- package/src/components/gui/menu/exit-menu.ce +41 -0
- package/src/components/gui/menu/items-menu.ce +317 -0
- package/src/components/gui/menu/main-menu.ce +294 -0
- package/src/components/gui/menu/options-menu.ce +35 -0
- package/src/components/gui/menu/skills-menu.ce +83 -0
- package/src/components/gui/mobile/index.ts +24 -0
- package/src/components/gui/mobile/mobile.ce +80 -0
- package/src/components/gui/notification/notification.ce +51 -0
- package/src/components/gui/save-load.ce +208 -0
- package/src/components/gui/shop/shop.ce +493 -0
- package/src/components/gui/title-screen.ce +163 -0
- package/src/components/index.ts +3 -0
- package/src/components/player-components-utils.spec.ts +109 -0
- package/src/components/player-components-utils.ts +205 -0
- package/src/components/player-components.ce +221 -0
- package/src/components/prebuilt/hp-bar.ce +255 -0
- package/src/components/prebuilt/index.ts +24 -0
- package/src/components/prebuilt/light-halo.ce +148 -0
- package/src/components/scenes/canvas.ce +185 -21
- package/src/components/scenes/draw-map.ce +55 -21
- package/src/components/scenes/event-layer.ce +8 -2
- package/src/components/scenes/transition.ce +60 -0
- package/src/core/setup.ts +2 -2
- package/src/decorators/spritesheet.ts +8 -0
- package/src/index.ts +17 -2
- package/src/module.ts +132 -10
- package/src/presets/animation.ts +46 -0
- package/src/presets/faceset.ts +60 -0
- package/src/presets/icon.ts +17 -0
- package/src/presets/index.ts +9 -1
- package/src/presets/lpc.ts +108 -0
- package/src/services/AbstractSocket.ts +10 -2
- package/src/services/keyboardControls.ts +20 -0
- package/src/services/loadMap.ts +3 -1
- package/src/services/mmorpg.ts +106 -12
- package/src/services/save.spec.ts +127 -0
- package/src/services/save.ts +103 -0
- package/src/services/standalone.ts +110 -18
- package/src/utils/getEntityProp.spec.ts +96 -0
- package/src/utils/getEntityProp.ts +88 -0
- package/src/utils/readPropValue.ts +16 -0
- package/vite.config.ts +4 -2
- package/dist/Game/EffectManager.d.ts +0 -5
- package/dist/components/effects/index.d.ts +0 -4
- package/dist/index.js.map +0 -1
- package/dist/index10.js +0 -8
- package/dist/index10.js.map +0 -1
- package/dist/index11.js +0 -10
- package/dist/index11.js.map +0 -1
- package/dist/index12.js +0 -8
- package/dist/index12.js.map +0 -1
- package/dist/index13.js +0 -17
- package/dist/index13.js.map +0 -1
- package/dist/index14.js +0 -107
- package/dist/index14.js.map +0 -1
- package/dist/index15.js +0 -50
- package/dist/index15.js.map +0 -1
- package/dist/index16.js +0 -191
- package/dist/index16.js.map +0 -1
- package/dist/index17.js +0 -9
- package/dist/index17.js.map +0 -1
- package/dist/index18.js +0 -387
- package/dist/index18.js.map +0 -1
- package/dist/index19.js +0 -31
- package/dist/index19.js.map +0 -1
- package/dist/index2.js +0 -181
- package/dist/index2.js.map +0 -1
- package/dist/index20.js +0 -24
- package/dist/index20.js.map +0 -1
- package/dist/index21.js +0 -2421
- package/dist/index21.js.map +0 -1
- package/dist/index22.js +0 -114
- package/dist/index22.js.map +0 -1
- package/dist/index23.js +0 -109
- package/dist/index23.js.map +0 -1
- package/dist/index24.js +0 -71
- package/dist/index24.js.map +0 -1
- package/dist/index25.js +0 -21
- package/dist/index25.js.map +0 -1
- package/dist/index26.js +0 -41
- package/dist/index26.js.map +0 -1
- package/dist/index27.js +0 -5
- package/dist/index27.js.map +0 -1
- package/dist/index28.js +0 -322
- package/dist/index28.js.map +0 -1
- package/dist/index29.js +0 -27
- package/dist/index29.js.map +0 -1
- package/dist/index3.js +0 -87
- package/dist/index3.js.map +0 -1
- package/dist/index30.js +0 -11
- package/dist/index30.js.map +0 -1
- package/dist/index31.js +0 -11
- package/dist/index31.js.map +0 -1
- package/dist/index32.js +0 -174
- package/dist/index32.js.map +0 -1
- package/dist/index33.js +0 -501
- package/dist/index33.js.map +0 -1
- package/dist/index34.js +0 -12
- package/dist/index34.js.map +0 -1
- package/dist/index35.js +0 -4403
- package/dist/index35.js.map +0 -1
- package/dist/index36.js +0 -316
- package/dist/index36.js.map +0 -1
- package/dist/index37.js +0 -61
- package/dist/index37.js.map +0 -1
- package/dist/index38.js +0 -20
- package/dist/index38.js.map +0 -1
- package/dist/index39.js +0 -20
- package/dist/index39.js.map +0 -1
- package/dist/index4.js +0 -67
- package/dist/index4.js.map +0 -1
- package/dist/index5.js +0 -16
- package/dist/index5.js.map +0 -1
- package/dist/index6.js +0 -17
- package/dist/index6.js.map +0 -1
- package/dist/index7.js +0 -39
- package/dist/index7.js.map +0 -1
- package/dist/index8.js +0 -108
- package/dist/index8.js.map +0 -1
- package/dist/index9.js +0 -76
- package/dist/index9.js.map +0 -1
- package/src/Game/EffectManager.ts +0 -20
- package/src/components/gui/dialogbox/itemMenu.ce +0 -23
- package/src/components/gui/dialogbox/selection.ce +0 -67
- /package/src/components/{effects → animations}/hit.ce +0 -0
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
import { signal } from "../../../../../@signe_reactive@3.0.1/node_modules/@signe/reactive/dist/index.js";
|
|
2
|
+
import { id, persist, sync } from "../../../../../@signe_sync@3.0.1/node_modules/@signe/sync/dist/index.js";
|
|
3
|
+
import { __decorateClass } from "./chunk-EUXUH3YW.js";
|
|
4
|
+
import { z } from "../../../../../zod@3.24.2/node_modules/zod/lib/index.js";
|
|
5
|
+
//#region ../../node_modules/.pnpm/@signe+room@3.0.1/node_modules/@signe/room/dist/index.js
|
|
6
|
+
function Request2(options, bodyValidation) {
|
|
7
|
+
return function(target, propertyKey) {
|
|
8
|
+
if (!target.constructor._requestMetadata) target.constructor._requestMetadata = /* @__PURE__ */ new Map();
|
|
9
|
+
const path = options.path.startsWith("/") ? options.path : `/${options.path}`;
|
|
10
|
+
const method = options.method || "GET";
|
|
11
|
+
const routeKey = `${method}:${path}`;
|
|
12
|
+
target.constructor._requestMetadata.set(routeKey, {
|
|
13
|
+
key: propertyKey,
|
|
14
|
+
path,
|
|
15
|
+
method,
|
|
16
|
+
bodyValidation
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function Room(options) {
|
|
21
|
+
return function(target) {
|
|
22
|
+
target.path = options.path;
|
|
23
|
+
target.prototype.maxUsers = options.maxUsers;
|
|
24
|
+
target.prototype.throttleStorage = options.throttleStorage;
|
|
25
|
+
target.prototype.throttleSync = options.throttleSync;
|
|
26
|
+
target.prototype.sessionExpiryTime = options.sessionExpiryTime ?? 300 * 1e3;
|
|
27
|
+
if (options.guards) target["_roomGuards"] = options.guards;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function Guard(guards) {
|
|
31
|
+
return function(target, propertyKey, descriptor) {
|
|
32
|
+
if (!target.constructor["_actionGuards"]) target.constructor["_actionGuards"] = /* @__PURE__ */ new Map();
|
|
33
|
+
if (!Array.isArray(guards)) guards = [guards];
|
|
34
|
+
target.constructor["_actionGuards"].set(propertyKey, guards);
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function generateShortUUID() {
|
|
38
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
39
|
+
let uuid = "";
|
|
40
|
+
for (let i = 0; i < 8; i++) {
|
|
41
|
+
const randomIndex = Math.floor(Math.random() * 62);
|
|
42
|
+
uuid += chars[randomIndex];
|
|
43
|
+
}
|
|
44
|
+
return uuid;
|
|
45
|
+
}
|
|
46
|
+
var Storage = class {
|
|
47
|
+
constructor() {
|
|
48
|
+
this.memory = /* @__PURE__ */ new Map();
|
|
49
|
+
}
|
|
50
|
+
async put(key, value) {
|
|
51
|
+
if (typeof key === "string") {
|
|
52
|
+
this.memory.set(key, value);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
for (const [entryKey, entryValue] of Object.entries(key)) this.memory.set(entryKey, entryValue);
|
|
56
|
+
}
|
|
57
|
+
async get(key) {
|
|
58
|
+
return this.memory.get(key);
|
|
59
|
+
}
|
|
60
|
+
async delete(key) {
|
|
61
|
+
if (Array.isArray(key)) {
|
|
62
|
+
let deleted = 0;
|
|
63
|
+
for (const entryKey of key) if (this.memory.delete(entryKey)) deleted += 1;
|
|
64
|
+
return deleted;
|
|
65
|
+
}
|
|
66
|
+
return this.memory.delete(key);
|
|
67
|
+
}
|
|
68
|
+
async list(options) {
|
|
69
|
+
if (!options?.prefix) return this.memory;
|
|
70
|
+
return new Map(Array.from(this.memory.entries()).filter(([key]) => String(key).startsWith(options.prefix)));
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
z.object({
|
|
74
|
+
action: z.string(),
|
|
75
|
+
value: z.any()
|
|
76
|
+
});
|
|
77
|
+
var INTERNAL_PREFIX = "$room:";
|
|
78
|
+
`${INTERNAL_PREFIX}`;
|
|
79
|
+
`${INTERNAL_PREFIX}`;
|
|
80
|
+
async function request(room, path, options = { method: "GET" }) {
|
|
81
|
+
const url = new URL("http://localhost" + path);
|
|
82
|
+
const request2 = new Request(url.toString(), options);
|
|
83
|
+
return await room.onRequest(request2);
|
|
84
|
+
}
|
|
85
|
+
var MockPartyClient = class {
|
|
86
|
+
constructor(server, sessionId) {
|
|
87
|
+
this.server = server;
|
|
88
|
+
this.events = /* @__PURE__ */ new Map();
|
|
89
|
+
this.id = generateShortUUID();
|
|
90
|
+
this.conn = new MockConnection(this, sessionId || this.id);
|
|
91
|
+
}
|
|
92
|
+
addEventListener(event, cb) {
|
|
93
|
+
if (!this.events.has(event)) this.events.set(event, []);
|
|
94
|
+
this.events.get(event).push(cb);
|
|
95
|
+
}
|
|
96
|
+
removeEventListener(event, cb) {
|
|
97
|
+
if (!this.events.has(event)) return;
|
|
98
|
+
const callbacks = this.events.get(event);
|
|
99
|
+
const index = callbacks.indexOf(cb);
|
|
100
|
+
if (index !== -1) callbacks.splice(index, 1);
|
|
101
|
+
if (callbacks.length === 0) this.events.delete(event);
|
|
102
|
+
}
|
|
103
|
+
_trigger(event, data) {
|
|
104
|
+
const callbacks = this.events.get(event);
|
|
105
|
+
if (callbacks) for (const cb of callbacks) cb(data);
|
|
106
|
+
}
|
|
107
|
+
send(data) {
|
|
108
|
+
return this.server.onMessage(JSON.stringify(data), this.conn);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
var MockLobby = class {
|
|
112
|
+
constructor(server, lobbyId) {
|
|
113
|
+
this.server = server;
|
|
114
|
+
this.lobbyId = lobbyId;
|
|
115
|
+
}
|
|
116
|
+
socket(init) {
|
|
117
|
+
return new MockPartyClient(this.server, init?.id);
|
|
118
|
+
}
|
|
119
|
+
async connection(idOrOptions, maybeOptions) {
|
|
120
|
+
const id2 = typeof idOrOptions === "string" ? idOrOptions : idOrOptions?.id;
|
|
121
|
+
const options = (typeof idOrOptions === "string" ? maybeOptions : idOrOptions) || {};
|
|
122
|
+
return this.server.room.connection(this.server, id2, options);
|
|
123
|
+
}
|
|
124
|
+
fetch(url, options) {
|
|
125
|
+
const baseUrl = url.includes("shard") ? "" : "/parties/main/" + this.lobbyId;
|
|
126
|
+
return request(this.server, baseUrl + url, options);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
var MockContext = class {
|
|
130
|
+
constructor(room, options = {}) {
|
|
131
|
+
this.room = room;
|
|
132
|
+
this.parties = { main: /* @__PURE__ */ new Map() };
|
|
133
|
+
const parties = options.parties || {};
|
|
134
|
+
if (options.partyFn) {
|
|
135
|
+
const serverCache = /* @__PURE__ */ new Map();
|
|
136
|
+
this.parties.main = { get: async (lobbyId) => {
|
|
137
|
+
if (!serverCache.has(lobbyId)) {
|
|
138
|
+
const server = await options.partyFn(lobbyId);
|
|
139
|
+
serverCache.set(lobbyId, new MockLobby(server, lobbyId));
|
|
140
|
+
}
|
|
141
|
+
return serverCache.get(lobbyId);
|
|
142
|
+
} };
|
|
143
|
+
} else for (let lobbyId in parties) {
|
|
144
|
+
const server = parties[lobbyId](room);
|
|
145
|
+
this.parties.main.set(lobbyId, new MockLobby(server, lobbyId));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
var MockPartyRoom = class {
|
|
150
|
+
constructor(id2, options = {}) {
|
|
151
|
+
this.id = id2;
|
|
152
|
+
this.clients = /* @__PURE__ */ new Map();
|
|
153
|
+
this.storage = new Storage();
|
|
154
|
+
this.env = {};
|
|
155
|
+
this.id = id2 || generateShortUUID();
|
|
156
|
+
this.context = new MockContext(this, {
|
|
157
|
+
parties: options.parties,
|
|
158
|
+
partyFn: options.partyFn
|
|
159
|
+
});
|
|
160
|
+
this.env = options.env || {};
|
|
161
|
+
}
|
|
162
|
+
async connection(server, id2, opts) {
|
|
163
|
+
const socket = new MockPartyClient(server, id2);
|
|
164
|
+
const url = new URL("http://localhost");
|
|
165
|
+
if (opts?.query) for (const [key, value] of Object.entries(opts.query)) url.searchParams.set(key, String(value));
|
|
166
|
+
const request2 = new Request(url.toString(), {
|
|
167
|
+
method: "GET",
|
|
168
|
+
headers: {
|
|
169
|
+
"Content-Type": "application/json",
|
|
170
|
+
...opts?.headers || {}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
await server.onConnect(socket.conn, { request: request2 });
|
|
174
|
+
this.clients.set(socket.id, socket);
|
|
175
|
+
return socket;
|
|
176
|
+
}
|
|
177
|
+
broadcast(data) {
|
|
178
|
+
this.clients.forEach((client) => {
|
|
179
|
+
client._trigger("message", data);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
getConnection(id2) {
|
|
183
|
+
let connection;
|
|
184
|
+
for (const client of this.clients.values()) if (client.conn.id === id2 || client.conn.sessionId === id2) connection = client.conn;
|
|
185
|
+
return connection;
|
|
186
|
+
}
|
|
187
|
+
getConnections() {
|
|
188
|
+
return Array.from(this.clients.values()).map((client) => client.conn);
|
|
189
|
+
}
|
|
190
|
+
clear() {
|
|
191
|
+
this.clients.clear();
|
|
192
|
+
}
|
|
193
|
+
deleteConnection(id2, connection) {
|
|
194
|
+
if (connection) {
|
|
195
|
+
this.clients.delete(connection.id);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
for (const [connectionKey, client] of this.clients) if (client.conn.id === id2 || client.conn.sessionId === id2) this.clients.delete(connectionKey);
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
var MockConnection = class {
|
|
202
|
+
constructor(client, sessionId) {
|
|
203
|
+
this.client = client;
|
|
204
|
+
this.state = {};
|
|
205
|
+
this.server = client.server;
|
|
206
|
+
this.id = client.id;
|
|
207
|
+
this.sessionId = sessionId;
|
|
208
|
+
}
|
|
209
|
+
setState(value) {
|
|
210
|
+
this.state = value;
|
|
211
|
+
}
|
|
212
|
+
send(data) {
|
|
213
|
+
this.client._trigger("message", data);
|
|
214
|
+
}
|
|
215
|
+
close() {
|
|
216
|
+
this.server.room.deleteConnection?.(this.id, this);
|
|
217
|
+
this.server.onClose(this);
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
var ServerIo = MockPartyRoom;
|
|
221
|
+
var ClientIo = MockPartyClient;
|
|
222
|
+
var JWTAuth = class {
|
|
223
|
+
/**
|
|
224
|
+
* Constructor for the JWTAuth class
|
|
225
|
+
* @param {string} secret - The secret key used for signing and verifying tokens
|
|
226
|
+
*/
|
|
227
|
+
constructor(secret) {
|
|
228
|
+
if (!secret || typeof secret !== "string") throw new Error("Secret is required and must be a string");
|
|
229
|
+
this.secret = secret;
|
|
230
|
+
this.encoder = new TextEncoder();
|
|
231
|
+
this.decoder = new TextDecoder();
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Convert the secret to a CryptoKey for HMAC operations
|
|
235
|
+
* @returns {Promise<CryptoKey>} - The CryptoKey for HMAC operations
|
|
236
|
+
*/
|
|
237
|
+
async getSecretKey() {
|
|
238
|
+
const keyData = this.encoder.encode(this.secret);
|
|
239
|
+
return await crypto.subtle.importKey("raw", keyData, {
|
|
240
|
+
name: "HMAC",
|
|
241
|
+
hash: { name: "SHA-256" }
|
|
242
|
+
}, false, ["sign", "verify"]);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Base64Url encode a buffer
|
|
246
|
+
* @param {ArrayBuffer} buffer - The buffer to encode
|
|
247
|
+
* @returns {string} - The base64url encoded string
|
|
248
|
+
*/
|
|
249
|
+
base64UrlEncode(buffer) {
|
|
250
|
+
return btoa(String.fromCharCode(...new Uint8Array(buffer))).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Base64Url decode a string
|
|
254
|
+
* @param {string} base64Url - The base64url encoded string
|
|
255
|
+
* @returns {ArrayBuffer} - The decoded buffer
|
|
256
|
+
*/
|
|
257
|
+
base64UrlDecode(base64Url) {
|
|
258
|
+
const padding = "=".repeat((4 - base64Url.length % 4) % 4);
|
|
259
|
+
const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/") + padding;
|
|
260
|
+
const rawData = atob(base64);
|
|
261
|
+
const buffer = new Uint8Array(rawData.length);
|
|
262
|
+
for (let i = 0; i < rawData.length; i++) buffer[i] = rawData.charCodeAt(i);
|
|
263
|
+
return buffer.buffer;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Sign a payload and create a JWT token
|
|
267
|
+
* @param {JWTPayload} payload - The payload to include in the token
|
|
268
|
+
* @param {JWTOptions} [options={}] - Options for the token
|
|
269
|
+
* @param {string | number} [options.expiresIn='1h'] - Token expiration time
|
|
270
|
+
* @returns {Promise<string>} - The JWT token
|
|
271
|
+
*/
|
|
272
|
+
async sign(payload, options = {}) {
|
|
273
|
+
if (!payload || typeof payload !== "object") throw new Error("Payload must be an object");
|
|
274
|
+
const expiresIn = options.expiresIn || "1h";
|
|
275
|
+
let exp;
|
|
276
|
+
if (typeof expiresIn === "number") exp = Math.floor(Date.now() / 1e3) + expiresIn;
|
|
277
|
+
else if (typeof expiresIn === "string") {
|
|
278
|
+
const match = expiresIn.match(/^(\d+)([smhd])$/);
|
|
279
|
+
if (match) {
|
|
280
|
+
const value = parseInt(match[1]);
|
|
281
|
+
const unit = match[2];
|
|
282
|
+
const seconds = {
|
|
283
|
+
"s": value,
|
|
284
|
+
"m": value * 60,
|
|
285
|
+
"h": value * 60 * 60,
|
|
286
|
+
"d": value * 60 * 60 * 24
|
|
287
|
+
}[unit];
|
|
288
|
+
exp = Math.floor(Date.now() / 1e3) + seconds;
|
|
289
|
+
} else throw new Error("Invalid expiresIn format. Use a number (seconds) or a string like \"1h\", \"30m\", etc.");
|
|
290
|
+
}
|
|
291
|
+
const fullPayload = {
|
|
292
|
+
...payload,
|
|
293
|
+
iat: Math.floor(Date.now() / 1e3),
|
|
294
|
+
exp
|
|
295
|
+
};
|
|
296
|
+
const signatureBase = `${this.base64UrlEncode(this.encoder.encode(JSON.stringify({
|
|
297
|
+
alg: "HS256",
|
|
298
|
+
typ: "JWT"
|
|
299
|
+
})))}.${this.base64UrlEncode(this.encoder.encode(JSON.stringify(fullPayload)))}`;
|
|
300
|
+
const key = await this.getSecretKey();
|
|
301
|
+
const signature = await crypto.subtle.sign({ name: "HMAC" }, key, this.encoder.encode(signatureBase));
|
|
302
|
+
return `${signatureBase}.${this.base64UrlEncode(signature)}`;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Verify a JWT token and return the decoded payload
|
|
306
|
+
* @param {string} token - The JWT token to verify
|
|
307
|
+
* @returns {Promise<JWTPayload>} - The decoded payload if verification succeeds
|
|
308
|
+
* @throws {Error} - If verification fails
|
|
309
|
+
*/
|
|
310
|
+
async verify(token) {
|
|
311
|
+
if (!token || typeof token !== "string") throw new Error("Token is required and must be a string");
|
|
312
|
+
const parts = token.split(".");
|
|
313
|
+
if (parts.length !== 3) throw new Error("Invalid token format");
|
|
314
|
+
const [encodedHeader, encodedPayload, encodedSignature] = parts;
|
|
315
|
+
try {
|
|
316
|
+
const header = JSON.parse(this.decoder.decode(this.base64UrlDecode(encodedHeader)));
|
|
317
|
+
const payload = JSON.parse(this.decoder.decode(this.base64UrlDecode(encodedPayload)));
|
|
318
|
+
if (header.alg !== "HS256") throw new Error(`Unsupported algorithm: ${header.alg}`);
|
|
319
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
320
|
+
if (payload.exp && payload.exp < now) throw new Error("Token has expired");
|
|
321
|
+
const key = await this.getSecretKey();
|
|
322
|
+
const signatureBase = `${encodedHeader}.${encodedPayload}`;
|
|
323
|
+
const signature = this.base64UrlDecode(encodedSignature);
|
|
324
|
+
if (!await crypto.subtle.verify({ name: "HMAC" }, key, signature, this.encoder.encode(signatureBase))) throw new Error("Invalid signature");
|
|
325
|
+
return payload;
|
|
326
|
+
} catch (error) {
|
|
327
|
+
if (error instanceof Error) throw new Error(`Token verification failed: ${error.message}`);
|
|
328
|
+
throw new Error("Token verification failed: Unknown error");
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
var guardManageWorld = async (_, req, room) => {
|
|
333
|
+
const tokenShard = req.headers.get("x-access-shard");
|
|
334
|
+
if (tokenShard) {
|
|
335
|
+
if (tokenShard !== room.env.SHARD_SECRET) return false;
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
const token = getAuthToken(req, new URL(req.url));
|
|
339
|
+
if (!token) return false;
|
|
340
|
+
const jwt = new JWTAuth(room.env.AUTH_JWT_SECRET);
|
|
341
|
+
try {
|
|
342
|
+
const payload = await jwt.verify(token);
|
|
343
|
+
if (!payload) return false;
|
|
344
|
+
if (!canAccessWorld(payload, room.id)) return false;
|
|
345
|
+
} catch (error) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
return true;
|
|
349
|
+
};
|
|
350
|
+
function getAuthToken(req, url) {
|
|
351
|
+
const authorization = req.headers.get("Authorization");
|
|
352
|
+
if (authorization?.startsWith("Bearer ")) return authorization.slice(7).trim();
|
|
353
|
+
return authorization ?? url.searchParams.get("world-auth-token");
|
|
354
|
+
}
|
|
355
|
+
function canAccessWorld(payload, worldId) {
|
|
356
|
+
const worlds = payload.worlds;
|
|
357
|
+
if (!Array.isArray(worlds)) return false;
|
|
358
|
+
return worlds.some((world) => world === "*" || world === worldId);
|
|
359
|
+
}
|
|
360
|
+
var MAX_PLAYERS_PER_SHARD = 75;
|
|
361
|
+
var RoomConfigSchema = z.object({
|
|
362
|
+
name: z.string(),
|
|
363
|
+
balancingStrategy: z.enum([
|
|
364
|
+
"round-robin",
|
|
365
|
+
"least-connections",
|
|
366
|
+
"random"
|
|
367
|
+
]),
|
|
368
|
+
public: z.boolean(),
|
|
369
|
+
maxPlayersPerShard: z.number().int().positive(),
|
|
370
|
+
minShards: z.number().int().min(0),
|
|
371
|
+
maxShards: z.number().int().positive().optional()
|
|
372
|
+
});
|
|
373
|
+
z.object({
|
|
374
|
+
shardId: z.string(),
|
|
375
|
+
roomId: z.string(),
|
|
376
|
+
worldId: z.string().optional(),
|
|
377
|
+
url: z.string().url(),
|
|
378
|
+
maxConnections: z.number().int().positive()
|
|
379
|
+
});
|
|
380
|
+
var UpdateShardStatsSchema = z.object({
|
|
381
|
+
shardId: z.string(),
|
|
382
|
+
worldId: z.string().optional(),
|
|
383
|
+
connections: z.number().int().min(0),
|
|
384
|
+
status: z.enum([
|
|
385
|
+
"active",
|
|
386
|
+
"maintenance",
|
|
387
|
+
"draining"
|
|
388
|
+
]).optional()
|
|
389
|
+
});
|
|
390
|
+
var ScaleRoomSchema = z.object({
|
|
391
|
+
roomId: z.string(),
|
|
392
|
+
targetShardCount: z.number().int().positive(),
|
|
393
|
+
shardTemplate: z.object({
|
|
394
|
+
urlTemplate: z.string(),
|
|
395
|
+
maxConnections: z.number().int().positive()
|
|
396
|
+
}).optional()
|
|
397
|
+
});
|
|
398
|
+
var RoomConfig = class {
|
|
399
|
+
constructor() {
|
|
400
|
+
this.name = signal("");
|
|
401
|
+
this.balancingStrategy = signal("round-robin");
|
|
402
|
+
this.public = signal(true);
|
|
403
|
+
this.maxPlayersPerShard = signal(MAX_PLAYERS_PER_SHARD);
|
|
404
|
+
this.minShards = signal(1);
|
|
405
|
+
this.maxShards = signal(void 0);
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
__decorateClass([id()], RoomConfig.prototype, "id", 2);
|
|
409
|
+
__decorateClass([sync()], RoomConfig.prototype, "name", 2);
|
|
410
|
+
__decorateClass([sync()], RoomConfig.prototype, "balancingStrategy", 2);
|
|
411
|
+
__decorateClass([sync()], RoomConfig.prototype, "public", 2);
|
|
412
|
+
__decorateClass([sync()], RoomConfig.prototype, "maxPlayersPerShard", 2);
|
|
413
|
+
__decorateClass([sync()], RoomConfig.prototype, "minShards", 2);
|
|
414
|
+
__decorateClass([sync()], RoomConfig.prototype, "maxShards", 2);
|
|
415
|
+
var ShardInfo = class {
|
|
416
|
+
constructor() {
|
|
417
|
+
this.roomId = signal("");
|
|
418
|
+
this.worldId = signal("");
|
|
419
|
+
this.url = signal("");
|
|
420
|
+
this.currentConnections = signal(0);
|
|
421
|
+
this.maxConnections = signal(MAX_PLAYERS_PER_SHARD);
|
|
422
|
+
this.status = signal("active");
|
|
423
|
+
this.lastHeartbeat = signal(0);
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
__decorateClass([id()], ShardInfo.prototype, "id", 2);
|
|
427
|
+
__decorateClass([sync()], ShardInfo.prototype, "roomId", 2);
|
|
428
|
+
__decorateClass([sync()], ShardInfo.prototype, "worldId", 2);
|
|
429
|
+
__decorateClass([sync()], ShardInfo.prototype, "url", 2);
|
|
430
|
+
__decorateClass([sync({ persist: false })], ShardInfo.prototype, "currentConnections", 2);
|
|
431
|
+
__decorateClass([sync()], ShardInfo.prototype, "maxConnections", 2);
|
|
432
|
+
__decorateClass([sync()], ShardInfo.prototype, "status", 2);
|
|
433
|
+
__decorateClass([sync()], ShardInfo.prototype, "lastHeartbeat", 2);
|
|
434
|
+
var WorldRoom = class {
|
|
435
|
+
constructor(room) {
|
|
436
|
+
this.room = room;
|
|
437
|
+
this.rooms = signal({});
|
|
438
|
+
this.shards = signal({});
|
|
439
|
+
this.rrCounters = signal({});
|
|
440
|
+
this.defaultShardUrlTemplate = signal("{shardId}");
|
|
441
|
+
this.defaultMaxConnectionsPerShard = signal(MAX_PLAYERS_PER_SHARD);
|
|
442
|
+
const { AUTH_JWT_SECRET, SHARD_SECRET } = this.room.env;
|
|
443
|
+
if (!AUTH_JWT_SECRET) throw new Error("AUTH_JWT_SECRET env variable is not set");
|
|
444
|
+
if (!SHARD_SECRET) throw new Error("SHARD_SECRET env variable is not set");
|
|
445
|
+
this.scheduleInactiveShardCleanup();
|
|
446
|
+
}
|
|
447
|
+
async onJoin(user, conn, ctx) {
|
|
448
|
+
const canConnect = await guardManageWorld(user, ctx.request, this.room);
|
|
449
|
+
conn.setState({
|
|
450
|
+
...conn.state,
|
|
451
|
+
isAdmin: canConnect
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
interceptorPacket(_, obj, conn) {
|
|
455
|
+
if (!conn.state["isAdmin"]) return null;
|
|
456
|
+
return obj;
|
|
457
|
+
}
|
|
458
|
+
getWorldId() {
|
|
459
|
+
return this.room.id;
|
|
460
|
+
}
|
|
461
|
+
scheduleInactiveShardCleanup() {
|
|
462
|
+
setTimeout(() => this.cleanupInactiveShards(), 6e4)?.unref?.();
|
|
463
|
+
}
|
|
464
|
+
cleanupInactiveShards() {
|
|
465
|
+
const now = Date.now();
|
|
466
|
+
const timeout = 300 * 1e3;
|
|
467
|
+
const shardsValue = this.shards();
|
|
468
|
+
Object.values(shardsValue).forEach((shard) => {
|
|
469
|
+
if (now - shard.lastHeartbeat() > timeout) delete this.shards()[shard.id];
|
|
470
|
+
});
|
|
471
|
+
this.scheduleInactiveShardCleanup();
|
|
472
|
+
}
|
|
473
|
+
removeShard(shardId) {
|
|
474
|
+
delete this.shards()[shardId];
|
|
475
|
+
}
|
|
476
|
+
shouldCompleteDrain(shard) {
|
|
477
|
+
return shard.status() === "draining" && shard.currentConnections() === 0;
|
|
478
|
+
}
|
|
479
|
+
async registerRoom(req, res) {
|
|
480
|
+
const parseResult = RoomConfigSchema.safeParse(await req.json());
|
|
481
|
+
if (!parseResult.success) return res?.badRequest("Invalid room configuration", { details: parseResult.error });
|
|
482
|
+
const roomConfig = parseResult.data;
|
|
483
|
+
if (roomConfig.maxShards !== void 0 && roomConfig.minShards > roomConfig.maxShards) return res?.badRequest("minShards cannot be greater than maxShards");
|
|
484
|
+
const roomId = roomConfig.name;
|
|
485
|
+
if (!this.rooms()[roomId]) {
|
|
486
|
+
const newRoom = new RoomConfig();
|
|
487
|
+
newRoom.id = roomId;
|
|
488
|
+
newRoom.name.set(roomConfig.name);
|
|
489
|
+
newRoom.balancingStrategy.set(roomConfig.balancingStrategy);
|
|
490
|
+
newRoom.public.set(roomConfig.public);
|
|
491
|
+
newRoom.maxPlayersPerShard.set(roomConfig.maxPlayersPerShard);
|
|
492
|
+
newRoom.minShards.set(roomConfig.minShards);
|
|
493
|
+
newRoom.maxShards.set(roomConfig.maxShards);
|
|
494
|
+
this.rooms()[roomId] = newRoom;
|
|
495
|
+
if (roomConfig.minShards > 0) for (let i = 0; i < roomConfig.minShards; i++) await this.createShard(roomId);
|
|
496
|
+
} else {
|
|
497
|
+
const room = this.rooms()[roomId];
|
|
498
|
+
room.balancingStrategy.set(roomConfig.balancingStrategy);
|
|
499
|
+
room.public.set(roomConfig.public);
|
|
500
|
+
room.maxPlayersPerShard.set(roomConfig.maxPlayersPerShard);
|
|
501
|
+
room.minShards.set(roomConfig.minShards);
|
|
502
|
+
room.maxShards.set(roomConfig.maxShards);
|
|
503
|
+
}
|
|
504
|
+
await this.ensureMinShards(roomId);
|
|
505
|
+
}
|
|
506
|
+
async updateShardStats(req, res) {
|
|
507
|
+
const parseResult = UpdateShardStatsSchema.safeParse(await req.json());
|
|
508
|
+
if (!parseResult.success) return res.badRequest("Invalid shard stats", { details: parseResult.error });
|
|
509
|
+
const body = parseResult.data;
|
|
510
|
+
const { shardId, connections, status } = body;
|
|
511
|
+
const shard = this.shards()[shardId];
|
|
512
|
+
if (!shard) return res.notFound(`Shard ${shardId} not found`);
|
|
513
|
+
if (body.worldId && body.worldId !== this.getWorldId()) return res.badRequest(`Shard ${shardId} belongs to world ${body.worldId}, not ${this.getWorldId()}`);
|
|
514
|
+
shard.currentConnections.set(connections);
|
|
515
|
+
if (status) shard.status.set(status);
|
|
516
|
+
shard.lastHeartbeat.set(Date.now());
|
|
517
|
+
if (this.shouldCompleteDrain(shard)) this.removeShard(shard.id);
|
|
518
|
+
}
|
|
519
|
+
async scaleRoom(req, res) {
|
|
520
|
+
const parseResult = ScaleRoomSchema.safeParse(await req.json());
|
|
521
|
+
if (!parseResult.success) return res.badRequest("Invalid scale room request", { details: parseResult.error });
|
|
522
|
+
const { targetShardCount, shardTemplate, roomId } = parseResult.data;
|
|
523
|
+
const room = this.rooms()[roomId];
|
|
524
|
+
if (!room) return res.notFound(`Room ${roomId} does not exist`);
|
|
525
|
+
const roomShards = Object.values(this.shards()).filter((shard) => shard.roomId() === roomId);
|
|
526
|
+
const previousShardCount = roomShards.length;
|
|
527
|
+
if (room.maxShards() !== void 0 && targetShardCount > room.maxShards()) return res.badRequest(`Cannot scale beyond maximum allowed shards (${room.maxShards()})`, {
|
|
528
|
+
roomId,
|
|
529
|
+
currentShardCount: previousShardCount
|
|
530
|
+
});
|
|
531
|
+
if (targetShardCount < previousShardCount) {
|
|
532
|
+
const shardsToDrain = [...roomShards].sort((a, b) => {
|
|
533
|
+
if (a.status() === "draining" && b.status() !== "draining") return -1;
|
|
534
|
+
if (a.status() !== "draining" && b.status() === "draining") return 1;
|
|
535
|
+
return a.currentConnections() - b.currentConnections();
|
|
536
|
+
}).slice(0, previousShardCount - targetShardCount);
|
|
537
|
+
for (const shard of shardsToDrain) {
|
|
538
|
+
shard.status.set("draining");
|
|
539
|
+
if (this.shouldCompleteDrain(shard)) this.removeShard(shard.id);
|
|
540
|
+
}
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
if (targetShardCount > previousShardCount) {
|
|
544
|
+
const newShards = [];
|
|
545
|
+
for (let i = 0; i < targetShardCount - previousShardCount; i++) {
|
|
546
|
+
const newShard = await this.createShard(roomId, shardTemplate?.urlTemplate, shardTemplate?.maxConnections);
|
|
547
|
+
if (newShard) newShards.push(newShard);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
async connect(req, res) {
|
|
552
|
+
try {
|
|
553
|
+
let data;
|
|
554
|
+
try {
|
|
555
|
+
const body = await req.text();
|
|
556
|
+
if (!body || body.trim() === "") return res.badRequest("Request body is empty");
|
|
557
|
+
data = JSON.parse(body);
|
|
558
|
+
} catch (parseError) {
|
|
559
|
+
return res.badRequest("Invalid JSON in request body");
|
|
560
|
+
}
|
|
561
|
+
if (!data.roomId) return res.badRequest("roomId parameter is required");
|
|
562
|
+
const autoCreate = data.autoCreate !== void 0 ? data.autoCreate : true;
|
|
563
|
+
const result = await this.findOptimalShard(data.roomId, autoCreate);
|
|
564
|
+
if ("error" in result) return res.notFound(result.error);
|
|
565
|
+
return res.success({
|
|
566
|
+
success: true,
|
|
567
|
+
shardId: result.shardId,
|
|
568
|
+
url: result.url
|
|
569
|
+
});
|
|
570
|
+
} catch (error) {
|
|
571
|
+
console.error("Error connecting to shard:", error);
|
|
572
|
+
return res.serverError();
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
async findOptimalShard(roomId, autoCreate = true) {
|
|
576
|
+
let room = this.rooms()[roomId];
|
|
577
|
+
if (!room) if (autoCreate) {
|
|
578
|
+
await this.registerRoom({ json: async () => ({
|
|
579
|
+
name: roomId,
|
|
580
|
+
balancingStrategy: "round-robin",
|
|
581
|
+
public: true,
|
|
582
|
+
maxPlayersPerShard: this.defaultMaxConnectionsPerShard(),
|
|
583
|
+
minShards: 1,
|
|
584
|
+
maxShards: void 0
|
|
585
|
+
}) });
|
|
586
|
+
room = this.rooms()[roomId];
|
|
587
|
+
if (!room) return { error: `Failed to create room ${roomId}` };
|
|
588
|
+
} else return { error: `Room ${roomId} does not exist` };
|
|
589
|
+
const roomShards = Object.values(this.shards()).filter((shard) => shard.roomId() === roomId);
|
|
590
|
+
if (roomShards.length === 0) if (autoCreate) {
|
|
591
|
+
const newShard = await this.createShard(roomId);
|
|
592
|
+
if (newShard) return {
|
|
593
|
+
shardId: newShard.id,
|
|
594
|
+
url: newShard.url()
|
|
595
|
+
};
|
|
596
|
+
else return { error: `Failed to create shard for room ${roomId}` };
|
|
597
|
+
} else return { error: `No shards available for room ${roomId}` };
|
|
598
|
+
let activeShards = this.getAvailableShards(roomShards);
|
|
599
|
+
if (activeShards.length === 0) {
|
|
600
|
+
if (autoCreate && this.canCreateShard(room, roomShards.length)) {
|
|
601
|
+
const newShard = await this.createShard(roomId);
|
|
602
|
+
if (newShard) return {
|
|
603
|
+
shardId: newShard.id,
|
|
604
|
+
url: newShard.url()
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
if (roomShards.some((shard) => shard.status() === "active")) return { error: `No shard capacity available for room ${roomId}` };
|
|
608
|
+
return { error: `No active shards available for room ${roomId}` };
|
|
609
|
+
}
|
|
610
|
+
const balancingStrategy = room.balancingStrategy();
|
|
611
|
+
let selectedShard;
|
|
612
|
+
switch (balancingStrategy) {
|
|
613
|
+
case "least-connections":
|
|
614
|
+
selectedShard = activeShards.reduce((min, shard) => shard.currentConnections() < min.currentConnections() ? shard : min, activeShards[0]);
|
|
615
|
+
break;
|
|
616
|
+
case "random":
|
|
617
|
+
selectedShard = activeShards[Math.floor(Math.random() * activeShards.length)];
|
|
618
|
+
break;
|
|
619
|
+
default:
|
|
620
|
+
const counter = this.rrCounters()[roomId] || 0;
|
|
621
|
+
const nextCounter = (counter + 1) % activeShards.length;
|
|
622
|
+
this.rrCounters()[roomId] = nextCounter;
|
|
623
|
+
selectedShard = activeShards[counter];
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
return {
|
|
627
|
+
shardId: selectedShard.id,
|
|
628
|
+
url: selectedShard.url()
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
getAvailableShards(shards) {
|
|
632
|
+
return shards.filter((shard) => shard && shard.status() === "active" && shard.currentConnections() < shard.maxConnections());
|
|
633
|
+
}
|
|
634
|
+
canCreateShard(room, currentShardCount) {
|
|
635
|
+
return room.maxShards() === void 0 || currentShardCount < room.maxShards();
|
|
636
|
+
}
|
|
637
|
+
async createShard(roomId, urlTemplate, maxConnections) {
|
|
638
|
+
const room = this.rooms()[roomId];
|
|
639
|
+
if (!room) {
|
|
640
|
+
console.error(`Cannot create shard for non-existent room: ${roomId}`);
|
|
641
|
+
return null;
|
|
642
|
+
}
|
|
643
|
+
const worldId = this.getWorldId();
|
|
644
|
+
const shardId = `${roomId}:${worldId}:${Date.now()}-${Math.floor(Math.random() * 1e4)}`;
|
|
645
|
+
const url = (urlTemplate || this.defaultShardUrlTemplate()).replace("{shardId}", shardId).replace("{roomId}", roomId);
|
|
646
|
+
const max = maxConnections || room.maxPlayersPerShard();
|
|
647
|
+
const newShard = new ShardInfo();
|
|
648
|
+
newShard.id = shardId;
|
|
649
|
+
newShard.roomId.set(roomId);
|
|
650
|
+
newShard.worldId.set(worldId);
|
|
651
|
+
newShard.url.set(url);
|
|
652
|
+
newShard.maxConnections.set(max);
|
|
653
|
+
newShard.currentConnections.set(0);
|
|
654
|
+
newShard.status.set("active");
|
|
655
|
+
newShard.lastHeartbeat.set(Date.now());
|
|
656
|
+
this.shards()[shardId] = newShard;
|
|
657
|
+
return newShard;
|
|
658
|
+
}
|
|
659
|
+
async ensureMinShards(roomId) {
|
|
660
|
+
const room = this.rooms()[roomId];
|
|
661
|
+
if (!room) return;
|
|
662
|
+
const currentShardCount = Object.values(this.shards()).filter((shard) => shard.roomId() === roomId).length;
|
|
663
|
+
const targetShardCount = room.minShards();
|
|
664
|
+
if (currentShardCount >= targetShardCount) return;
|
|
665
|
+
for (let i = currentShardCount; i < targetShardCount; i++) await this.createShard(roomId);
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
__decorateClass([sync(RoomConfig)], WorldRoom.prototype, "rooms", 2);
|
|
669
|
+
__decorateClass([sync(ShardInfo)], WorldRoom.prototype, "shards", 2);
|
|
670
|
+
__decorateClass([persist()], WorldRoom.prototype, "rrCounters", 2);
|
|
671
|
+
__decorateClass([Request2({
|
|
672
|
+
path: "register-room",
|
|
673
|
+
method: "POST"
|
|
674
|
+
}), Guard([guardManageWorld])], WorldRoom.prototype, "registerRoom", 1);
|
|
675
|
+
__decorateClass([Request2({
|
|
676
|
+
path: "update-shard",
|
|
677
|
+
method: "POST"
|
|
678
|
+
}), Guard([guardManageWorld])], WorldRoom.prototype, "updateShardStats", 1);
|
|
679
|
+
__decorateClass([Request2({
|
|
680
|
+
path: "scale-room",
|
|
681
|
+
method: "POST"
|
|
682
|
+
}), Guard([guardManageWorld])], WorldRoom.prototype, "scaleRoom", 1);
|
|
683
|
+
__decorateClass([Request2({
|
|
684
|
+
path: "connect",
|
|
685
|
+
method: "POST"
|
|
686
|
+
})], WorldRoom.prototype, "connect", 1);
|
|
687
|
+
WorldRoom = __decorateClass([Room({
|
|
688
|
+
path: "world-{worldId}",
|
|
689
|
+
maxUsers: 100,
|
|
690
|
+
throttleStorage: 2e3,
|
|
691
|
+
throttleSync: 500
|
|
692
|
+
})], WorldRoom);
|
|
693
|
+
//#endregion
|
|
694
|
+
export { ClientIo, ServerIo };
|
|
695
|
+
|
|
696
|
+
//# sourceMappingURL=index.js.map
|