@signe/room 2.0.0 → 2.0.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.
package/dist/index.js CHANGED
@@ -326,6 +326,257 @@ 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
+ newHeaders.set("Access-Control-Allow-Origin", options.origin || "*");
550
+ if (options.credentials) {
551
+ newHeaders.set("Access-Control-Allow-Credentials", "true");
552
+ }
553
+ if (options.exposedHeaders && options.exposedHeaders.length) {
554
+ newHeaders.set("Access-Control-Expose-Headers", options.exposedHeaders.join(", "));
555
+ }
556
+ if (options.methods && options.methods.length) {
557
+ newHeaders.set("Access-Control-Allow-Methods", options.methods.join(", "));
558
+ } else {
559
+ newHeaders.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
560
+ }
561
+ if (options.allowedHeaders && options.allowedHeaders.length) {
562
+ newHeaders.set("Access-Control-Allow-Headers", options.allowedHeaders.join(", "));
563
+ } else {
564
+ newHeaders.set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With");
565
+ }
566
+ if (options.maxAge) {
567
+ newHeaders.set("Access-Control-Max-Age", options.maxAge.toString());
568
+ }
569
+ return new Response(res.body, {
570
+ status: res.status,
571
+ headers: newHeaders
572
+ });
573
+ }
574
+ __name(cors, "cors");
575
+ function createCorsInterceptor(options = {}) {
576
+ return (res) => cors(res, options);
577
+ }
578
+ __name(createCorsInterceptor, "createCorsInterceptor");
579
+
329
580
  // src/server.ts
330
581
  var Message = z.object({
331
582
  action: z.string(),
@@ -980,10 +1231,13 @@ var Server = class {
980
1231
  async onRequest(req) {
981
1232
  const isFromShard = req.headers.has("x-forwarded-by-shard");
982
1233
  const shardId = req.headers.get("x-shard-id");
1234
+ const res = new ServerResponse([
1235
+ createCorsInterceptor()
1236
+ ]);
983
1237
  if (isFromShard) {
984
- return this.handleShardRequest(req, shardId);
1238
+ return this.handleShardRequest(req, res, shardId);
985
1239
  }
986
- return this.handleDirectRequest(req);
1240
+ return this.handleDirectRequest(req, res);
987
1241
  }
988
1242
  /**
989
1243
  * @method handleDirectRequest
@@ -993,32 +1247,23 @@ var Server = class {
993
1247
  * @description Processes requests received directly from clients
994
1248
  * @returns {Promise<Response>} The response to return to the client
995
1249
  */
996
- async handleDirectRequest(req) {
1250
+ async handleDirectRequest(req, res) {
997
1251
  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
1252
  if (!subRoom) {
1004
- return res({
1005
- error: "Not found"
1006
- }, 404);
1253
+ return res.notFound();
1007
1254
  }
1008
- const response2 = await this.tryMatchRequestHandler(req, subRoom);
1255
+ const response2 = await this.tryMatchRequestHandler(req, res, subRoom);
1009
1256
  if (response2) {
1010
1257
  return response2;
1011
1258
  }
1012
- const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(req, this.room));
1259
+ const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(req, res));
1013
1260
  if (!legacyResponse) {
1014
- return res({
1015
- error: "Not found"
1016
- }, 404);
1261
+ return res.notFound();
1017
1262
  }
1018
1263
  if (legacyResponse instanceof Response) {
1019
1264
  return legacyResponse;
1020
1265
  }
1021
- return res(legacyResponse, 200);
1266
+ return res.success(legacyResponse);
1022
1267
  }
1023
1268
  /**
1024
1269
  * @method tryMatchRequestHandler
@@ -1029,7 +1274,7 @@ var Server = class {
1029
1274
  * @description Attempts to match the request to a registered @Request handler
1030
1275
  * @returns {Promise<Response | null>} The response or null if no handler matched
1031
1276
  */
1032
- async tryMatchRequestHandler(req, subRoom) {
1277
+ async tryMatchRequestHandler(req, res, subRoom) {
1033
1278
  const requestHandlers = subRoom.constructor["_requestMetadata"];
1034
1279
  if (!requestHandlers) {
1035
1280
  return null;
@@ -1054,11 +1299,7 @@ var Server = class {
1054
1299
  return isAuthorized;
1055
1300
  }
1056
1301
  if (!isAuthorized) {
1057
- return new Response(JSON.stringify({
1058
- error: "Unauthorized"
1059
- }), {
1060
- status: 403
1061
- });
1302
+ return res.notPermitted();
1062
1303
  }
1063
1304
  }
1064
1305
  let bodyData = null;
@@ -1073,41 +1314,27 @@ var Server = class {
1073
1314
  const body = await req.json();
1074
1315
  const validation = handler.bodyValidation.safeParse(body);
1075
1316
  if (!validation.success) {
1076
- return new Response(JSON.stringify({
1077
- error: "Invalid request body",
1317
+ return res.badRequest("Invalid request body", {
1078
1318
  details: validation.error
1079
- }), {
1080
- status: 400
1081
1319
  });
1082
1320
  }
1083
1321
  bodyData = validation.data;
1084
1322
  }
1085
1323
  } catch (error) {
1086
- return new Response(JSON.stringify({
1087
- error: "Failed to parse request body"
1088
- }), {
1089
- status: 400
1090
- });
1324
+ return res.badRequest("Failed to parse request body");
1091
1325
  }
1092
1326
  }
1093
1327
  try {
1094
- const result = await awaitReturn(subRoom[handler.key](req, bodyData, params, this.room));
1328
+ req["data"] = bodyData;
1329
+ req["params"] = params;
1330
+ const result = await awaitReturn(subRoom[handler.key](req, res));
1095
1331
  if (result instanceof Response) {
1096
1332
  return result;
1097
1333
  }
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
- });
1334
+ return res.success(result);
1104
1335
  } catch (error) {
1105
1336
  console.error("Error executing request handler:", error);
1106
- return new Response(JSON.stringify({
1107
- error: "Internal server error"
1108
- }), {
1109
- status: 500
1110
- });
1337
+ return res.serverError();
1111
1338
  }
1112
1339
  }
1113
1340
  }
