@rpgjs/server 5.0.0-alpha.42 → 5.0.0-alpha.43

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.
@@ -0,0 +1,693 @@
1
+ import { r as resolveMapUpdateToken, u as updateMap, c as createMapUpdateHeaders } from '../map-D4T2_hc-.js';
2
+ export { a as MAP_UPDATE_TOKEN_ENV, M as MAP_UPDATE_TOKEN_HEADER, i as isMapUpdateAuthorized, b as readMapUpdateToken } from '../map-D4T2_hc-.js';
3
+
4
+ function readEnvVariable(name) {
5
+ const value = globalThis.process?.env?.[name];
6
+ return typeof value === "string" ? value : void 0;
7
+ }
8
+ class PartyConnection {
9
+ constructor(ws, id, uri) {
10
+ this.ws = ws;
11
+ this._state = {};
12
+ this.messageQueue = [];
13
+ this.isProcessingQueue = false;
14
+ this.sequenceCounter = 0;
15
+ this.incomingQueue = [];
16
+ this.isProcessingIncomingQueue = false;
17
+ this.id = id || this.generateId();
18
+ this.uri = uri || "";
19
+ }
20
+ static {
21
+ this.packetLossRate = parseFloat(readEnvVariable("RPGJS_PACKET_LOSS_RATE") || "0.1");
22
+ }
23
+ static {
24
+ this.packetLossEnabled = readEnvVariable("RPGJS_ENABLE_PACKET_LOSS") === "true";
25
+ }
26
+ static {
27
+ this.packetLossFilter = readEnvVariable("RPGJS_PACKET_LOSS_FILTER") || "";
28
+ }
29
+ static {
30
+ this.bandwidthEnabled = readEnvVariable("RPGJS_ENABLE_BANDWIDTH") === "true";
31
+ }
32
+ static {
33
+ this.bandwidthKbps = parseInt(readEnvVariable("RPGJS_BANDWIDTH_KBPS") || "100");
34
+ }
35
+ static {
36
+ this.bandwidthFilter = readEnvVariable("RPGJS_BANDWIDTH_FILTER") || "";
37
+ }
38
+ static {
39
+ this.latencyEnabled = readEnvVariable("RPGJS_ENABLE_LATENCY") === "true";
40
+ }
41
+ static {
42
+ this.latencyMs = parseInt(readEnvVariable("RPGJS_LATENCY_MS") || "50");
43
+ }
44
+ static {
45
+ this.latencyFilter = readEnvVariable("RPGJS_LATENCY_FILTER") || "";
46
+ }
47
+ generateId() {
48
+ return `conn_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
49
+ }
50
+ async send(data) {
51
+ if (this.ws.readyState !== 1) {
52
+ return;
53
+ }
54
+ const message = typeof data === "string" ? data : JSON.stringify(data);
55
+ const timestamp = Date.now();
56
+ const sequence = ++this.sequenceCounter;
57
+ this.messageQueue.push({ message, timestamp, sequence });
58
+ if (!this.isProcessingQueue) {
59
+ void this.processMessageQueue();
60
+ }
61
+ }
62
+ async processMessageQueue() {
63
+ if (this.isProcessingQueue) {
64
+ return;
65
+ }
66
+ this.isProcessingQueue = true;
67
+ while (this.messageQueue.length > 0) {
68
+ const queueItem = this.messageQueue.shift();
69
+ if (this.shouldApplyLatency(queueItem.message)) {
70
+ await this.waitUntil(queueItem.timestamp + PartyConnection.latencyMs);
71
+ }
72
+ if (PartyConnection.bandwidthEnabled && PartyConnection.bandwidthKbps > 0) {
73
+ if (!PartyConnection.bandwidthFilter || queueItem.message.includes(PartyConnection.bandwidthFilter)) {
74
+ const messageSizeBits = queueItem.message.length * 8;
75
+ const transmissionTimeMs = messageSizeBits / (PartyConnection.bandwidthKbps * 1e3) * 1e3;
76
+ const bandwidthDelayMs = Math.max(transmissionTimeMs, 10);
77
+ console.log(
78
+ `\x1B[34m[BANDWIDTH SIMULATION]\x1B[0m Connection ${this.id}: Message #${queueItem.sequence} transmission time: ${bandwidthDelayMs.toFixed(1)}ms`
79
+ );
80
+ await new Promise((resolve) => setTimeout(resolve, bandwidthDelayMs));
81
+ }
82
+ }
83
+ this.ws.send(queueItem.message);
84
+ }
85
+ this.isProcessingQueue = false;
86
+ }
87
+ shouldApplyLatency(message) {
88
+ if (!PartyConnection.latencyEnabled || PartyConnection.latencyMs <= 0) {
89
+ return false;
90
+ }
91
+ if (!PartyConnection.latencyFilter) {
92
+ return true;
93
+ }
94
+ return message.includes(PartyConnection.latencyFilter);
95
+ }
96
+ async waitUntil(targetTimestamp) {
97
+ const delayMs = targetTimestamp - Date.now();
98
+ if (delayMs <= 0) {
99
+ return;
100
+ }
101
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
102
+ }
103
+ close() {
104
+ if (this.ws.readyState === 1) {
105
+ this.ws.close();
106
+ }
107
+ }
108
+ setState(value) {
109
+ this._state = value;
110
+ }
111
+ get state() {
112
+ return this._state;
113
+ }
114
+ bufferIncoming(message, processor) {
115
+ this.incomingQueue.push({
116
+ message,
117
+ timestamp: Date.now(),
118
+ processor
119
+ });
120
+ if (!this.isProcessingIncomingQueue) {
121
+ void this.processIncomingQueue();
122
+ }
123
+ }
124
+ async processIncomingQueue() {
125
+ if (this.isProcessingIncomingQueue) {
126
+ return;
127
+ }
128
+ this.isProcessingIncomingQueue = true;
129
+ while (this.incomingQueue.length > 0) {
130
+ const item = this.incomingQueue.shift();
131
+ if (this.shouldApplyLatency(item.message)) {
132
+ await this.waitUntil(item.timestamp + PartyConnection.latencyMs);
133
+ }
134
+ try {
135
+ await item.processor([item.message]);
136
+ } catch (err) {
137
+ console.error("Error processing incoming message:", err);
138
+ }
139
+ }
140
+ this.isProcessingIncomingQueue = false;
141
+ }
142
+ static configurePacketLoss(enabled, rate, filter) {
143
+ PartyConnection.packetLossEnabled = enabled;
144
+ PartyConnection.packetLossRate = Math.max(0, Math.min(1, rate));
145
+ PartyConnection.packetLossFilter = filter || "";
146
+ if (enabled && rate > 0) {
147
+ const filterInfo = filter ? ` (filtered: "${filter}")` : "";
148
+ console.log(`\x1B[35m[PACKET LOSS SIMULATION]\x1B[0m Enabled with ${(rate * 100).toFixed(1)}% loss rate${filterInfo}`);
149
+ } else if (enabled) {
150
+ console.log("\x1B[35m[PACKET LOSS SIMULATION]\x1B[0m Enabled but rate is 0% (no messages will be dropped)");
151
+ } else {
152
+ console.log("\x1B[35m[PACKET LOSS SIMULATION]\x1B[0m Disabled");
153
+ }
154
+ }
155
+ static getPacketLossStatus() {
156
+ return {
157
+ enabled: PartyConnection.packetLossEnabled,
158
+ rate: PartyConnection.packetLossRate,
159
+ filter: PartyConnection.packetLossFilter
160
+ };
161
+ }
162
+ static configureBandwidth(enabled, kbps, filter) {
163
+ PartyConnection.bandwidthEnabled = enabled;
164
+ PartyConnection.bandwidthKbps = Math.max(1, kbps);
165
+ PartyConnection.bandwidthFilter = filter || "";
166
+ if (enabled && kbps > 0) {
167
+ const filterInfo = filter ? ` (filtered: "${filter}")` : "";
168
+ console.log(`\x1B[35m[BANDWIDTH SIMULATION]\x1B[0m Enabled with ${kbps} kbps bandwidth${filterInfo}`);
169
+ } else if (enabled) {
170
+ console.log("\x1B[35m[BANDWIDTH SIMULATION]\x1B[0m Enabled but bandwidth is 0 kbps (no delay will be applied)");
171
+ } else {
172
+ console.log("\x1B[35m[BANDWIDTH SIMULATION]\x1B[0m Disabled");
173
+ }
174
+ }
175
+ static getBandwidthStatus() {
176
+ return {
177
+ enabled: PartyConnection.bandwidthEnabled,
178
+ kbps: PartyConnection.bandwidthKbps,
179
+ filter: PartyConnection.bandwidthFilter
180
+ };
181
+ }
182
+ static configureLatency(enabled, ms, filter) {
183
+ PartyConnection.latencyEnabled = enabled;
184
+ PartyConnection.latencyMs = Math.max(0, ms);
185
+ PartyConnection.latencyFilter = filter || "";
186
+ if (enabled && ms > 0) {
187
+ const filterInfo = filter ? ` (filtered: "${filter}")` : "";
188
+ console.log(`\x1B[35m[LATENCY SIMULATION]\x1B[0m Enabled with ${ms}ms fixed latency${filterInfo}`);
189
+ } else if (enabled) {
190
+ console.log("\x1B[35m[LATENCY SIMULATION]\x1B[0m Enabled but latency is 0ms (no delay will be applied)");
191
+ } else {
192
+ console.log("\x1B[35m[LATENCY SIMULATION]\x1B[0m Disabled");
193
+ }
194
+ }
195
+ static getLatencyStatus() {
196
+ return {
197
+ enabled: PartyConnection.latencyEnabled,
198
+ ms: PartyConnection.latencyMs,
199
+ filter: PartyConnection.latencyFilter
200
+ };
201
+ }
202
+ }
203
+ function logNetworkSimulationStatus() {
204
+ const packetLossStatus = PartyConnection.getPacketLossStatus();
205
+ const bandwidthStatus = PartyConnection.getBandwidthStatus();
206
+ const latencyStatus = PartyConnection.getLatencyStatus();
207
+ if (packetLossStatus.enabled) {
208
+ const filterInfo = packetLossStatus.filter ? ` (filter: "${packetLossStatus.filter}")` : "";
209
+ console.log(
210
+ `\x1B[36m[NETWORK SIMULATION]\x1B[0m Packet loss simulation: ${(packetLossStatus.rate * 100).toFixed(1)}% loss rate${filterInfo}`
211
+ );
212
+ } else {
213
+ console.log("\x1B[36m[NETWORK SIMULATION]\x1B[0m Packet loss simulation: disabled");
214
+ }
215
+ if (bandwidthStatus.enabled) {
216
+ const filterInfo = bandwidthStatus.filter ? ` (filter: "${bandwidthStatus.filter}")` : "";
217
+ console.log(`\x1B[36m[NETWORK SIMULATION]\x1B[0m Bandwidth simulation: ${bandwidthStatus.kbps} kbps${filterInfo}`);
218
+ } else {
219
+ console.log("\x1B[36m[NETWORK SIMULATION]\x1B[0m Bandwidth simulation: disabled");
220
+ }
221
+ if (latencyStatus.enabled) {
222
+ const filterInfo = latencyStatus.filter ? ` (filter: "${latencyStatus.filter}")` : "";
223
+ console.log(`\x1B[36m[NETWORK SIMULATION]\x1B[0m Latency simulation: ${latencyStatus.ms}ms ping${filterInfo}`);
224
+ } else {
225
+ console.log("\x1B[36m[NETWORK SIMULATION]\x1B[0m Latency simulation: disabled");
226
+ }
227
+ }
228
+
229
+ class PartyRoom {
230
+ constructor(id) {
231
+ this.env = {};
232
+ this.context = {};
233
+ this.connections = /* @__PURE__ */ new Map();
234
+ this.storageData = /* @__PURE__ */ new Map();
235
+ this.id = id;
236
+ this.internalID = `internal_${id}_${Date.now()}`;
237
+ }
238
+ async broadcast(message, except = []) {
239
+ const data = typeof message === "string" ? message : JSON.stringify(message);
240
+ const sendPromises = [];
241
+ for (const [connectionId, connection] of this.connections) {
242
+ if (!except.includes(connectionId)) {
243
+ sendPromises.push(connection.send(data));
244
+ }
245
+ }
246
+ await Promise.all(sendPromises);
247
+ }
248
+ getConnection(id) {
249
+ return this.connections.get(id);
250
+ }
251
+ getConnections(tag) {
252
+ return this.connections.values();
253
+ }
254
+ addConnection(connection) {
255
+ this.connections.set(connection.id, connection);
256
+ }
257
+ removeConnection(connectionId) {
258
+ this.connections.delete(connectionId);
259
+ }
260
+ get storage() {
261
+ return {
262
+ put: async (key, value) => {
263
+ this.storageData.set(key, value);
264
+ },
265
+ get: async (key) => {
266
+ return this.storageData.get(key);
267
+ },
268
+ delete: async (key) => {
269
+ this.storageData.delete(key);
270
+ },
271
+ list: async () => {
272
+ return Array.from(this.storageData.entries());
273
+ }
274
+ };
275
+ }
276
+ }
277
+
278
+ function normalizePathPrefix(path, fallback) {
279
+ const trimmed = (path || fallback).trim();
280
+ if (!trimmed) {
281
+ return fallback;
282
+ }
283
+ const prefixed = trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
284
+ return prefixed !== "/" ? prefixed.replace(/\/+$/, "") : prefixed;
285
+ }
286
+ function hasPathPrefix(pathname, prefix) {
287
+ return pathname === prefix || pathname.startsWith(`${prefix}/`);
288
+ }
289
+ function prependMountedPath(pathname, mountedPath) {
290
+ if (!mountedPath) {
291
+ return pathname;
292
+ }
293
+ const normalizedMountedPath = normalizePathPrefix(mountedPath, "/");
294
+ if (hasPathPrefix(pathname, normalizedMountedPath)) {
295
+ return pathname;
296
+ }
297
+ if (pathname === "/") {
298
+ return normalizedMountedPath;
299
+ }
300
+ return `${normalizedMountedPath}${pathname.startsWith("/") ? pathname : `/${pathname}`}`.replace(/\/{2,}/g, "/");
301
+ }
302
+ function parseHttpRoute(pathname, partiesPath) {
303
+ if (!hasPathPrefix(pathname, partiesPath)) {
304
+ return null;
305
+ }
306
+ const remainder = pathname.slice(partiesPath.length);
307
+ const segments = remainder.split("/").filter(Boolean);
308
+ if (segments.length < 2) {
309
+ return null;
310
+ }
311
+ return {
312
+ roomId: segments[0],
313
+ requestPath: `/${segments.slice(1).join("/")}`
314
+ };
315
+ }
316
+ function parseSocketRoute(pathname, partiesPath) {
317
+ if (!hasPathPrefix(pathname, partiesPath)) {
318
+ return null;
319
+ }
320
+ const remainder = pathname.slice(partiesPath.length);
321
+ const segments = remainder.split("/").filter(Boolean);
322
+ if (segments.length < 1) {
323
+ return null;
324
+ }
325
+ return { roomId: segments[0] };
326
+ }
327
+ function toHeaders(input) {
328
+ if (!input) {
329
+ return new Headers();
330
+ }
331
+ if (input instanceof Headers) {
332
+ return new Headers(input);
333
+ }
334
+ if (Array.isArray(input)) {
335
+ return new Headers(input);
336
+ }
337
+ if (input instanceof Map) {
338
+ const headers2 = new Headers();
339
+ for (const [key, value] of input) {
340
+ if (typeof value !== "undefined") {
341
+ headers2.set(key, value);
342
+ }
343
+ }
344
+ return headers2;
345
+ }
346
+ const headers = new Headers();
347
+ Object.entries(input).forEach(([key, value]) => {
348
+ if (Array.isArray(value)) {
349
+ if (typeof value[0] !== "undefined") {
350
+ headers.set(key, value[0]);
351
+ }
352
+ return;
353
+ }
354
+ if (typeof value !== "undefined") {
355
+ headers.set(key, String(value));
356
+ }
357
+ });
358
+ return headers;
359
+ }
360
+ function createRequestLike(url, method, headers, bodyText) {
361
+ return {
362
+ url,
363
+ method,
364
+ headers,
365
+ json: async () => {
366
+ if (!bodyText) {
367
+ return void 0;
368
+ }
369
+ return JSON.parse(bodyText);
370
+ },
371
+ text: async () => bodyText
372
+ };
373
+ }
374
+ async function normalizeEngineResponse(result) {
375
+ if (result instanceof Response) {
376
+ return result;
377
+ }
378
+ if (typeof result === "string") {
379
+ return new Response(result, {
380
+ status: 200,
381
+ headers: {
382
+ "Content-Type": "text/plain"
383
+ }
384
+ });
385
+ }
386
+ return new Response(JSON.stringify(result ?? {}), {
387
+ status: 200,
388
+ headers: {
389
+ "Content-Type": "application/json"
390
+ }
391
+ });
392
+ }
393
+ async function sendNodeResponse(res, response) {
394
+ res.statusCode = response.status;
395
+ response.headers.forEach((value, key) => {
396
+ res.setHeader(key, value);
397
+ });
398
+ res.end(await response.text());
399
+ }
400
+ async function readNodeBody(req) {
401
+ return await new Promise((resolve, reject) => {
402
+ const chunks = [];
403
+ req.on("data", (chunk) => {
404
+ chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
405
+ });
406
+ req.on("end", () => {
407
+ resolve(Buffer.concat(chunks).toString("utf8"));
408
+ });
409
+ req.on("error", reject);
410
+ });
411
+ }
412
+ function resolveUrlFromSocketRequest(request) {
413
+ const headers = toHeaders(request.headers);
414
+ const host = headers.get("host") || "localhost";
415
+ const rawUrl = request.url || "/";
416
+ const url = new URL(rawUrl, `http://${host}`);
417
+ return {
418
+ headers,
419
+ method: request.method,
420
+ rawUrl,
421
+ url
422
+ };
423
+ }
424
+ function createConnectionContext(url, headers, method) {
425
+ const normalizedHeaders = /* @__PURE__ */ new Map();
426
+ headers.forEach((value, key) => {
427
+ normalizedHeaders.set(key.toLowerCase(), value);
428
+ });
429
+ return {
430
+ request: {
431
+ headers: {
432
+ has: (name) => normalizedHeaders.has(name.toLowerCase()),
433
+ get: (name) => normalizedHeaders.get(name.toLowerCase()),
434
+ entries: () => normalizedHeaders.entries(),
435
+ keys: () => normalizedHeaders.keys(),
436
+ values: () => normalizedHeaders.values()
437
+ },
438
+ method,
439
+ url: url.toString()
440
+ },
441
+ url
442
+ };
443
+ }
444
+ class RpgServerTransport {
445
+ constructor(serverModule, options = {}) {
446
+ this.serverModule = serverModule;
447
+ this.rooms = /* @__PURE__ */ new Map();
448
+ this.servers = /* @__PURE__ */ new Map();
449
+ this.lastKnownHost = "";
450
+ this.initializeMaps = options.initializeMaps ?? true;
451
+ this.mapUpdateToken = resolveMapUpdateToken(options.mapUpdateToken);
452
+ this.partiesPath = normalizePathPrefix(options.partiesPath || "/parties/main", "/parties/main");
453
+ this.tiledBasePaths = options.tiledBasePaths;
454
+ }
455
+ getRoom(roomId) {
456
+ return this.rooms.get(roomId);
457
+ }
458
+ getServer(roomId) {
459
+ return this.servers.get(roomId);
460
+ }
461
+ async ensureRoomAndServer(roomId, host) {
462
+ if (host) {
463
+ this.lastKnownHost = host;
464
+ }
465
+ let room = this.rooms.get(roomId);
466
+ if (!room) {
467
+ room = new PartyRoom(roomId);
468
+ this.rooms.set(roomId, room);
469
+ console.log(`Created new room: ${roomId}`);
470
+ }
471
+ let rpgServer = this.servers.get(roomId);
472
+ if (!rpgServer) {
473
+ rpgServer = new this.serverModule(room);
474
+ this.servers.set(roomId, rpgServer);
475
+ console.log(`Created new server instance for room: ${roomId}`);
476
+ if (typeof rpgServer.onStart === "function") {
477
+ try {
478
+ await rpgServer.onStart();
479
+ console.log(`Server started for room: ${roomId}`);
480
+ } catch (error) {
481
+ console.error(`Error starting server for room ${roomId}:`, error);
482
+ }
483
+ }
484
+ if (this.initializeMaps) {
485
+ await updateMap(roomId, rpgServer, {
486
+ host: host || this.lastKnownHost,
487
+ mapUpdateToken: this.mapUpdateToken,
488
+ tiledBasePaths: this.tiledBasePaths
489
+ });
490
+ }
491
+ }
492
+ room.context.parties = this.buildPartiesContext();
493
+ return { room, rpgServer };
494
+ }
495
+ buildPartiesContext() {
496
+ return {
497
+ main: {
498
+ get: async (targetRoomId) => {
499
+ return {
500
+ fetch: async (path, init) => {
501
+ const method = (init?.method || "GET").toUpperCase();
502
+ const headers = toHeaders(init?.headers);
503
+ const requestPath = path.startsWith("/") ? path : `/${path}`;
504
+ let bodyText = "";
505
+ if (typeof init?.body === "string") {
506
+ bodyText = init.body;
507
+ } else if (typeof init?.body !== "undefined") {
508
+ bodyText = JSON.stringify(init.body);
509
+ }
510
+ return this.dispatchRoomRequest(
511
+ targetRoomId,
512
+ createRequestLike(
513
+ `http://localhost${this.partiesPath}/${targetRoomId}${requestPath}`,
514
+ method,
515
+ headers,
516
+ bodyText
517
+ ),
518
+ this.lastKnownHost
519
+ );
520
+ }
521
+ };
522
+ }
523
+ }
524
+ };
525
+ }
526
+ async dispatchRoomRequest(roomId, requestLike, host) {
527
+ const { room, rpgServer } = await this.ensureRoomAndServer(roomId, host);
528
+ room.context.parties = this.buildPartiesContext();
529
+ const result = await rpgServer.onRequest?.(requestLike);
530
+ return normalizeEngineResponse(result);
531
+ }
532
+ async fetch(request, init) {
533
+ const webRequest = request instanceof Request ? request : new Request(String(request), init);
534
+ const url = new URL(webRequest.url);
535
+ const route = parseHttpRoute(url.pathname, this.partiesPath);
536
+ if (!route) {
537
+ return new Response(JSON.stringify({ error: "Not found" }), {
538
+ status: 404,
539
+ headers: {
540
+ "Content-Type": "application/json"
541
+ }
542
+ });
543
+ }
544
+ const bodyText = await webRequest.text();
545
+ return this.dispatchRoomRequest(
546
+ route.roomId,
547
+ createRequestLike(webRequest.url, webRequest.method.toUpperCase(), toHeaders(webRequest.headers), bodyText),
548
+ url.host
549
+ );
550
+ }
551
+ async updateMap(mapId, payload, options = {}) {
552
+ const roomId = mapId.startsWith("map-") ? mapId : `map-${mapId}`;
553
+ const headers = createMapUpdateHeaders(this.mapUpdateToken, options.headers);
554
+ if (!headers.has("content-type")) {
555
+ headers.set("content-type", "application/json");
556
+ }
557
+ return this.dispatchRoomRequest(
558
+ roomId,
559
+ createRequestLike(
560
+ `http://localhost${this.partiesPath}/${roomId}/map/update`,
561
+ "POST",
562
+ headers,
563
+ JSON.stringify(payload)
564
+ ),
565
+ options.host ?? this.lastKnownHost
566
+ );
567
+ }
568
+ async handleNodeRequest(req, res, next, options = {}) {
569
+ try {
570
+ const headers = toHeaders(req.headers);
571
+ const host = headers.get("host") || "localhost";
572
+ const url = new URL(req.url || "/", `http://${host}`);
573
+ const normalizedPathname = prependMountedPath(url.pathname, options.mountedPath);
574
+ const normalizedUrl = new URL(url.toString());
575
+ normalizedUrl.pathname = normalizedPathname;
576
+ const route = parseHttpRoute(normalizedUrl.pathname, this.partiesPath);
577
+ if (!route) {
578
+ next?.();
579
+ return false;
580
+ }
581
+ const bodyText = await readNodeBody(req);
582
+ const response = await this.dispatchRoomRequest(
583
+ route.roomId,
584
+ createRequestLike(normalizedUrl.toString(), (req.method || "GET").toUpperCase(), headers, bodyText),
585
+ host
586
+ );
587
+ await sendNodeResponse(res, response);
588
+ return true;
589
+ } catch (error) {
590
+ console.error("Error handling RPG-JS request:", error);
591
+ res.statusCode = 500;
592
+ res.setHeader("Content-Type", "application/json");
593
+ res.end(JSON.stringify({ error: "Internal server error" }));
594
+ return true;
595
+ }
596
+ }
597
+ async acceptWebSocket(ws, request) {
598
+ const normalizedRequest = resolveUrlFromSocketRequest(request);
599
+ const route = parseSocketRoute(normalizedRequest.url.pathname, this.partiesPath);
600
+ if (!route) {
601
+ return false;
602
+ }
603
+ try {
604
+ console.log(`WebSocket upgrade request: ${normalizedRequest.url.pathname}`);
605
+ const queryParams = Object.fromEntries(normalizedRequest.url.searchParams.entries());
606
+ console.log(`Room: ${route.roomId}, Query params:`, queryParams);
607
+ const { room, rpgServer } = await this.ensureRoomAndServer(route.roomId, normalizedRequest.url.host);
608
+ room.context.parties = this.buildPartiesContext();
609
+ const connection = new PartyConnection(ws, queryParams._pk, normalizedRequest.rawUrl);
610
+ room.addConnection(connection);
611
+ console.log(`WebSocket connection established: ${connection.id} in room: ${route.roomId}`);
612
+ let isClosed = false;
613
+ const cleanup = async (logMessage, error) => {
614
+ if (isClosed) {
615
+ return;
616
+ }
617
+ isClosed = true;
618
+ if (logMessage) {
619
+ console.log(logMessage);
620
+ }
621
+ if (error) {
622
+ console.error("WebSocket error:", error);
623
+ }
624
+ room.removeConnection(connection.id);
625
+ await rpgServer.onClose?.(connection);
626
+ };
627
+ ws.on("message", async (data) => {
628
+ try {
629
+ const rawMessage = typeof data === "string" ? data : data.toString();
630
+ if (PartyConnection.packetLossEnabled && PartyConnection.packetLossRate > 0) {
631
+ if (!PartyConnection.packetLossFilter || rawMessage.includes(PartyConnection.packetLossFilter)) {
632
+ const random = Math.random();
633
+ if (random < PartyConnection.packetLossRate) {
634
+ console.log(
635
+ `\x1B[31m[PACKET LOSS]\x1B[0m Connection ${connection.id}: Server dropped an incoming packet (${(PartyConnection.packetLossRate * 100).toFixed(1)}% loss rate)`
636
+ );
637
+ console.log(`\x1B[33m[PACKET DATA]\x1B[0m ${rawMessage.slice(0, 100)}${rawMessage.length > 100 ? "..." : ""}`);
638
+ return;
639
+ }
640
+ }
641
+ }
642
+ connection.bufferIncoming(rawMessage, async (batch) => {
643
+ for (const message of batch) {
644
+ await rpgServer.onMessage?.(message, connection);
645
+ }
646
+ });
647
+ } catch (error) {
648
+ console.error("Error processing WebSocket message:", error);
649
+ }
650
+ });
651
+ ws.on("close", () => {
652
+ void cleanup(`WebSocket connection closed: ${connection.id} from room: ${route.roomId}`);
653
+ });
654
+ ws.on("error", (error) => {
655
+ void cleanup(void 0, error);
656
+ });
657
+ if (typeof rpgServer.onConnect === "function") {
658
+ await rpgServer.onConnect(
659
+ connection,
660
+ createConnectionContext(normalizedRequest.url, normalizedRequest.headers, normalizedRequest.method)
661
+ );
662
+ }
663
+ await connection.send({
664
+ type: "connected",
665
+ id: connection.id,
666
+ message: "Connected to RPG-JS server"
667
+ });
668
+ return true;
669
+ } catch (error) {
670
+ console.error("Error establishing WebSocket connection:", error);
671
+ ws.close();
672
+ return true;
673
+ }
674
+ }
675
+ async handleUpgrade(wsServer, request, socket, head) {
676
+ const headers = toHeaders(request.headers);
677
+ const host = headers.get("host") || "localhost";
678
+ const url = new URL(request.url || "/", `http://${host}`);
679
+ if (!parseSocketRoute(url.pathname, this.partiesPath)) {
680
+ return false;
681
+ }
682
+ wsServer.handleUpgrade(request, socket, head, (ws) => {
683
+ void this.acceptWebSocket(ws, request);
684
+ });
685
+ return true;
686
+ }
687
+ }
688
+ function createRpgServerTransport(serverModule, options) {
689
+ return new RpgServerTransport(serverModule, options);
690
+ }
691
+
692
+ export { PartyConnection, PartyRoom, RpgServerTransport, createMapUpdateHeaders, createRpgServerTransport, logNetworkSimulationStatus, resolveMapUpdateToken };
693
+ //# sourceMappingURL=index.js.map