@fluidframework/container-loader 2.0.0-dev.6.4.0.192049 → 2.0.0-dev.7.2.0.204906

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 (146) hide show
  1. package/CHANGELOG.md +122 -0
  2. package/api-extractor.json +9 -1
  3. package/api-report/container-loader.api.md +153 -0
  4. package/dist/catchUpMonitor.d.ts +2 -2
  5. package/dist/catchUpMonitor.d.ts.map +1 -1
  6. package/dist/connectionManager.d.ts +2 -15
  7. package/dist/connectionManager.d.ts.map +1 -1
  8. package/dist/connectionManager.js +117 -100
  9. package/dist/connectionManager.js.map +1 -1
  10. package/dist/connectionState.js +1 -1
  11. package/dist/connectionState.js.map +1 -1
  12. package/dist/connectionStateHandler.js +9 -9
  13. package/dist/connectionStateHandler.js.map +1 -1
  14. package/dist/container-loader-alpha.d.ts +333 -0
  15. package/dist/container-loader-beta.d.ts +333 -0
  16. package/dist/container-loader-public.d.ts +333 -0
  17. package/dist/container-loader-untrimmed.d.ts +333 -0
  18. package/dist/container.d.ts +22 -2
  19. package/dist/container.d.ts.map +1 -1
  20. package/dist/container.js +235 -222
  21. package/dist/container.js.map +1 -1
  22. package/dist/containerContext.js +16 -16
  23. package/dist/containerContext.js.map +1 -1
  24. package/dist/containerStorageAdapter.d.ts +1 -1
  25. package/dist/containerStorageAdapter.d.ts.map +1 -1
  26. package/dist/containerStorageAdapter.js +9 -11
  27. package/dist/containerStorageAdapter.js.map +1 -1
  28. package/dist/contracts.d.ts +5 -4
  29. package/dist/contracts.d.ts.map +1 -1
  30. package/dist/contracts.js +1 -1
  31. package/dist/contracts.js.map +1 -1
  32. package/dist/debugLogger.d.ts.map +1 -1
  33. package/dist/debugLogger.js +5 -4
  34. package/dist/debugLogger.js.map +1 -1
  35. package/dist/deltaManager.d.ts.map +1 -1
  36. package/dist/deltaManager.js +92 -96
  37. package/dist/deltaManager.js.map +1 -1
  38. package/dist/deltaQueue.js +14 -14
  39. package/dist/deltaQueue.js.map +1 -1
  40. package/dist/index.d.ts +2 -0
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +6 -1
  43. package/dist/index.js.map +1 -1
  44. package/dist/loader.d.ts +6 -9
  45. package/dist/loader.d.ts.map +1 -1
  46. package/dist/loader.js +26 -91
  47. package/dist/loader.js.map +1 -1
  48. package/dist/location-redirection-utilities/index.d.ts +6 -0
  49. package/dist/location-redirection-utilities/index.d.ts.map +1 -0
  50. package/dist/location-redirection-utilities/index.js +11 -0
  51. package/dist/location-redirection-utilities/index.js.map +1 -0
  52. package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts +22 -0
  53. package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -0
  54. package/dist/location-redirection-utilities/resolveWithLocationRedirection.js +51 -0
  55. package/dist/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -0
  56. package/dist/packageVersion.d.ts +1 -1
  57. package/dist/packageVersion.js +1 -1
  58. package/dist/packageVersion.js.map +1 -1
  59. package/dist/protocol.d.ts +1 -2
  60. package/dist/protocol.d.ts.map +1 -1
  61. package/dist/protocol.js +3 -5
  62. package/dist/protocol.js.map +1 -1
  63. package/dist/retriableDocumentStorageService.d.ts +3 -2
  64. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  65. package/dist/retriableDocumentStorageService.js +18 -11
  66. package/dist/retriableDocumentStorageService.js.map +1 -1
  67. package/dist/tsdoc-metadata.json +1 -1
  68. package/dist/utils.d.ts +23 -1
  69. package/dist/utils.d.ts.map +1 -1
  70. package/dist/utils.js +11 -3
  71. package/dist/utils.js.map +1 -1
  72. package/lib/catchUpMonitor.d.ts +2 -2
  73. package/lib/catchUpMonitor.d.ts.map +1 -1
  74. package/lib/connectionManager.d.ts +2 -15
  75. package/lib/connectionManager.d.ts.map +1 -1
  76. package/lib/connectionManager.js +120 -101
  77. package/lib/connectionManager.js.map +1 -1
  78. package/lib/connectionStateHandler.js +9 -9
  79. package/lib/connectionStateHandler.js.map +1 -1
  80. package/lib/container.d.ts +22 -2
  81. package/lib/container.d.ts.map +1 -1
  82. package/lib/container.js +236 -223
  83. package/lib/container.js.map +1 -1
  84. package/lib/containerContext.js +16 -16
  85. package/lib/containerContext.js.map +1 -1
  86. package/lib/containerStorageAdapter.d.ts +1 -1
  87. package/lib/containerStorageAdapter.d.ts.map +1 -1
  88. package/lib/containerStorageAdapter.js +9 -11
  89. package/lib/containerStorageAdapter.js.map +1 -1
  90. package/lib/contracts.d.ts +5 -4
  91. package/lib/contracts.d.ts.map +1 -1
  92. package/lib/contracts.js.map +1 -1
  93. package/lib/debugLogger.d.ts.map +1 -1
  94. package/lib/debugLogger.js +5 -4
  95. package/lib/debugLogger.js.map +1 -1
  96. package/lib/deltaManager.d.ts.map +1 -1
  97. package/lib/deltaManager.js +92 -96
  98. package/lib/deltaManager.js.map +1 -1
  99. package/lib/deltaQueue.js +14 -14
  100. package/lib/deltaQueue.js.map +1 -1
  101. package/lib/index.d.ts +2 -0
  102. package/lib/index.d.ts.map +1 -1
  103. package/lib/index.js +2 -0
  104. package/lib/index.js.map +1 -1
  105. package/lib/loader.d.ts +6 -9
  106. package/lib/loader.d.ts.map +1 -1
  107. package/lib/loader.js +27 -92
  108. package/lib/loader.js.map +1 -1
  109. package/lib/location-redirection-utilities/index.d.ts +6 -0
  110. package/lib/location-redirection-utilities/index.d.ts.map +1 -0
  111. package/lib/location-redirection-utilities/index.js +6 -0
  112. package/lib/location-redirection-utilities/index.js.map +1 -0
  113. package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.ts +22 -0
  114. package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -0
  115. package/lib/location-redirection-utilities/resolveWithLocationRedirection.js +46 -0
  116. package/lib/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -0
  117. package/lib/packageVersion.d.ts +1 -1
  118. package/lib/packageVersion.js +1 -1
  119. package/lib/packageVersion.js.map +1 -1
  120. package/lib/protocol.d.ts +1 -2
  121. package/lib/protocol.d.ts.map +1 -1
  122. package/lib/protocol.js +1 -3
  123. package/lib/protocol.js.map +1 -1
  124. package/lib/retriableDocumentStorageService.d.ts +3 -2
  125. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  126. package/lib/retriableDocumentStorageService.js +18 -11
  127. package/lib/retriableDocumentStorageService.js.map +1 -1
  128. package/lib/utils.d.ts +23 -1
  129. package/lib/utils.d.ts.map +1 -1
  130. package/lib/utils.js +9 -1
  131. package/lib/utils.js.map +1 -1
  132. package/package.json +24 -26
  133. package/src/connectionManager.ts +69 -34
  134. package/src/container.ts +48 -30
  135. package/src/containerStorageAdapter.ts +3 -9
  136. package/src/contracts.ts +8 -4
  137. package/src/debugLogger.ts +5 -1
  138. package/src/deltaManager.ts +16 -31
  139. package/src/index.ts +5 -0
  140. package/src/loader.ts +31 -99
  141. package/src/location-redirection-utilities/index.ts +9 -0
  142. package/src/location-redirection-utilities/resolveWithLocationRedirection.ts +59 -0
  143. package/src/packageVersion.ts +1 -1
  144. package/src/protocol.ts +2 -6
  145. package/src/retriableDocumentStorageService.ts +29 -15
  146. package/src/utils.ts +23 -1
