@colyseus/core 0.16.0-preview.35 → 0.16.0-preview.37

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/build/MatchMaker.d.ts +5 -8
  2. package/build/MatchMaker.js +40 -13
  3. package/build/MatchMaker.js.map +2 -2
  4. package/build/MatchMaker.mjs +40 -13
  5. package/build/MatchMaker.mjs.map +2 -2
  6. package/build/Protocol.d.ts +3 -3
  7. package/build/Protocol.js +5 -0
  8. package/build/Protocol.js.map +2 -2
  9. package/build/Protocol.mjs +5 -0
  10. package/build/Protocol.mjs.map +2 -2
  11. package/build/Room.d.ts +33 -10
  12. package/build/Room.js +156 -60
  13. package/build/Room.js.map +2 -2
  14. package/build/Room.mjs +158 -62
  15. package/build/Room.mjs.map +2 -2
  16. package/build/Server.d.ts +2 -0
  17. package/build/Server.js +10 -1
  18. package/build/Server.js.map +2 -2
  19. package/build/Server.mjs +10 -1
  20. package/build/Server.mjs.map +2 -2
  21. package/build/Stats.d.ts +2 -0
  22. package/build/Stats.js +37 -2
  23. package/build/Stats.js.map +2 -2
  24. package/build/Stats.mjs +25 -2
  25. package/build/Stats.mjs.map +2 -2
  26. package/build/Transport.d.ts +5 -0
  27. package/build/Transport.js.map +2 -2
  28. package/build/Transport.mjs.map +2 -2
  29. package/build/errors/RoomExceptions.d.ts +39 -0
  30. package/build/errors/RoomExceptions.js +100 -0
  31. package/build/errors/RoomExceptions.js.map +7 -0
  32. package/build/errors/RoomExceptions.mjs +71 -0
  33. package/build/errors/RoomExceptions.mjs.map +7 -0
  34. package/build/index.d.ts +2 -1
  35. package/build/index.js +19 -0
  36. package/build/index.js.map +2 -2
  37. package/build/index.mjs +20 -0
  38. package/build/index.mjs.map +2 -2
  39. package/build/matchmaker/controller.d.ts +2 -1
  40. package/build/matchmaker/controller.js.map +2 -2
  41. package/build/matchmaker/controller.mjs.map +2 -2
  42. package/build/matchmaker/driver/index.d.ts +2 -2
  43. package/build/matchmaker/driver/index.js +2 -2
  44. package/build/matchmaker/driver/index.js.map +2 -2
  45. package/build/matchmaker/driver/index.mjs +5 -4
  46. package/build/matchmaker/driver/index.mjs.map +2 -2
  47. package/build/matchmaker/driver/local/LocalDriver.js +2 -2
  48. package/build/matchmaker/driver/local/LocalDriver.js.map +2 -2
  49. package/build/matchmaker/driver/local/LocalDriver.mjs +2 -2
  50. package/build/matchmaker/driver/local/LocalDriver.mjs.map +2 -2
  51. package/build/serializer/SchemaSerializer.d.ts +2 -2
  52. package/build/serializer/SchemaSerializer.js +1 -1
  53. package/build/serializer/SchemaSerializer.js.map +2 -2
  54. package/build/serializer/SchemaSerializer.mjs +1 -1
  55. package/build/serializer/SchemaSerializer.mjs.map +2 -2
  56. package/build/utils/Utils.d.ts +4 -1
  57. package/build/utils/Utils.js +25 -2
  58. package/build/utils/Utils.js.map +2 -2
  59. package/build/utils/Utils.mjs +23 -1
  60. package/build/utils/Utils.mjs.map +2 -2
  61. package/package.json +11 -6
package/build/Room.js CHANGED
@@ -39,12 +39,13 @@ var import_events = require("events");
39
39
  var import_Logger = require("./Logger.js");
40
40
  var import_NoneSerializer = require("./serializer/NoneSerializer.js");
41
41
  var import_SchemaSerializer = require("./serializer/SchemaSerializer.js");
42
- var import_Protocol = require("./Protocol.js");
42
+ var import_Protocol = require("./Protocol");
43
43
  var import_Utils = require("./utils/Utils.js");
