@signe/room 2.0.0 → 2.1.0

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/dist/index.js CHANGED
@@ -326,6 +326,260 @@ function response(status, body) {
326
326
  }
327
327
  __name(response, "response");
328
328
 
329
+ // src/request/response.ts
330
+ var ServerResponse = class {
331
+ static {
332
+ __name(this, "ServerResponse");
333
+ }
334
+ interceptors;
335
+ statusCode = 200;
336
+ responseBody = {};
337
+ responseHeaders = {
338
+ "Content-Type": "application/json"
339
+ };
340
+ /**
341
+ * Creates a new ServerResponse instance
342
+ * @param interceptors Array of interceptor functions that can modify the response
343
+ */
344
+ constructor(interceptors = []) {
345
+ this.interceptors = interceptors;
346
+ }
347
+ /**
348
+ * Sets the status code for the response
349
+ * @param code HTTP status code
350
+ * @returns this instance for chaining
351
+ */
352
+ status(code) {
353
+ this.statusCode = code;
354
+ return this;
355
+ }
356
+ /**
357
+ * Sets the response body and returns this instance (chainable method)
358
+ *
359
+ * @param body Response body
360
+ * @returns this instance for chaining
361
+ */
362
+ body(body) {
363
+ this.responseBody = body;
364
+ return this;
365
+ }
366
+ /**
367
+ * Adds a header to the response
368
+ * @param name Header name
369
+ * @param value Header value
370
+ * @returns this instance for chaining
371
+ */
372
+ header(name, value) {
373
+ this.responseHeaders[name] = value;
374
+ return this;
375
+ }
376
+ /**
377
+ * Adds multiple headers to the response
378
+ * @param headers Object containing headers
379
+ * @returns this instance for chaining
380
+ */
381
+ setHeaders(headers) {
382
+ this.responseHeaders = {
383
+ ...this.responseHeaders,
384
+ ...headers
385
+ };
386
+ return this;
387
+ }
388
+ /**
389
+ * Add an interceptor to the chain
390
+ * @param interceptor Function that takes a Response and returns a modified Response
391
+ * @returns this instance for chaining
392
+ */
393
+ use(interceptor) {
394
+ this.interceptors.push(interceptor);
395
+ return this;
396
+ }
397
+ /**
398
+ * Builds and returns the Response object after applying all interceptors
399
+ * @returns Promise<Response> The final Response object
400
+ * @private Internal method used by terminal methods
401
+ */
402
+ async buildResponse() {
403
+ let response2 = new Response(JSON.stringify(this.responseBody), {
404
+ status: this.statusCode,
405
+ headers: this.responseHeaders
406
+ });
407
+ for (const interceptor of this.interceptors) {
408
+ try {
409
+ const interceptedResponse = interceptor(response2);
410
+ if (interceptedResponse instanceof Promise) {
411
+ response2 = await interceptedResponse;
412
+ } else {
413
+ response2 = interceptedResponse;
414
+ }
415
+ } catch (error) {
416
+ console.error("Error in interceptor:", error);
417
+ }
418
+ }
419
+ return response2;
420
+ }
421
+ /**
422
+ * Sets the response body to the JSON-stringified version of the provided value
423
+ * and sends the response (terminal method)
424
+ *
425
+ * @param body Response body to be JSON stringified
426
+ * @returns Promise<Response> The final Response object
427
+ */
428
+ async json(body) {
429
+ this.responseBody = body;
430
+ this.responseHeaders["Content-Type"] = "application/json";
431
+ return this.buildResponse();
432
+ }
433
+ /**
434
+ * Sends the response with the current configuration (terminal method)
435
+ *
436
+ * @param body Optional body to set before sending
437
+ * @returns Promise<Response> The final Response object
438
+ */
439
+ async send(body) {
440
+ if (body !== void 0) {
441
+ this.responseBody = body;
442
+ }
443
+ return this.buildResponse();
444
+ }
445
+ /**
446
+ * Sends a plain text response (terminal method)
447
+ *
448
+ * @param text Text to send
449
+ * @returns Promise<Response> The final Response object
450
+ */
451
+ async text(text) {
452
+ this.responseBody = text;
453
+ this.responseHeaders["Content-Type"] = "text/plain";
454
+ let response2 = new Response(text, {
455
+ status: this.statusCode,
456
+ headers: this.responseHeaders
457
+ });
458
+ for (const interceptor of this.interceptors) {
459
+ try {
460
+ const interceptedResponse = interceptor(response2);
461
+ if (interceptedResponse instanceof Promise) {
462
+ response2 = await interceptedResponse;
463
+ } else {
464
+ response2 = interceptedResponse;
465
+ }
466
+ } catch (error) {
467
+ console.error("Error in interceptor:", error);
468
+ }
469
+ }
470
+ return response2;
471
+ }
472
+ /**
473
+ * Redirects to the specified URL (terminal method)
474
+ *
475
+ * @param url URL to redirect to
476
+ * @param statusCode HTTP status code (default: 302)
477
+ * @returns Promise<Response> The final Response object
478
+ */
479
+ async redirect(url, statusCode = 302) {
480
+ this.statusCode = statusCode;
481
+ this.responseHeaders["Location"] = url;
482
+ return this.buildResponse();
483
+ }
484
+ /**
485
+ * Creates a success response with status 200
486
+ * @param body Response body
487
+ * @returns Promise<Response> The final Response object
488
+ */
489
+ async success(body = {}) {
490
+ return this.status(200).json(body);
491
+ }
492
+ /**
493
+ * Creates an error response with status 400
494
+ * @param message Error message
495
+ * @param details Additional error details
496
+ * @returns Promise<Response> The final Response object
497
+ */
498
+ async badRequest(message, details = {}) {
499
+ return this.status(400).json({
500
+ error: message,
501
+ ...details
502
+ });
503
+ }
504
+ /**
505
+ * Creates an error response with status 403
506
+ * @param message Error message
507
+ * @returns Promise<Response> The final Response object
508
+ */
509
+ async notPermitted(message = "Not permitted") {
510
+ return this.status(403).json({
511
+ error: message
512
+ });
513
+ }
514
+ /**
515
+ * Creates an error response with status 401
516
+ * @param message Error message
517
+ * @returns Promise<Response> The final Response object
518
+ */
519
+ async unauthorized(message = "Unauthorized") {
520
+ return this.status(401).json({
521
+ error: message
522
+ });
523
+ }
524
+ /**
525
+ * Creates an error response with status 404
526
+ * @param message Error message
527
+ * @returns Promise<Response> The final Response object
528
+ */
529
+ async notFound(message = "Not found") {
530
+ return this.status(404).json({
531
+ error: message
532
+ });
533
+ }
534
+ /**
535
+ * Creates an error response with status 500
536
+ * @param message Error message
537
+ * @returns Promise<Response> The final Response object
538
+ */
539
+ async serverError(message = "Internal Server Error") {
540
+ return this.status(500).json({
541
+ error: message
542
+ });
543
+ }
544
+ };
545
+
546
+ // src/request/cors.ts
547
+ function cors(res, options = {}) {
548
+ const newHeaders = new Headers(res.headers);
549
+ const requestOrigin = options.origin || "*";
550
+ newHeaders.set("Access-Control-Allow-Origin", requestOrigin);
551
+ if (options.credentials) {
552
+ newHeaders.set("Access-Control-Allow-Credentials", "true");
553
+ }
554
+ if (options.exposedHeaders && options.exposedHeaders.length) {
555
+ newHeaders.set("Access-Control-Expose-Headers", options.exposedHeaders.join(", "));
556
+ }
557
+ if (options.methods && options.methods.length) {
558
+ newHeaders.set("Access-Control-Allow-Methods", options.methods.join(", "));
559
+ } else {
560
+ newHeaders.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
561
+ }
562
+ if (options.allowedHeaders && options.allowedHeaders.length) {
563
+ newHeaders.set("Access-Control-Allow-Headers", options.allowedHeaders.join(", "));
564
+ } else {
565
+ newHeaders.set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
566
+ }
567
+ if (options.maxAge) {
568
+ newHeaders.set("Access-Control-Max-Age", options.maxAge.toString());
569
+ } else {
570
+ newHeaders.set("Access-Control-Max-Age", "86400");
571
+ }
572
+ return new Response(res.body, {
573
+ status: res.status,
574
+ headers: newHeaders
575
+ });
576
+ }
577
+ __name(cors, "cors");
578
+ function createCorsInterceptor(options = {}) {
579
+ return (res) => cors(res, options);
580
+ }
581
+ __name(createCorsInterceptor, "createCorsInterceptor");
582
+
329
583
  // src/server.ts