@@ -2,11 +2,15 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
+ import { LogLevel } from "@fluidframework/core-interfaces";
5
6
  import { assert } from "@fluidframework/core-utils";
6
7
  import { performance, TypedEventEmitter } from "@fluid-internal/client-utils";
8
+ import {
9
+ // eslint-disable-next-line import/no-deprecated
10
+ DriverErrorType, } from "@fluidframework/driver-definitions";
7
11
  import { canRetryOnError, createWriteError, createGenericNetworkError, getRetryDelayFromError, logNetworkFailure, isRuntimeMessage, calculateMaxWaitTime, } from "@fluidframework/driver-utils";
8
12
  import { MessageType, ScopeType, } from "@fluidframework/protocol-definitions";
9
- import { formatTick, GenericError, normalizeError, UsageError, } from "@fluidframework/telemetry-utils";
13
+ import { formatTick, GenericError, isFluidError, normalizeError, UsageError, } from "@fluidframework/telemetry-utils";
10
14
  import { ReconnectMode, } from "./contracts";
11
15
  import { DeltaQueue } from "./deltaQueue";
12
16
  import { SignalType } from "./protocol";
@@ -33,9 +37,15 @@ const clientNoDeltaStream = {
33
37
  };
34
38
  const clientIdNoDeltaStream = "storage-only client";