44
44
  var import_DevMode = require("./utils/DevMode.js");
45
45
  var import_Debug = require("./Debug.js");
46
46
  var import_ServerError = require("./errors/ServerError.js");
47
- var import_Transport = require("./Transport.js");
47
+ var import_Transport = require("./Transport");
48
+ var import_RoomExceptions = require("./errors/RoomExceptions.js");
48
49
  const DEFAULT_PATCH_RATE = 1e3 / 20;
49
50
  const DEFAULT_SIMULATION_INTERVAL = 1e3 / 60;
50
51
  const noneSerializer = new import_NoneSerializer.NoneSerializer();
@@ -62,12 +63,15 @@ class Room {
62
63
  * Intervals and timeouts are cleared when the room is disposed.
63
64
  */
64
65
  this.clock = new import_timer.default();
66
+ this.#_onLeaveConcurrent = 0;
67
+ // number of onLeave calls in progress
65
68
  /**
66
69
  * Maximum number of clients allowed to connect into the room. When room reaches this limit,
67
70
  * it is locked automatically. Unless the room was explicitly locked by you via `lock()` method,
68
71
  * the room will be unlocked as soon as a client disconnects from it.
69
72
  */
70
73
  this.maxClients = Infinity;
74
+ this.#_maxClientsReached = false;
71
75
  /**
72
76
  * Automatically dispose the room when last client disconnects.
73
77
  *
@@ -110,12 +114,14 @@ class Room {
110
114
  this._serializer = noneSerializer;
111
115
  this._afterNextPatchQueue = [];
112
116
  this._internalState = 0 /* CREATING */;
113
- this._locked = false;
114
117
  this._lockedExplicitly = false;
115
- this._maxClientsReached = false;
118
+ this.#_locked = false;
116
119
  this._events.once("dispose", () => {
117
- this._dispose().catch((e) => (0, import_Debug.debugAndPrintError)(`onDispose error: ${e && e.message || e || "promise rejected"}`)).finally(() => this._events.emit("disconnect"));
120
+ this._dispose().catch((e) => (0, import_Debug.debugAndPrintError)(`onDispose error: ${e && e.stack || e.message || e || "promise rejected"}`)).finally(() => this._events.emit("disconnect"));
118
121
  });
122
+ if (this.onUncaughtException !== void 0) {
123
+ this.#registerUncaughtExceptionHandlers();
124
+ }
119
125
  }
120
126
  /**
121
127
  * This property will change on these situations:
@@ -125,23 +131,68 @@ class Room {
125
131
  * @readonly
126
132
  */
127
133
  get locked() {
128
- return this._locked;
134
+ return this.#_locked;
129
135
  }
130
136
  get metadata() {
131
137
  return this.listing.metadata;
132
138
  }
133
139
  #_roomId;
134
140
  #_roomName;
141
+ #_onLeaveConcurrent;
142
+ #_maxClientsReached;
143
+ #_maxClients;
135
144
  #_autoDispose;
136
145
  #_patchRate;
137
146
  #_patchInterval;