@@ -1161,43 +1388,29 @@ var Server = class {
1161
1388
  * @description Processes requests forwarded by shards, preserving client context
1162
1389
  * @returns {Promise<Response>} The response to return to the shard (which will forward it to the client)
1163
1390
  */
1164
- async handleShardRequest(req, shardId) {
1391
+ async handleShardRequest(req, res, shardId) {
1165
1392
  const subRoom = await this.getSubRoom();
1166
1393
  if (!subRoom) {
1167
- return new Response(JSON.stringify({
1168
- error: "Not found"
1169
- }), {
1170
- status: 404
1171
- });
1394
+ return res.notFound();
1172
1395
  }
1173
1396
  const originalClientIp = req.headers.get("x-original-client-ip");
1174
1397
  const enhancedReq = this.createEnhancedRequest(req, originalClientIp);
1175
1398
  try {
1176
- const response2 = await this.tryMatchRequestHandler(enhancedReq, subRoom);
1399
+ const response2 = await this.tryMatchRequestHandler(enhancedReq, res, subRoom);
1177
1400
  if (response2) {
1178
1401
  return response2;
1179
1402
  }
1180
- const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(enhancedReq, this.room));
1403
+ const legacyResponse = await awaitReturn(subRoom["onRequest"]?.(enhancedReq, res));
1181
1404
  if (!legacyResponse) {
1182
- return new Response(JSON.stringify({
1183
- error: "Not found"
1184
- }), {
1185
- status: 404
1186
- });
1405
+ return res.notFound();
1187
1406
  }
1188
1407
  if (legacyResponse instanceof Response) {
1189
1408
  return legacyResponse;
1190
1409
  }
1191
- return new Response(JSON.stringify(legacyResponse), {
1192
- status: 200
1193
- });
1410
+ return res.success(legacyResponse);
1194
1411
  } catch (error) {
1195
1412
  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
- });
1413
+ return res.serverError();
1201
1414
  }
1202
1415
  }
