@rpgjs/client 5.0.0-alpha.9 → 5.0.0-beta.1

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