147
+ #_state;
148
+ #_locked;
149
+ /**
150
+ * This method is called by the MatchMaker before onCreate()
151
+ * @internal
152
+ */
138
153
  __init() {
139
- if (this.state) {
140
- this.setState(this.state);
141
- }
154
+ this.#_state = this.state;
142
155
  this.#_autoDispose = this.autoDispose;
143
156
  this.#_patchRate = this.patchRate;
157
+ this.#_maxClients = this.maxClients;
144
158
  Object.defineProperties(this, {
159
+ state: {
160
+ enumerable: true,
161
+ get: () => this.#_state,
162
+ set: (newState) => {
163
+ if (newState[import_schema.$changes] !== void 0) {
164
+ this.setSerializer(new import_SchemaSerializer.SchemaSerializer());
165
+ } else if ("_definition" in newState) {
166
+ throw new Error("@colyseus/schema v2 compatibility currently missing (reach out if you need it)");
167
+ } else if (import_schema.$changes === void 0) {
168
+ throw new Error("Multiple @colyseus/schema versions detected. Please make sure you don't have multiple versions of @colyseus/schema installed.");
169
+ }
170
+ this._serializer.reset(newState);
171
+ this.#_state = newState;
172
+ }
173
+ },
174
+ maxClients: {
175
+ enumerable: true,
176
+ get: () => this.#_maxClients,
177
+ set: (value) => {
178
+ this.#_maxClients = value;
179
+ if (this._internalState === 1 /* CREATED */) {
180
+ const hasReachedMaxClients = this.hasReachedMaxClients();
181
+ if (!this._lockedExplicitly && this.#_maxClientsReached && !hasReachedMaxClients) {
182
+ this.#_maxClientsReached = false;
183
+ this.#_locked = false;
184
+ this.listing.locked = false;
185
+ }
186
+ if (hasReachedMaxClients) {
187
+ this.#_maxClientsReached = true;
188
+ this.#_locked = true;
189
+ this.listing.locked = true;
190
+ }
191
+ this.listing.maxClients = value;
192
+ this.listing.save();
193
+ }
194
+ }
195
+ },
145
196
  autoDispose: {
146
197
  enumerable: true,
147
198
  get: () => this.#_autoDispose,
@@ -168,7 +219,11 @@ class Room {
168
219
  }
169
220
  });
170
221
  this.patchRate = this.#_patchRate;
222
+ if (this.#_state) {
223
+ this.state = this.#_state;
224
+ }
171
225
  this.resetAutoDisposeTimeout(this.seatReservationTime);
226
+ this.clock.start();
172
227
  }
173
228
  /**
174
229
  * The name of the room you provided as first argument for `gameServer.define()`.
@@ -210,18 +265,23 @@ class Room {
210
265
  }
211
266
  this.#_roomId = roomId;
212
267
  }
213
- // TODO: flag as @deprecated on v0.16
214
- // TOOD: remove instance level `onAuth` on 1.0
215
- /**
216
- * onAuth at the instance level will be deprecated in the future.
217
- * Please use "static onAuth(token, req) instead
218
- */
219
- onAuth(client, options, request) {
268
+ onAuth(client, options, context) {
220
269
  return true;
221
270
  }
222
- static async onAuth(token, req) {
271
+ static async onAuth(token, options, context) {
223
272
  return true;
224
273
  }
274
+ /**
275
+ * This method is called during graceful shutdown of the server process
276
+ * You may override this method to dispose the room in your own way.
277
+ *
278
+ * Once process reaches room count of 0, the room process will be terminated.
279
+ */
280
+ onBeforeShutdown() {
281
+ this.disconnect(
282
+ import_DevMode.isDevMode ? import_Protocol.Protocol.WS_CLOSE_DEVMODE_RESTART : import_Protocol.Protocol.WS_CLOSE_CONSENTED
283
+ );
284
+ }
225
285
  /**
226
286
  * Returns whether the sum of connected clients and reserved seats exceeds maximum number of clients.
227
287
  *
@@ -281,6 +341,9 @@ class Room {
281
341
  clearInterval(this._simulationInterval);
282
342
  }
283
343
  if (onTickCallback) {
344
+ if (this.onUncaughtException !== void 0) {
345
+ onTickCallback = (0, import_Utils.wrapTryCatch)(onTickCallback, this.onUncaughtException.bind(this), import_RoomExceptions.SimulationIntervalException, "setSimulationInterval");
346
+ }
284
347
  this._simulationInterval = setInterval(() => {
285
348
  this.clock.tick();
286
349
  onTickCallback(this.clock.deltaTime);
@@ -293,14 +356,10 @@ class Room {
293
356
  setPatchRate(milliseconds) {
294
357
  this.patchRate = milliseconds;
295
358
  }
359
+ /**
360
+ * @deprecated Use `.state =` instead.
361
+ */
296
362
  setState(newState) {
297
- this.clock.start();
298
- if (newState[import_schema.$changes] !== void 0) {
299
- this.setSerializer(new import_SchemaSerializer.SchemaSerializer());
300
- } else if (import_schema.$changes === void 0) {
301
- throw new Error("@colyseus/schema v2 compatibility currently missing (reach out if you need it)");
302
- }
303
- this._serializer.reset(newState);
304
363
  this.state = newState;
305
364
  }
306
365
  setSerializer(serializer) {
@@ -337,12 +396,12 @@ class Room {
337
396
  */
338
397
  async lock() {
339
398
  this._lockedExplicitly = arguments[0] === void 0;
340
- if (this._locked) {
399
+ if (this.#_locked) {
341
400
  return;
342
401
  }
343
- this._locked = true;
402
+ this.#_locked = true;
344
403
  await this.listing.updateOne({
345
- $set: { locked: this._locked }
404
+ $set: { locked: this.#_locked }
346
405
  });
347
406
  this._events.emit("lock");
348
407
  }
@@ -353,12 +412,12 @@ class Room {
353
412
  if (arguments[0] === void 0) {
354
413
  this._lockedExplicitly = false;
355
414
  }
356
- if (!this._locked) {
415
+ if (!this.#_locked) {
357
416
  return;
358
417
  }
359
- this._locked = false;
418
+ this.#_locked = false;
360
419
  await this.listing.updateOne({
361
- $set: { locked: this._locked }
420
+ $set: { locked: this.#_locked }
362
421
  });
363
422
  this._events.emit("unlock");
364
423
  }
@@ -403,7 +462,7 @@ class Room {
403
462
  return hasChanges;
404
463
  }
405
464
  onMessage(messageType, callback, validate) {
406
- this.onMessageHandlers[messageType] = { callback, validate };
465
+ this.onMessageHandlers[messageType] = this.onUncaughtException !== void 0 ? { validate, callback: (0, import_Utils.wrapTryCatch)(callback, this.onUncaughtException.bind(this), import_RoomExceptions.OnMessageException, "onMessage", false, messageType) } : { validate, callback };
407
466
  return () => delete this.onMessageHandlers[messageType];
408
467
  }
409
468
  /**
@@ -423,7 +482,7 @@ class Room {
423
482
  this.#_autoDispose = true;
424
483
  const delayedDisconnection = new Promise((resolve) => this._events.once("disconnect", () => resolve()));
425
484
  for (const [_, reconnection] of Object.values(this._reconnections)) {
426
- reconnection.reject();
485
+ reconnection.reject(new Error("disconnecting"));
427
486
  }
428
487
  let numClients = this.clients.length;
429
488
  if (numClients > 0) {
@@ -435,7 +494,7 @@ class Room {
435
494
  }
436
495
  return delayedDisconnection;
437
496
  }
438
- async ["_onJoin"](client, req) {
497
+ async ["_onJoin"](client, authContext) {
439
498
  const sessionId = client.sessionId;
440
499
  client.reconnectionToken = (0, import_Utils.generateId)();
441
500
  if (this.reservedSeatTimeouts[sessionId]) {
@@ -451,6 +510,7 @@ class Room {
451
510
  throw new import_ServerError.ServerError(import_Protocol.ErrorCode.MATCHMAKE_EXPIRED, "already consumed");
452
511
  }
453
512
  this.reservedSeats[sessionId][2] = true;
513
+ (0, import_Debug.debugMatchMaking)("consuming seat reservation, sessionId: '%s'", client.sessionId);
454
514
  client._afterNextPatchQueue = this._afterNextPatchQueue;
455
515
  client.ref["onleave"] = (_) => client.state = import_Transport.ClientState.LEAVING;
456
516
  client.ref.once("close", client.ref["onleave"]);
@@ -468,9 +528,15 @@ class Room {
468
528
  if (authData) {
469
529
  client.auth = authData;
470
530
  } else if (this.onAuth !== Room.prototype.onAuth) {
471
- client.auth = await this.onAuth(client, joinOptions, req);
472
- if (!client.auth) {
473
- throw new import_ServerError.ServerError(import_Protocol.ErrorCode.AUTH_FAILED, "onAuth failed");
531
+ try {
532
+ client.auth = await this.onAuth(client, joinOptions, authContext);
533
+ if (!client.auth) {
534
+ throw new import_ServerError.ServerError(import_Protocol.ErrorCode.AUTH_FAILED, "onAuth failed");
535
+ }
536
+ } catch (e) {
537
+ delete this.reservedSeats[sessionId];
538
+ await this._decrementClientCount();
539
+ throw e;
474
540
  }
475
541
  }
476
542
  if (client.state === import_Transport.ClientState.LEAVING) {
@@ -484,15 +550,15 @@ class Room {
484
550
  if (this.onJoin) {
485
551
  await this.onJoin(client, joinOptions, client.auth);
486
552
  }
487
- this._events.emit("join", client);
488
- delete this.reservedSeats[sessionId];
489
553
  if (client.state === import_Transport.ClientState.LEAVING) {
490
- await this._onLeave(client, import_Protocol.Protocol.WS_CLOSE_GOING_AWAY);
554
+ throw new Error("early_leave");
555
+ } else {
556
+ delete this.reservedSeats[sessionId];
557
+ this._events.emit("join", client);
491
558
  }
492
559
  } catch (e) {
493
- this.clients.delete(client);
560
+ await this._onLeave(client, import_Protocol.Protocol.WS_CLOSE_GOING_AWAY);
494
561
  delete this.reservedSeats[sessionId];
495
- this._decrementClientCount();
496
562
  if (!e.code) {
497
563
  e.code = import_Protocol.ErrorCode.APPLICATION_ERROR;
498
564
  }
@@ -523,7 +589,7 @@ class Room {
523
589
  */
524
590
  allowReconnection(previousClient, seconds) {
525
591
  if (previousClient._enqueuedMessages !== void 0) {
526
- return import_Utils.Deferred.reject("client not joined");
592
+ return Promise.reject(new Error("not joined"));
527
593
  }
528
594
  if (seconds === void 0) {
529
595
  console.warn('DEPRECATED: allowReconnection() requires a second argument. Using "manual" mode.');
@@ -533,8 +599,7 @@ class Room {
533
599
  seconds = Infinity;
534
600
  }
535
601
  if (this._internalState === 2 /* DISPOSING */) {
536
- this._disposeIfEmpty();
537
- return import_Utils.Deferred.reject("disconnecting");
602
+ return Promise.reject(new Error("disposing"));
538
603
  }
539
604
  const sessionId = previousClient.sessionId;
540
605
  const reconnectionToken = previousClient.reconnectionToken;
@@ -622,7 +687,8 @@ class Room {
622
687
  return true;
623
688
  }
624
689
  _disposeIfEmpty() {
625
- const willDispose = this.#_autoDispose && this._autoDisposeTimeout === void 0 && this.clients.length === 0 && Object.keys(this.reservedSeats).length === 0;
690
+ const willDispose = this.#_onLeaveConcurrent === 0 && // no "onLeave" calls in progress
691
+ this.#_autoDispose && this._autoDisposeTimeout === void 0 && this.clients.length === 0 && Object.keys(this.reservedSeats).length === 0;
626
692
  if (willDispose) {
627
693
  this._events.emit("dispose");
628
694
  }
@@ -714,15 +780,19 @@ class Room {
714
780
  this._onLeave(client, closeCode).then(() => client.leave(closeCode));
715
781
  }
716
782
  async _onLeave(client, code) {
717
- const success = this.clients.delete(client);
718
- if (success) {
719
- client.state = import_Transport.ClientState.LEAVING;
720
- if (this.onLeave) {
721
- try {
722
- await this.onLeave(client, code === import_Protocol.Protocol.WS_CLOSE_CONSENTED);
723
- } catch (e) {
724
- (0, import_Debug.debugAndPrintError)(`onLeave error: ${e && e.message || e || "promise rejected"}`);
725
- }
783
+ (0, import_Debug.debugMatchMaking)("onLeave, sessionId: '%s'", client.sessionId);
784
+ client.state = import_Transport.ClientState.LEAVING;
785
+ if (!this.clients.delete(client)) {
786
+ return;
787
+ }
788
+ if (this.onLeave) {
789
+ try {
790
+ this.#_onLeaveConcurrent++;
791
+ await this.onLeave(client, code === import_Protocol.Protocol.WS_CLOSE_CONSENTED);
792
+ } catch (e) {
793
+ (0, import_Debug.debugAndPrintError)(`onLeave error: ${e && e.message || e || "promise rejected"}`);
794
+ } finally {
795
+ this.#_onLeaveConcurrent--;
726
796
  }
727
797
  }
728
798
  if (this._reconnections[client.reconnectionToken]) {
@@ -740,13 +810,13 @@ class Room {
740
810
  }
741
811
  }
742
812
  async _incrementClientCount() {
743
- if (!this._locked && this.hasReachedMaxClients()) {
744
- this._maxClientsReached = true;
813
+ if (!this.#_locked && this.hasReachedMaxClients()) {
814
+ this.#_maxClientsReached = true;
745
815
  this.lock.call(this, true);
746
816
  }
747
817
  await this.listing.updateOne({
748
818
  $inc: { clients: 1 },
749
- $set: { locked: this._locked }
819
+ $set: { locked: this.#_locked }
750
820
  });
751
821
  }
752
822
  async _decrementClientCount() {
@@ -755,17 +825,43 @@ class Room {
755
825
  return true;
756
826
  }
757
827
  if (!willDispose) {
758
- if (this._maxClientsReached && !this._lockedExplicitly) {
759
- this._maxClientsReached = false;
828
+ if (this.#_maxClientsReached && !this._lockedExplicitly) {
829
+ this.#_maxClientsReached = false;
760
830
  this.unlock.call(this, true);
761
831
  }
762
832
  await this.listing.updateOne({
763
833
  $inc: { clients: -1 },
764
- $set: { locked: this._locked }
834
+ $set: { locked: this.#_locked }
765
835
  });
766
836
  }
767
837
  return willDispose;
768
838
  }
839
+ #registerUncaughtExceptionHandlers() {
840
+ const onUncaughtException = this.onUncaughtException.bind(this);
841
+ const originalSetTimeout = this.clock.setTimeout;
842
+ this.clock.setTimeout = (cb, timeout, ...args) => {
843
+ return originalSetTimeout.call(this.clock, (0, import_Utils.wrapTryCatch)(cb, onUncaughtException, import_RoomExceptions.TimedEventException, "setTimeout"), timeout, ...args);
844
+ };
845
+ const originalSetInterval = this.clock.setInterval;
846
+ this.clock.setInterval = (cb, timeout, ...args) => {
847
+ return originalSetInterval.call(this.clock, (0, import_Utils.wrapTryCatch)(cb, onUncaughtException, import_RoomExceptions.TimedEventException, "setInterval"), timeout, ...args);
848
+ };
849
+ if (this.onCreate !== void 0) {
850
+ this.onCreate = (0, import_Utils.wrapTryCatch)(this.onCreate.bind(this), onUncaughtException, import_RoomExceptions.OnCreateException, "onCreate", true);
851
+ }
852
+ if (this.onAuth !== void 0) {
853
+ this.onAuth = (0, import_Utils.wrapTryCatch)(this.onAuth.bind(this), onUncaughtException, import_RoomExceptions.OnAuthException, "onAuth", true);
854
+ }
855
+ if (this.onJoin !== void 0) {
856
+ this.onJoin = (0, import_Utils.wrapTryCatch)(this.onJoin.bind(this), onUncaughtException, import_RoomExceptions.OnJoinException, "onJoin", true);
857
+ }
858
+ if (this.onLeave !== void 0) {
859
+ this.onLeave = (0, import_Utils.wrapTryCatch)(this.onLeave.bind(this), onUncaughtException, import_RoomExceptions.OnLeaveException, "onLeave", true);
860
+ }
861
+ if (this.onDispose !== void 0) {
862
+ this.onDispose = (0, import_Utils.wrapTryCatch)(this.onDispose.bind(this), onUncaughtException, import_RoomExceptions.OnDisposeException, "onDispose");
863
+ }
864
+ }
769
865
  }
770
866
  // Annotate the CommonJS export names for ESM import in node:
771
867
  0 && (module.exports = {