@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.mjs CHANGED
@@ -6,12 +6,13 @@ import { EventEmitter } from "events";
6
6
  import { logger } from "./Logger.mjs";
7
7
  import { NoneSerializer } from "./serializer/NoneSerializer.mjs";
8
8
  import { SchemaSerializer } from "./serializer/SchemaSerializer.mjs";
9
- import { ErrorCode, getMessageBytes, Protocol } from "./Protocol.mjs";
10
- import { Deferred, generateId } from "./utils/Utils.mjs";
9
+ import { ErrorCode, getMessageBytes, Protocol } from "./Protocol";
10
+ import { Deferred, generateId, wrapTryCatch } from "./utils/Utils.mjs";
11
11
  import { isDevMode } from "./utils/DevMode.mjs";
12
- import { debugAndPrintError, debugMessage } from "./Debug.mjs";
12
+ import { debugAndPrintError, debugMatchMaking, debugMessage } from "./Debug.mjs";
13
13
  import { ServerError } from "./errors/ServerError.mjs";
14
- import { ClientArray, ClientState } from "./Transport.mjs";
14
+ import { ClientArray, ClientState } from "./Transport";
15
+ import { OnAuthException, OnCreateException, OnDisposeException, OnJoinException, OnLeaveException, OnMessageException, SimulationIntervalException, TimedEventException } from "./errors/RoomExceptions.mjs";
15
16
  var DEFAULT_PATCH_RATE = 1e3 / 20;
16
17
  var DEFAULT_SIMULATION_INTERVAL = 1e3 / 60;
17
18
  var noneSerializer = new NoneSerializer();
@@ -29,12 +30,15 @@ var Room = class _Room {
29
30
  * Intervals and timeouts are cleared when the room is disposed.
30
31
  */
31
32
  this.clock = new Clock();
33
+ this.#_onLeaveConcurrent = 0;
34
+ // number of onLeave calls in progress
32
35
  /**
33
36
  * Maximum number of clients allowed to connect into the room. When room reaches this limit,
34
37
  * it is locked automatically. Unless the room was explicitly locked by you via `lock()` method,
35
38
  * the room will be unlocked as soon as a client disconnects from it.
36
39
  */
37
40
  this.maxClients = Infinity;
41
+ this.#_maxClientsReached = false;
38
42
  /**
39
43
  * Automatically dispose the room when last client disconnects.
40
44
  *
@@ -77,12 +81,14 @@ var Room = class _Room {
77
81
  this._serializer = noneSerializer;
78
82
  this._afterNextPatchQueue = [];
79
83
  this._internalState = 0 /* CREATING */;
80
- this._locked = false;
81
84
  this._lockedExplicitly = false;
82
- this._maxClientsReached = false;
85
+ this.#_locked = false;
83
86
  this._events.once("dispose", () => {
84
- this._dispose().catch((e) => debugAndPrintError(`onDispose error: ${e && e.message || e || "promise rejected"}`)).finally(() => this._events.emit("disconnect"));
87
+ this._dispose().catch((e) => debugAndPrintError(`onDispose error: ${e && e.stack || e.message || e || "promise rejected"}`)).finally(() => this._events.emit("disconnect"));
85
88
  });
89
+ if (this.onUncaughtException !== void 0) {
90
+ this.#registerUncaughtExceptionHandlers();
91
+ }
86
92
  }
87
93
  /**
88
94
  * This property will change on these situations:
@@ -92,23 +98,68 @@ var Room = class _Room {
92
98
  * @readonly
93
99
  */
94
100
  get locked() {
95
- return this._locked;
101
+ return this.#_locked;
96
102
  }
97
103
  get metadata() {
98
104
  return this.listing.metadata;
99
105
  }
100
106
  #_roomId;
101
107
  #_roomName;
108
+ #_onLeaveConcurrent;
109
+ #_maxClientsReached;
110
+ #_maxClients;
102
111
  #_autoDispose;
103
112
  #_patchRate;
104
113
  #_patchInterval;