330
584
  var Message = z.object({
331
585
  action: z.string(),
@@ -727,6 +981,10 @@ var Server = class {
727
981
  return;
728
982
  }
729
983
  const subRoom = await this.getSubRoom();
984
+ if (!subRoom) {
985
+ console.warn("Room not found");
986
+ return;
987
+ }
730
988
  const roomGuards = subRoom.constructor["_roomGuards"] || [];
731
989
  for (const guard of roomGuards) {
732
990
  const isAuthorized = await guard(sender, result.data.value, this.room);
@@ -980,10 +1238,16 @@ var Server = class {
980
1238
  async onRequest(req) {
981
1239
  const isFromShard = req.headers.has("x-forwarded-by-shard");
982
1240
  const shardId = req.headers.get("x-shard-id");
1241
+ const res = new ServerResponse([
1242
+ createCorsInterceptor()
1243
+ ]);
1244
+ if (req.method === "OPTIONS") {
1245
+ return res.status(200).send({});
1246
+ }
983
1247
  if (isFromShard) {
984
- return this.handleShardRequest(req, shardId);
1248
+ return this.handleShardRequest(req, res, shardId);
985
1249
  }
986
- return this.handleDirectRequest(req);
1250
+ return this.handleDirectRequest(req, res);
987
1251
  }
988
1252
  /**
989
1253
  * @method handleDirectRequest
@@ -993,32 +1257,23 @@ var Server = class {
993
1257
  * @description Processes requests received directly from clients
994
1258
  * @returns {Promise<Response>} The response to return to the client
995
1259
  */
996
- async handleDirectRequest(req) {
1260
+ async handleDirectRequest(req, res) {
997
1261
  const subRoom = await this.getSubRoom();
998
- const res = /* @__PURE__ */ __name((body, status) => {
999
- return new Response(JSON.stringify(body), {
1000
- status
1001
- });
1002
- }, "res");
1003
1262
  if (!subRoom) {
1004
- return res({
1005
- error: "Not found"
1006
- }, 404);
1263
+ return res.notFound();
1007
1264
  }
1008
- const response2 = await this.tryMatchRequestHandler(req, subRoom);
1265
+ const response2 = await this.tryMatchRequestHandler(req, res, subRoom);
1009
1266
  if (response2) {
1010
1267
  return response2;
1011
1268
  }
1012
- const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(req, this.room));
1269
+ const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(req, res));
1013
1270
  if (!legacyResponse) {
1014
- return res({
1015
- error: "Not found"
1016
- }, 404);
1271
+ return res.notFound();
1017
1272
  }
1018
1273
  if (legacyResponse instanceof Response) {
1019
1274
  return legacyResponse;
1020
1275
  }
1021
- return res(legacyResponse, 200);
1276
+ return res.success(legacyResponse);
1022
1277
  }
1023
1278
  /**
1024
1279
  * @method tryMatchRequestHandler
@@ -1029,7 +1284,7 @@ var Server = class {
1029
1284
  * @description Attempts to match the request to a registered @Request handler
1030
1285
  * @returns {Promise<Response | null>} The response or null if no handler matched
1031
1286
  */
1032
- async tryMatchRequestHandler(req, subRoom) {
1287
+ async tryMatchRequestHandler(req, res, subRoom) {
1033
1288
  const requestHandlers = subRoom.constructor["_requestMetadata"];
1034
1289
  if (!requestHandlers) {
1035
1290
  return null;
@@ -1054,11 +1309,7 @@ var Server = class {
1054
1309
  return isAuthorized;
1055
1310
  }
1056
1311
  if (!isAuthorized) {
1057
- return new Response(JSON.stringify({
1058
- error: "Unauthorized"
1059
- }), {
1060
- status: 403
1061
- });
1312
+ return res.notPermitted();
1062
1313
  }
1063
1314
  }
1064
1315
  let bodyData = null;
@@ -1073,41 +1324,27 @@ var Server = class {
1073
1324
  const body = await req.json();
1074
1325
  const validation = handler.bodyValidation.safeParse(body);
1075
1326
  if (!validation.success) {
1076
- return new Response(JSON.stringify({
1077
- error: "Invalid request body",
1327
+ return res.badRequest("Invalid request body", {
1078
1328
  details: validation.error
1079
- }), {
1080
- status: 400
1081
1329
  });
1082
1330
  }
1083
1331
  bodyData = validation.data;
1084
1332
  }
1085
1333
  } catch (error) {
1086
- return new Response(JSON.stringify({
1087
- error: "Failed to parse request body"
1088
- }), {
1089
- status: 400
1090
- });
1334
+ return res.badRequest("Failed to parse request body");
1091
1335
  }
1092
1336
  }
1093
1337
  try {
1094
- const result = await awaitReturn(subRoom[handler.key](req, bodyData, params, this.room));
1338
+ req["data"] = bodyData;
1339
+ req["params"] = params;
1340
+ const result = await awaitReturn(subRoom[handler.key](req, res));
1095
1341
  if (result instanceof Response) {
1096
1342
  return result;
1097
1343
  }
1098
- return new Response(typeof result === "string" ? result : JSON.stringify(result), {
1099
- status: 200,
1100
- headers: {
1101
- "Content-Type": typeof result === "string" ? "text/plain" : "application/json"
1102
- }
1103
- });
1344
+ return res.success(result);
1104
1345
  } catch (error) {
1105
1346
  console.error("Error executing request handler:", error);
1106
- return new Response(JSON.stringify({
1107
- error: "Internal server error"
1108
- }), {
1109
- status: 500
1110
- });
1347
+ return res.serverError();
1111
1348
  }
1112
1349
  }
1113
1350
  }
@@ -1161,43 +1398,29 @@ var Server = class {
1161
1398
  * @description Processes requests forwarded by shards, preserving client context
1162
1399
  * @returns {Promise<Response>} The response to return to the shard (which will forward it to the client)
1163
1400
  */
1164
- async handleShardRequest(req, shardId) {
1401
+ async handleShardRequest(req, res, shardId) {
1165
1402
  const subRoom = await this.getSubRoom();
1166
1403
  if (!subRoom) {
1167
- return new Response(JSON.stringify({
1168
- error: "Not found"
1169
- }), {
1170
- status: 404
1171
- });
1404
+ return res.notFound();
1172
1405
  }
1173
1406
  const originalClientIp = req.headers.get("x-original-client-ip");
1174
1407
  const enhancedReq = this.createEnhancedRequest(req, originalClientIp);
1175
1408
  try {
1176
- const response2 = await this.tryMatchRequestHandler(enhancedReq, subRoom);
1409
+ const response2 = await this.tryMatchRequestHandler(enhancedReq, res, subRoom);
1177
1410
  if (response2) {
1178
1411
  return response2;
1179
1412
  }
1180
- const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(enhancedReq, this.room));
1413
+ const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(enhancedReq, res));
1181
1414
  if (!legacyResponse) {
1182
- return new Response(JSON.stringify({
1183
- error: "Not found"
1184
- }), {
1185
- status: 404
1186
- });
1415
+ return res.notFound();
1187
1416
  }
1188
1417
  if (legacyResponse instanceof Response) {
1189
1418
  return legacyResponse;
1190
1419
  }
1191
- return new Response(JSON.stringify(legacyResponse), {
1192
- status: 200
1193
- });
1420
+ return res.success(legacyResponse);
1194
1421
  } catch (error) {
1195
1422
  console.error(`Error processing request from shard ${shardId}:`, error);
1196
- return new Response(JSON.stringify({
1197
- error: "Internal server error"
1198
- }), {
1199
- status: 500
1200
- });
1423
+ return res.serverError();
1201
1424
  }
1202
1425
  }
1203
1426
  /**
@@ -1858,14 +2081,12 @@ var WorldRoom = class {
1858
2081
  room.maxShards.set(roomConfig.maxShards);
1859
2082
  }
1860
2083
  }
1861
- async updateShardStats(req) {
2084
+ async updateShardStats(req, res) {
1862
2085
  const body = await req.json();
1863
2086
  const { shardId, connections, status } = body;
1864
2087
  const shard = this.shards()[shardId];
1865
2088
  if (!shard) {
1866
- return {
1867
- error: `Shard ${shardId} not found`
1868
- };
2089
+ return res.notFound(`Shard ${shardId} not found`);
1869
2090
  }
1870
2091
  shard.currentConnections.set(connections);
1871
2092
  if (status) {
@@ -1873,23 +2094,20 @@ var WorldRoom = class {
1873
2094
  }
1874
2095
  shard.lastHeartbeat.set(Date.now());
1875
2096
  }
1876
- async scaleRoom(req) {
2097
+ async scaleRoom(req, res) {
1877
2098
  const data = await req.json();
1878
2099
  const { targetShardCount, shardTemplate, roomId } = data;
1879
2100
  const room = this.rooms()[roomId];
1880
2101
  if (!room) {
1881
- return {
1882
- error: `Room ${roomId} does not exist`
1883
- };
2102
+ return res.notFound(`Room ${roomId} does not exist`);
1884
2103
  }
1885
2104
  const roomShards = Object.values(this.shards()).filter((shard) => shard.roomId() === roomId);
1886
2105
  const previousShardCount = roomShards.length;
1887
2106
  if (room.maxShards() !== void 0 && targetShardCount > room.maxShards()) {
1888
- return {
1889
- error: `Cannot scale beyond maximum allowed shards (${room.maxShards()})`,
2107
+ return res.badRequest(`Cannot scale beyond maximum allowed shards (${room.maxShards()})`, {
1890
2108
  roomId,
1891
2109
  currentShardCount: previousShardCount
1892
- };
2110
+ });
1893
2111
  }
1894
2112
  if (targetShardCount < previousShardCount) {
1895
2113
  const shardsToRemove = [
@@ -1915,45 +2133,34 @@ var WorldRoom = class {
1915
2133
  }
1916
2134
  }
1917
2135
  }
1918
- async connect(req) {
2136
+ async connect(req, res) {
1919
2137
  try {
1920
2138
  let data;
1921
2139
  try {
1922
2140
  const body = await req.text();
1923
2141
  if (!body || body.trim() === "") {
1924
- return response(400, {
1925
- error: "Request body is empty"
1926
- });
2142
+ return res.badRequest("Request body is empty");
1927
2143
  }
1928
2144
  data = JSON.parse(body);
1929
2145
  } catch (parseError) {
1930
- return response(400, {
1931
- error: "Invalid JSON in request body"
1932
- });
2146
+ return res.badRequest("Invalid JSON in request body");
1933
2147
  }
1934
2148
  if (!data.roomId) {
1935
- return response(400, {
1936
- error: "roomId parameter is required"
1937
- });
2149
+ return res.badRequest("roomId parameter is required");
1938
2150
  }
1939
2151
  const autoCreate = data.autoCreate !== void 0 ? data.autoCreate : true;
1940
2152
  const result = await this.findOptimalShard(data.roomId, autoCreate);
1941
2153
  if ("error" in result) {
1942
- return response(404, {
1943
- error: result.error
1944
- });
2154
+ return res.notFound(result.error);
1945
2155
  }
1946
- return response(200, {
2156
+ return res.success({
1947
2157
  success: true,
1948
2158
  shardId: result.shardId,
1949
2159
  url: result.url
1950
2160
  });
1951
2161
  } catch (error) {
1952
2162
  console.error("Error connecting to shard:", error);
1953
- return response(500, {
1954
- error: "Internal server error",
1955
- details: error instanceof Error ? error.message : String(error)
1956
- });
2163
+ return res.serverError();
1957
2164
  }
1958
2165
  }
1959
2166
  async findOptimalShard(roomId, autoCreate = true) {
@@ -2087,7 +2294,8 @@ _ts_decorate([
2087
2294
  ]),
2088
2295
  _ts_metadata("design:type", Function),
2089
2296
  _ts_metadata("design:paramtypes", [
2090
- typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0
2297
+ typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0,
2298
+ typeof ServerResponse === "undefined" ? Object : ServerResponse
2091
2299
  ]),
2092
2300
  _ts_metadata("design:returntype", Promise)
2093
2301
  ], WorldRoom.prototype, "updateShardStats", null);
@@ -2101,7 +2309,8 @@ _ts_decorate([
2101
2309
  ]),
2102
2310
  _ts_metadata("design:type", Function),
2103
2311
  _ts_metadata("design:paramtypes", [
2104
- typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0
2312
+ typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0,
2313
+ typeof ServerResponse === "undefined" ? Object : ServerResponse
2105
2314
  ]),
2106
2315
  _ts_metadata("design:returntype", Promise)
2107
2316
  ], WorldRoom.prototype, "scaleRoom", null);
@@ -2112,7 +2321,8 @@ _ts_decorate([
2112
2321
  }),
2113
2322
  _ts_metadata("design:type", Function),
2114
2323
  _ts_metadata("design:paramtypes", [
2115
- typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0
2324
+ typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0,
2325
+ typeof ServerResponse === "undefined" ? Object : ServerResponse
2116
2326
  ]),
2117
2327
  _ts_metadata("design:returntype", Promise)
2118
2328
  ], WorldRoom.prototype, "connect", null);
@@ -2138,6 +2348,7 @@ export {
2138
2348
  RoomGuard,
2139
2349
  Server,
2140
2350
  ServerIo,
2351
+ ServerResponse,
2141
2352
  Shard,
2142
2353
  WorldRoom,
2143
2354
  request,