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