114
+ #_state;
115
+ #_locked;
116
+ /**
117
+ * This method is called by the MatchMaker before onCreate()
118
+ * @internal
119
+ */
105
120
  __init() {
106
- if (this.state) {
107
- this.setState(this.state);
108
- }
121
+ this.#_state = this.state;
109
122
  this.#_autoDispose = this.autoDispose;
110
123
  this.#_patchRate = this.patchRate;
124
+ this.#_maxClients = this.maxClients;
111
125
  Object.defineProperties(this, {
126
+ state: {
127
+ enumerable: true,
128
+ get: () => this.#_state,
129
+ set: (newState) => {
130
+ if (newState[$changes] !== void 0) {
131
+ this.setSerializer(new SchemaSerializer());
132
+ } else if ("_definition" in newState) {
133
+ throw new Error("@colyseus/schema v2 compatibility currently missing (reach out if you need it)");
134
+ } else if ($changes === void 0) {
135
+ throw new Error("Multiple @colyseus/schema versions detected. Please make sure you don't have multiple versions of @colyseus/schema installed.");
136
+ }
137
+ this._serializer.reset(newState);
138
+ this.#_state = newState;
139
+ }
140
+ },
141
+ maxClients: {
142
+ enumerable: true,
143
+ get: () => this.#_maxClients,
144
+ set: (value) => {
145
+ this.#_maxClients = value;
146
+ if (this._internalState === 1 /* CREATED */) {
147
+ const hasReachedMaxClients = this.hasReachedMaxClients();
148
+ if (!this._lockedExplicitly && this.#_maxClientsReached && !hasReachedMaxClients) {
149
+ this.#_maxClientsReached = false;
150
+ this.#_locked = false;
151
+ this.listing.locked = false;
152
+ }
153
+ if (hasReachedMaxClients) {
154
+ this.#_maxClientsReached = true;
155
+ this.#_locked = true;
156
+ this.listing.locked = true;
157
+ }
158
+ this.listing.maxClients = value;
159
+ this.listing.save();
160
+ }
161
+ }
162
+ },
112
163
  autoDispose: {
113
164
  enumerable: true,
114
165
  get: () => this.#_autoDispose,
@@ -135,7 +186,11 @@ var Room = class _Room {
135
186
  }
136
187
  });
137
188
  this.patchRate = this.#_patchRate;
189
+ if (this.#_state) {
190
+ this.state = this.#_state;
191
+ }
138
192
  this.resetAutoDisposeTimeout(this.seatReservationTime);
193
+ this.clock.start();
139
194
  }
140
195
  /**
141
196
  * The name of the room you provided as first argument for `gameServer.define()`.
@@ -177,18 +232,23 @@ var Room = class _Room {
177
232
  }
178
233
  this.#_roomId = roomId;
179
234
  }
180
- // TODO: flag as @deprecated on v0.16
181
- // TOOD: remove instance level `onAuth` on 1.0
182
- /**
183
- * onAuth at the instance level will be deprecated in the future.
184
- * Please use "static onAuth(token, req) instead
185
- */
186
- onAuth(client, options, request) {
235
+ onAuth(client, options, context) {
187
236
  return true;
188
237
  }
189
- static async onAuth(token, req) {
238
+ static async onAuth(token, options, context) {
190
239
  return true;
191
240
  }
241
+ /**
242
+ * This method is called during graceful shutdown of the server process
243
+ * You may override this method to dispose the room in your own way.
244
+ *
245
+ * Once process reaches room count of 0, the room process will be terminated.
246
+ */
247
+ onBeforeShutdown() {
248
+ this.disconnect(
249
+ isDevMode ? Protocol.WS_CLOSE_DEVMODE_RESTART : Protocol.WS_CLOSE_CONSENTED
250
+ );
251
+ }
192
252
  /**
193
253
  * Returns whether the sum of connected clients and reserved seats exceeds maximum number of clients.
194
254
  *
@@ -248,6 +308,9 @@ var Room = class _Room {
248
308
  clearInterval(this._simulationInterval);
249
309
  }
250
310
  if (onTickCallback) {
311
+ if (this.onUncaughtException !== void 0) {
312
+ onTickCallback = wrapTryCatch(onTickCallback, this.onUncaughtException.bind(this), SimulationIntervalException, "setSimulationInterval");
313
+ }
251
314
  this._simulationInterval = setInterval(() => {
252
315
  this.clock.tick();
253
316
  onTickCallback(this.clock.deltaTime);
@@ -260,14 +323,10 @@ var Room = class _Room {
260
323
  setPatchRate(milliseconds) {
261
324
  this.patchRate = milliseconds;
262
325
  }
326
+ /**
327
+ * @deprecated Use `.state =` instead.
328
+ */
263
329
  setState(newState) {
264
- this.clock.start();
265
- if (newState[$changes] !== void 0) {
266
- this.setSerializer(new SchemaSerializer());
267
- } else if ($changes === void 0) {
268
- throw new Error("@colyseus/schema v2 compatibility currently missing (reach out if you need it)");
269
- }
270
- this._serializer.reset(newState);
271
330
  this.state = newState;
272
331
  }
273
332
  setSerializer(serializer) {
@@ -304,12 +363,12 @@ var Room = class _Room {
304
363
  */
305
364
  async lock() {
306
365
  this._lockedExplicitly = arguments[0] === void 0;
307
- if (this._locked) {
366
+ if (this.#_locked) {
308
367
  return;
309
368
  }
310
- this._locked = true;
369
+ this.#_locked = true;
311
370
  await this.listing.updateOne({
312
- $set: { locked: this._locked }
371
+ $set: { locked: this.#_locked }
313
372
  });
314
373
  this._events.emit("lock");
315
374
  }
@@ -320,12 +379,12 @@ var Room = class _Room {
320
379
  if (arguments[0] === void 0) {
321
380
  this._lockedExplicitly = false;
322
381
  }
323
- if (!this._locked) {
382
+ if (!this.#_locked) {
324
383
  return;
325
384
  }
326
- this._locked = false;
385
+ this.#_locked = false;
327
386
  await this.listing.updateOne({
328
- $set: { locked: this._locked }
387
+ $set: { locked: this.#_locked }
329
388
  });
330
389
  this._events.emit("unlock");
331
390
  }
@@ -370,7 +429,7 @@ var Room = class _Room {
370
429
  return hasChanges;
371
430
  }
372
431
  onMessage(messageType, callback, validate) {
373
- this.onMessageHandlers[messageType] = { callback, validate };
432
+ this.onMessageHandlers[messageType] = this.onUncaughtException !== void 0 ? { validate, callback: wrapTryCatch(callback, this.onUncaughtException.bind(this), OnMessageException, "onMessage", false, messageType) } : { validate, callback };
374
433
  return () => delete this.onMessageHandlers[messageType];
375
434
  }
376
435
  /**
@@ -390,7 +449,7 @@ var Room = class _Room {
390
449
  this.#_autoDispose = true;
391
450
  const delayedDisconnection = new Promise((resolve) => this._events.once("disconnect", () => resolve()));
392
451
  for (const [_, reconnection] of Object.values(this._reconnections)) {
393
- reconnection.reject();
452
+ reconnection.reject(new Error("disconnecting"));
394
453
  }
395
454
  let numClients = this.clients.length;
396
455
  if (numClients > 0) {
@@ -402,7 +461,7 @@ var Room = class _Room {
402
461
  }
403
462
  return delayedDisconnection;
404
463
  }
405
- async ["_onJoin"](client, req) {
464
+ async ["_onJoin"](client, authContext) {
406
465
  const sessionId = client.sessionId;
407
466
  client.reconnectionToken = generateId();
408
467
  if (this.reservedSeatTimeouts[sessionId]) {
@@ -418,6 +477,7 @@ var Room = class _Room {
418
477
  throw new ServerError(ErrorCode.MATCHMAKE_EXPIRED, "already consumed");
419
478
  }
420
479
  this.reservedSeats[sessionId][2] = true;
480
+ debugMatchMaking("consuming seat reservation, sessionId: '%s'", client.sessionId);
421
481
  client._afterNextPatchQueue = this._afterNextPatchQueue;
422
482
  client.ref["onleave"] = (_) => client.state = ClientState.LEAVING;
423
483
  client.ref.once("close", client.ref["onleave"]);
@@ -435,9 +495,15 @@ var Room = class _Room {
435
495
  if (authData) {
436
496
  client.auth = authData;
437
497
  } else if (this.onAuth !== _Room.prototype.onAuth) {
438
- client.auth = await this.onAuth(client, joinOptions, req);
439
- if (!client.auth) {
440
- throw new ServerError(ErrorCode.AUTH_FAILED, "onAuth failed");
498
+ try {
499
+ client.auth = await this.onAuth(client, joinOptions, authContext);
500
+ if (!client.auth) {
501
+ throw new ServerError(ErrorCode.AUTH_FAILED, "onAuth failed");
502
+ }
503
+ } catch (e) {
504
+ delete this.reservedSeats[sessionId];
505
+ await this._decrementClientCount();
506
+ throw e;
441
507
  }
442
508
  }
443
509
  if (client.state === ClientState.LEAVING) {
@@ -451,15 +517,15 @@ var Room = class _Room {
451
517
  if (this.onJoin) {
452
518
  await this.onJoin(client, joinOptions, client.auth);
453
519
  }
454
- this._events.emit("join", client);
455
- delete this.reservedSeats[sessionId];
456
520
  if (client.state === ClientState.LEAVING) {
457
- await this._onLeave(client, Protocol.WS_CLOSE_GOING_AWAY);
521
+ throw new Error("early_leave");
522
+ } else {
523
+ delete this.reservedSeats[sessionId];
524
+ this._events.emit("join", client);
458
525
  }
459
526
  } catch (e) {
460
- this.clients.delete(client);
527
+ await this._onLeave(client, Protocol.WS_CLOSE_GOING_AWAY);
461
528
  delete this.reservedSeats[sessionId];
462
- this._decrementClientCount();
463
529
  if (!e.code) {
464
530
  e.code = ErrorCode.APPLICATION_ERROR;
465
531
  }
@@ -490,7 +556,7 @@ var Room = class _Room {
490
556
  */
491
557
  allowReconnection(previousClient, seconds) {
492
558
  if (previousClient._enqueuedMessages !== void 0) {
493
- return Deferred.reject("client not joined");
559
+ return Promise.reject(new Error("not joined"));
494
560
  }
495
561
  if (seconds === void 0) {
496
562
  console.warn('DEPRECATED: allowReconnection() requires a second argument. Using "manual" mode.');
@@ -500,8 +566,7 @@ var Room = class _Room {
500
566
  seconds = Infinity;
501
567
  }
502
568
  if (this._internalState === 2 /* DISPOSING */) {
503
- this._disposeIfEmpty();
504
- return Deferred.reject("disconnecting");
569
+ return Promise.reject(new Error("disposing"));
505
570
  }
506
571
  const sessionId = previousClient.sessionId;
507
572
  const reconnectionToken = previousClient.reconnectionToken;
@@ -589,7 +654,8 @@ var Room = class _Room {
589
654
  return true;
590
655
  }
591
656
  _disposeIfEmpty() {
592
- const willDispose = this.#_autoDispose && this._autoDisposeTimeout === void 0 && this.clients.length === 0 && Object.keys(this.reservedSeats).length === 0;
657
+ const willDispose = this.#_onLeaveConcurrent === 0 && // no "onLeave" calls in progress
658
+ this.#_autoDispose && this._autoDisposeTimeout === void 0 && this.clients.length === 0 && Object.keys(this.reservedSeats).length === 0;
593
659
  if (willDispose) {
594
660
  this._events.emit("dispose");
595
661
  }
@@ -681,15 +747,19 @@ var Room = class _Room {
681
747
  this._onLeave(client, closeCode).then(() => client.leave(closeCode));
682
748
  }
683
749
  async _onLeave(client, code) {
684
- const success = this.clients.delete(client);
685
- if (success) {
686
- client.state = ClientState.LEAVING;
687
- if (this.onLeave) {
688
- try {
689
- await this.onLeave(client, code === Protocol.WS_CLOSE_CONSENTED);
690
- } catch (e) {
691
- debugAndPrintError(`onLeave error: ${e && e.message || e || "promise rejected"}`);
692
- }
750
+ debugMatchMaking("onLeave, sessionId: '%s'", client.sessionId);
751
+ client.state = ClientState.LEAVING;
752
+ if (!this.clients.delete(client)) {
753
+ return;
754
+ }
755
+ if (this.onLeave) {
756
+ try {
757
+ this.#_onLeaveConcurrent++;
758
+ await this.onLeave(client, code === Protocol.WS_CLOSE_CONSENTED);
759
+ } catch (e) {
760
+ debugAndPrintError(`onLeave error: ${e && e.message || e || "promise rejected"}`);
761
+ } finally {
762
+ this.#_onLeaveConcurrent--;
693
763
  }
694
764
  }
695
765
  if (this._reconnections[client.reconnectionToken]) {
@@ -707,13 +777,13 @@ var Room = class _Room {
707
777
  }
708
778
  }
709
779
  async _incrementClientCount() {
710
- if (!this._locked && this.hasReachedMaxClients()) {
711
- this._maxClientsReached = true;
780
+ if (!this.#_locked && this.hasReachedMaxClients()) {
781
+ this.#_maxClientsReached = true;
712
782
  this.lock.call(this, true);
713
783
  }
714
784
  await this.listing.updateOne({
715
785
  $inc: { clients: 1 },
716
- $set: { locked: this._locked }
786
+ $set: { locked: this.#_locked }
717
787
  });
718
788
  }
719
789
  async _decrementClientCount() {
@@ -722,17 +792,43 @@ var Room = class _Room {
722
792
  return true;
723
793
  }
724
794
  if (!willDispose) {
725
- if (this._maxClientsReached && !this._lockedExplicitly) {
726
- this._maxClientsReached = false;
795
+ if (this.#_maxClientsReached && !this._lockedExplicitly) {
796
+ this.#_maxClientsReached = false;
727
797
  this.unlock.call(this, true);
728
798
  }
729
799
  await this.listing.updateOne({
730
800
  $inc: { clients: -1 },
731
- $set: { locked: this._locked }
801
+ $set: { locked: this.#_locked }
732
802
  });
733
803
  }
734
804
  return willDispose;
735
805
  }
806
+ #registerUncaughtExceptionHandlers() {
807
+ const onUncaughtException = this.onUncaughtException.bind(this);
808
+ const originalSetTimeout = this.clock.setTimeout;
809
+ this.clock.setTimeout = (cb, timeout, ...args) => {
810
+ return originalSetTimeout.call(this.clock, wrapTryCatch(cb, onUncaughtException, TimedEventException, "setTimeout"), timeout, ...args);
811
+ };
812
+ const originalSetInterval = this.clock.setInterval;
813
+ this.clock.setInterval = (cb, timeout, ...args) => {
814
+ return originalSetInterval.call(this.clock, wrapTryCatch(cb, onUncaughtException, TimedEventException, "setInterval"), timeout, ...args);
815
+ };
816
+ if (this.onCreate !== void 0) {
817
+ this.onCreate = wrapTryCatch(this.onCreate.bind(this), onUncaughtException, OnCreateException, "onCreate", true);
818
+ }
819
+ if (this.onAuth !== void 0) {
820
+ this.onAuth = wrapTryCatch(this.onAuth.bind(this), onUncaughtException, OnAuthException, "onAuth", true);
821
+ }
822
+ if (this.onJoin !== void 0) {
823
+ this.onJoin = wrapTryCatch(this.onJoin.bind(this), onUncaughtException, OnJoinException, "onJoin", true);
824
+ }
825
+ if (this.onLeave !== void 0) {
826
+ this.onLeave = wrapTryCatch(this.onLeave.bind(this), onUncaughtException, OnLeaveException, "onLeave", true);
827
+ }
828
+ if (this.onDispose !== void 0) {
829
+ this.onDispose = wrapTryCatch(this.onDispose.bind(this), onUncaughtException, OnDisposeException, "onDispose");
830
+ }
831
+ }
736
832
  };
737
833
  export {
738
834
  DEFAULT_SEAT_RESERVATION_TIME,