1203
1416
  /**
@@ -1858,14 +2071,12 @@ var WorldRoom = class {
1858
2071
  room.maxShards.set(roomConfig.maxShards);
1859
2072
  }
1860
2073
  }
1861
- async updateShardStats(req) {
2074
+ async updateShardStats(req, res) {
1862
2075
  const body = await req.json();
1863
2076
  const { shardId, connections, status } = body;
1864
2077
  const shard = this.shards()[shardId];
1865
2078
  if (!shard) {
1866
- return {
1867
- error: `Shard ${shardId} not found`
1868
- };
2079
+ return res.notFound(`Shard ${shardId} not found`);
1869
2080
  }
1870
2081
  shard.currentConnections.set(connections);
1871
2082
  if (status) {
@@ -1873,23 +2084,20 @@ var WorldRoom = class {
1873
2084
  }
1874
2085
  shard.lastHeartbeat.set(Date.now());
1875
2086
  }
1876
- async scaleRoom(req) {
2087
+ async scaleRoom(req, res) {
1877
2088
  const data = await req.json();
1878
2089
  const { targetShardCount, shardTemplate, roomId } = data;
1879
2090
  const room = this.rooms()[roomId];
1880
2091
  if (!room) {
1881
- return {
1882
- error: `Room ${roomId} does not exist`
1883
- };
2092
+ return res.notFound(`Room ${roomId} does not exist`);
1884
2093
  }
1885
2094
  const roomShards = Object.values(this.shards()).filter((shard) => shard.roomId() === roomId);
1886
2095
  const previousShardCount = roomShards.length;
1887
2096
  if (room.maxShards() !== void 0 && targetShardCount > room.maxShards()) {
1888
- return {
1889
- error: `Cannot scale beyond maximum allowed shards (${room.maxShards()})`,
2097
+ return res.badRequest(`Cannot scale beyond maximum allowed shards (${room.maxShards()})`, {
1890
2098
  roomId,
1891
2099
  currentShardCount: previousShardCount
1892
- };
2100
+ });
1893
2101
  }
1894
2102
  if (targetShardCount < previousShardCount) {
1895
2103
  const shardsToRemove = [
@@ -1915,45 +2123,34 @@ var WorldRoom = class {
1915
2123
  }
1916
2124
  }
1917
2125
  }
1918
- async connect(req) {
2126
+ async connect(req, res) {
1919
2127
  try {
1920
2128
  let data;
1921
2129
  try {
1922
2130
  const body = await req.text();
1923
2131
  if (!body || body.trim() === "") {
1924
- return response(400, {
1925
- error: "Request body is empty"
1926
- });
2132
+ return res.badRequest("Request body is empty");
1927
2133
  }
1928
2134
  data = JSON.parse(body);
1929
2135
  } catch (parseError) {
1930
- return response(400, {
1931
- error: "Invalid JSON in request body"
1932
- });
2136
+ return res.badRequest("Invalid JSON in request body");
1933
2137
  }
1934
2138
  if (!data.roomId) {
1935
- return response(400, {
1936
- error: "roomId parameter is required"
1937
- });
2139
+ return res.badRequest("roomId parameter is required");
1938
2140
  }
1939
2141
  const autoCreate = data.autoCreate !== void 0 ? data.autoCreate : true;
1940
2142
  const result = await this.findOptimalShard(data.roomId, autoCreate);
1941
2143
  if ("error" in result) {
1942
- return response(404, {
1943
- error: result.error
1944
- });
2144
+ return res.notFound(result.error);
1945
2145
  }
1946
- return response(200, {
2146
+ return res.success({
1947
2147
  success: true,
1948
2148
  shardId: result.shardId,
1949
2149
  url: result.url
1950
2150
  });
1951
2151
  } catch (error) {
1952
2152
  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
- });
2153
+ return res.serverError();
1957
2154
  }
1958
2155
  }
1959
2156
  async findOptimalShard(roomId, autoCreate = true) {
@@ -2087,7 +2284,8 @@ _ts_decorate([
2087
2284
  ]),
2088
2285
  _ts_metadata("design:type", Function),
2089
2286
  _ts_metadata("design:paramtypes", [
2090
- typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0
2287
+ typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0,
2288
+ typeof ServerResponse === "undefined" ? Object : ServerResponse
2091
2289
  ]),
2092
2290
  _ts_metadata("design:returntype", Promise)
2093
2291
  ], WorldRoom.prototype, "updateShardStats", null);
@@ -2101,7 +2299,8 @@ _ts_decorate([
2101
2299
  ]),
2102
2300
  _ts_metadata("design:type", Function),
2103
2301
  _ts_metadata("design:paramtypes", [
2104
- typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0
2302
+ typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0,
2303
+ typeof ServerResponse === "undefined" ? Object : ServerResponse
2105
2304
  ]),
2106
2305
  _ts_metadata("design:returntype", Promise)
2107
2306
  ], WorldRoom.prototype, "scaleRoom", null);
@@ -2112,7 +2311,8 @@ _ts_decorate([
2112
2311
  }),
2113
2312
  _ts_metadata("design:type", Function),
2114
2313
  _ts_metadata("design:paramtypes", [
2115
- typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0
2314
+ typeof party_exports === "undefined" || typeof void 0 === "undefined" ? Object : void 0,
2315
+ typeof ServerResponse === "undefined" ? Object : ServerResponse
2116
2316
  ]),
2117
2317
  _ts_metadata("design:returntype", Promise)
2118
2318
  ], WorldRoom.prototype, "connect", null);
@@ -2138,6 +2338,7 @@ export {
2138
2338
  RoomGuard,
2139
2339
  Server,
2140
2340
  ServerIo,
2341
+ ServerResponse,
2141
2342
  Shard,
2142
2343
  WorldRoom,
2143
2344
  request,