35
39
  class NoDeltaStream extends TypedEventEmitter {
36
- constructor(storageOnlyReason) {
40
+ /**
41
+ * Connection which is not connected to socket.
42
+ * @param storageOnlyReason - Reason on why the connection to delta stream is not allowed.
43
+ * @param readonlyConnectionReason - reason/error if any which lead to using NoDeltaStream.
44
+ */
45
+ constructor(storageOnlyReason, readonlyConnectionReason) {
37
46
  super();
38
47
  this.storageOnlyReason = storageOnlyReason;
48
+ this.readonlyConnectionReason = readonlyConnectionReason;
39
49
  this.clientId = clientIdNoDeltaStream;
40
50
  this.claims = {
41
51
  scopes: [ScopeType.DocRead],
@@ -98,67 +108,6 @@ const waitForOnline = async () => {
98
108
  * Exposes various controls to influence this process, including manual reconnects, forced read-only mode, etc.
99
109
  */
100
110
  export class ConnectionManager {
101
- constructor(serviceProvider, containerDirty, client, reconnectAllowed, logger, props) {
102
- this.serviceProvider = serviceProvider;
103
- this.containerDirty = containerDirty;
104
- this.client = client;
105
- this.logger = logger;
106
- this.props = props;
107
- /** tracks host requiring read-only mode. */
108
- this._forceReadonly = false;
109
- /** True if there is pending (async) reconnection from "read" to "write" */
110
- this.pendingReconnect = false;
111
- this.clientSequenceNumber = 0;
112
- this.clientSequenceNumberObserved = 0;
113
- /** Counts the number of non-runtime ops sent by the client which may not be acked. */
114
- this.localOpsToIgnore = 0;
115
- this.connectFirstConnection = true;
116
- this._connectionVerboseProps = {};
117
- this._connectionProps = {};
118
- this._disposed = false;
119
- this.opHandler = (documentId, messagesArg) => {
120
- const messages = Array.isArray(messagesArg) ? messagesArg : [messagesArg];
121
- this.props.incomingOpHandler(messages, "opHandler");
122
- };
123
- // Always connect in write mode after getting nacked.
124
- this.nackHandler = (documentId, messages) => {
125
- const message = messages[0];
126
- if (this._readonlyPermissions === true) {
127
- this.props.closeHandler(createWriteError("writeOnReadOnlyDocument", { driverVersion: undefined }));
128
- return;
129
- }
130
- const reconnectInfo = getNackReconnectInfo(message.content);
131
- // If the nack indicates we cannot retry, then close the container outright
132
- if (!reconnectInfo.canRetry) {
133
- this.props.closeHandler(reconnectInfo);
134
- return;
135
- }
136
- this.reconnectOnError("write", reconnectInfo);
137
- };
138
- // Connection mode is always read on disconnect/error unless the system mode was write.
139
- this.disconnectHandlerInternal = (disconnectReason) => {
140
- // Note: we might get multiple disconnect calls on same socket, as early disconnect notification
141
- // ("server_disconnect", ODSP-specific) is mapped to "disconnect"
142
- this.reconnectOnError(this.defaultReconnectionMode, disconnectReason);
143
- };
144
- this.errorHandler = (error) => {
145
- this.reconnectOnError(this.defaultReconnectionMode, error);
146
- };
147
- this.clientDetails = this.client.details;
148
- this.defaultReconnectionMode = this.client.mode;
149
- this._reconnectMode = reconnectAllowed ? ReconnectMode.Enabled : ReconnectMode.Never;
150
- // Outbound message queue. The outbound queue is represented as a queue of an array of ops. Ops contained
151
- // within an array *must* fit within the maxMessageSize and are guaranteed to be ordered sequentially.
152
- this._outbound = new DeltaQueue((messages) => {
153
- if (this.connection === undefined) {
154
- throw new Error("Attempted to submit an outbound message without connection");
155
- }
156
- this.connection.submit(messages);
157
- });
158
- this._outbound.on("error", (error) => {
159
- this.props.closeHandler(normalizeError(error));
160
- });
161
- }
162
111
  get connectionVerboseProps() {
163
112
  return this._connectionVerboseProps;
164
113
  }
@@ -272,6 +221,71 @@ export class ConnectionManager {
272
221
  reason,
273
222
  };
274
223
  }
224
+ constructor(serviceProvider, containerDirty, client, reconnectAllowed, logger, props) {
225
+ this.serviceProvider = serviceProvider;
226
+ this.containerDirty = containerDirty;
227
+ this.client = client;
228
+ this.logger = logger;
229
+ this.props = props;
230
+ /** tracks host requiring read-only mode. */
231
+ this._forceReadonly = false;
232
+ /** True if there is pending (async) reconnection from "read" to "write" */
233
+ this.pendingReconnect = false;
234
+ this.clientSequenceNumber = 0;
235
+ this.clientSequenceNumberObserved = 0;
236
+ /** Counts the number of non-runtime ops sent by the client which may not be acked. */
237
+ this.localOpsToIgnore = 0;
238
+ this.connectFirstConnection = true;
239
+ this._connectionVerboseProps = {};
240
+ this._connectionProps = {};
241
+ this._disposed = false;
242
+ this.opHandler = (documentId, messagesArg) => {
243
+ const messages = Array.isArray(messagesArg) ? messagesArg : [messagesArg];
244
+ this.props.incomingOpHandler(messages, "opHandler");
245
+ };
246
+ this.signalHandler = (signalsArg) => {
247
+ const signals = Array.isArray(signalsArg) ? signalsArg : [signalsArg];
248
+ this.props.signalHandler(signals);
249
+ };
250
+ // Always connect in write mode after getting nacked.
251
+ this.nackHandler = (documentId, messages) => {
252
+ const message = messages[0];
253
+ if (this._readonlyPermissions === true) {
254
+ this.props.closeHandler(createWriteError("writeOnReadOnlyDocument", { driverVersion: undefined }));
255
+ return;
256
+ }
257
+ const reconnectInfo = getNackReconnectInfo(message.content);
258
+ // If the nack indicates we cannot retry, then close the container outright
259
+ if (!reconnectInfo.canRetry) {
260
+ this.props.closeHandler(reconnectInfo);
261
+ return;
262
+ }
263
+ this.reconnectOnError("write", reconnectInfo);
264
+ };
265
+ // Connection mode is always read on disconnect/error unless the system mode was write.
266
+ this.disconnectHandlerInternal = (disconnectReason) => {
267
+ // Note: we might get multiple disconnect calls on same socket, as early disconnect notification
268
+ // ("server_disconnect", ODSP-specific) is mapped to "disconnect"
269
+ this.reconnectOnError(this.defaultReconnectionMode, disconnectReason);
270
+ };
271
+ this.errorHandler = (error) => {
272
+ this.reconnectOnError(this.defaultReconnectionMode, error);
273
+ };
274
+ this.clientDetails = this.client.details;
275
+ this.defaultReconnectionMode = this.client.mode;
276
+ this._reconnectMode = reconnectAllowed ? ReconnectMode.Enabled : ReconnectMode.Never;
277
+ // Outbound message queue. The outbound queue is represented as a queue of an array of ops. Ops contained
278
+ // within an array *must* fit within the maxMessageSize and are guaranteed to be ordered sequentially.
279
+ this._outbound = new DeltaQueue((messages) => {
280
+ if (this.connection === undefined) {
281
+ throw new Error("Attempted to submit an outbound message without connection");
282
+ }
283
+ this.connection.submit(messages);
284
+ });
285
+ this._outbound.on("error", (error) => {
286
+ this.props.closeHandler(normalizeError(error));
287
+ });
288
+ }
275
289
  dispose(error, switchToReadonly = true) {
276
290
  if (this._disposed) {
277
291
  return;
@@ -291,7 +305,7 @@ export class ConnectionManager {
291
305
  // Notify everyone we are in read-only state.
292
306
  // Useful for data stores in case we hit some critical error,
293
307
  // to switch to a mode where user edits are not accepted
294
- this.set_readonlyPermissions(true, oldReadonlyValue);
308
+ this.set_readonlyPermissions(true, oldReadonlyValue, disconnectReason);
295
309
  }
296
310
  }
297
311
  /**
@@ -307,21 +321,7 @@ export class ConnectionManager {
307
321
  }
308
322
  }
309
323
  /**
310
- * Sends signal to runtime (and data stores) to be read-only.
311
- * Hosts may have read only views, indicating to data stores that no edits are allowed.
312
- * This is independent from this._readonlyPermissions (permissions) and this.connectionMode
313
- * (server can return "write" mode even when asked for "read")
314
- * Leveraging same "readonly" event as runtime & data stores should behave the same in such case
315
- * as in read-only permissions.
316
- * But this.active can be used by some DDSes to figure out if ops can be sent
317
- * (for example, read-only view still participates in code proposals / upgrades decisions)
318
- *
319
- * Forcing Readonly does not prevent DDS from generating ops. It is up to user code to honour
320
- * the readonly flag. If ops are generated, they will accumulate locally and not be sent. If
321
- * there are pending in the outbound queue, it will stop sending until force readonly is
322
- * cleared.
323
- *
324
- * @param readonly - set or clear force readonly.
324
+ * {@inheritDoc Container.forceReadonly}
325
325
  */
326
326
  forceReadonly(readonly) {
327
327
  if (readonly !== this._forceReadonly) {
@@ -355,10 +355,10 @@ export class ConnectionManager {
355
355
  }
356
356
  }
357
357
  }
358
- set_readonlyPermissions(newReadonlyValue, oldReadonlyValue) {
358
+ set_readonlyPermissions(newReadonlyValue, oldReadonlyValue, readonlyConnectionReason) {
359
359
  this._readonlyPermissions = newReadonlyValue;
360
360
  if (oldReadonlyValue !== this.readonly) {
361
- this.props.readonlyChangeHandler(this.readonly);
361
+ this.props.readonlyChangeHandler(this.readonly, readonlyConnectionReason);
362
362
  }
363
363
  }
364
364
  connect(reason, connectionMode) {
@@ -436,10 +436,29 @@ export class ConnectionManager {
436
436
  this.logger.sendTelemetryEvent({ eventName: "ReceivedClosedConnection" });
437
437
  connection = undefined;
438
438
  }
439
+ this.logger.sendTelemetryEvent({
440
+ eventName: "ConnectionReceived",
441
+ connected: connection !== undefined && connection.disposed === false,
442
+ }, undefined, LogLevel.verbose);
439
443
  }
440
444
  catch (origError) {
441
445
  if (isDeltaStreamConnectionForbiddenError(origError)) {
442
- connection = new NoDeltaStream(origError.storageOnlyReason);
446
+ connection = new NoDeltaStream(origError.storageOnlyReason, {
447
+ text: origError.message,
448
+ error: origError,
449
+ });
450
+ requestedMode = "read";
451
+ break;
452
+ }
453
+ else if (isFluidError(origError) &&
454
+ // eslint-disable-next-line import/no-deprecated
455
+ origError.errorType === DriverErrorType.outOfStorageError) {
456
+ // If we get out of storage error from calling joinsession, then use the NoDeltaStream object so
457
+ // that user can at least load the container.
458
+ connection = new NoDeltaStream(undefined, {
459
+ text: origError.message,
460
+ error: origError,
461
+ });
443
462
  requestedMode = "read";
444
463
  break;
445
464
  }
@@ -496,8 +515,8 @@ export class ConnectionManager {
496
515
  duration: formatTick(performance.now() - connectStartTime),
497
516
  }, lastError);
498
517
  }
499
- // Check for abort signal after while loop as well
500
- if (abortSignal.aborted === true) {
518
+ // Check for abort signal after while loop as well or we've been disposed
519
+ if (abortSignal.aborted === true || this._disposed) {
501
520
  connection.dispose();
502
521
  this.logger.sendTelemetryEvent({
503
522
  eventName: "ConnectionAttemptCancelled",
@@ -547,7 +566,7 @@ export class ConnectionManager {
547
566
  this.connection = undefined;
548
567
  // Remove listeners first so we don't try to retrigger this flow accidentally through reconnectOnError
549
568
  connection.off("op", this.opHandler);
550
- connection.off("signal", this.props.signalHandler);
569
+ connection.off("signal", this.signalHandler);
551
570
  connection.off("nack", this.nackHandler);
552
571
  connection.off("disconnect", this.disconnectHandlerInternal);
553
572
  connection.off("error", this.errorHandler);
@@ -600,7 +619,7 @@ export class ConnectionManager {
600
619
  // removed after those packages have released and become ubiquitous.
601
620
  assert(requestedMode === "read" || readonly === (this.connectionMode === "read"), 0x0e7 /* "claims/connectionMode mismatch" */);
602
621
  assert(!readonly || this.connectionMode === "read", 0x0e8 /* "readonly perf with write connection" */);
603
- this.set_readonlyPermissions(readonly, oldReadonlyValue);
622
+ this.set_readonlyPermissions(readonly, oldReadonlyValue, isNoDeltaStreamConnection(connection) ? connection.readonlyConnectionReason : undefined);
604
623
  if (this._disposed) {
605
624
  // Raise proper events, Log telemetry event and close connection.
606
625
  this.disconnectFromDeltaStream({ text: "ConnectionManager already closed" });
@@ -608,7 +627,7 @@ export class ConnectionManager {
608
627
  }
609
628
  this._outbound.resume();
610
629
  connection.on("op", this.opHandler);
611
- connection.on("signal", this.props.signalHandler);
630
+ connection.on("signal", this.signalHandler);
612
631
  connection.on("nack", this.nackHandler);
613
632
  connection.on("disconnect", this.disconnectHandlerInternal);
614
633
  connection.on("error", this.errorHandler);
@@ -659,26 +678,26 @@ export class ConnectionManager {
659
678
  type: SignalType.Clear,
660
679
  }),
661
680
  };
662
- this.props.signalHandler(clearSignal);
663
- for (const priorClient of connection.initialClients ?? []) {
664
- const joinSignal = {
665
- clientId: null,
666
- content: JSON.stringify({
667
- type: SignalType.ClientJoin,
668
- content: priorClient, // ISignalClient
669
- }),
670
- };
671
- this.props.signalHandler(joinSignal);
681
+ // list of signals to process due to this new connection
682
+ let signalsToProcess = [clearSignal];
683
+ const clientJoinSignals = (connection.initialClients ?? []).map((priorClient) => ({
684
+ clientId: null,
685
+ content: JSON.stringify({
686
+ type: SignalType.ClientJoin,
687
+ content: priorClient, // ISignalClient
688
+ }),
689
+ }));
690
+ if (clientJoinSignals.length > 0) {
691
+ signalsToProcess = signalsToProcess.concat(clientJoinSignals);
672
692
  }
673
693
  // Unfortunately, there is no defined order between initialSignals (including join & leave signals)
674
694
  // and connection.initialClients. In practice, connection.initialSignals quite often contains join signal
675
695
  // for "self" and connection.initialClients does not contain "self", so we have to process them after
676
696
  // "clear" signal above.
677
- if (connection.initialSignals !== undefined) {
678
- for (const signal of connection.initialSignals) {
679
- this.props.signalHandler(signal);
680
- }
697
+ if (connection.initialSignals !== undefined && connection.initialSignals.length > 0) {
698
+ signalsToProcess = signalsToProcess.concat(connection.initialSignals);
681
699
  }
700
+ this.props.signalHandler(signalsToProcess);
682
701
  }
683
702
  /**
684
703
  * Disconnect the current connection and reconnect. Closes the container if it fails.