@fluidframework/container-loader 2.0.0-dev.2.3.0.115467 → 2.0.0-dev.4.1.0.148229

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 (168) hide show
  1. package/.eslintrc.js +18 -21
  2. package/.mocharc.js +2 -2
  3. package/README.md +65 -44
  4. package/api-extractor.json +2 -2
  5. package/closeAndGetPendingLocalState.md +51 -0
  6. package/dist/audience.d.ts +0 -1
  7. package/dist/audience.d.ts.map +1 -1
  8. package/dist/audience.js.map +1 -1
  9. package/dist/catchUpMonitor.d.ts.map +1 -1
  10. package/dist/catchUpMonitor.js.map +1 -1
  11. package/dist/collabWindowTracker.d.ts.map +1 -1
  12. package/dist/collabWindowTracker.js.map +1 -1
  13. package/dist/connectionManager.d.ts +5 -5
  14. package/dist/connectionManager.d.ts.map +1 -1
  15. package/dist/connectionManager.js +107 -44
  16. package/dist/connectionManager.js.map +1 -1
  17. package/dist/connectionState.d.ts.map +1 -1
  18. package/dist/connectionState.js.map +1 -1
  19. package/dist/connectionStateHandler.d.ts +7 -7
  20. package/dist/connectionStateHandler.d.ts.map +1 -1
  21. package/dist/connectionStateHandler.js +50 -21
  22. package/dist/connectionStateHandler.js.map +1 -1
  23. package/dist/container.d.ts +64 -5
  24. package/dist/container.d.ts.map +1 -1
  25. package/dist/container.js +329 -137
  26. package/dist/container.js.map +1 -1
  27. package/dist/containerContext.d.ts +19 -8
  28. package/dist/containerContext.d.ts.map +1 -1
  29. package/dist/containerContext.js +58 -14
  30. package/dist/containerContext.js.map +1 -1
  31. package/dist/containerStorageAdapter.d.ts +41 -2
  32. package/dist/containerStorageAdapter.d.ts.map +1 -1
  33. package/dist/containerStorageAdapter.js +88 -14
  34. package/dist/containerStorageAdapter.js.map +1 -1
  35. package/dist/contracts.d.ts +3 -3
  36. package/dist/contracts.d.ts.map +1 -1
  37. package/dist/contracts.js.map +1 -1
  38. package/dist/deltaManager.d.ts +21 -8
  39. package/dist/deltaManager.d.ts.map +1 -1
  40. package/dist/deltaManager.js +112 -37
  41. package/dist/deltaManager.js.map +1 -1
  42. package/dist/deltaManagerProxy.d.ts +10 -22
  43. package/dist/deltaManagerProxy.d.ts.map +1 -1
  44. package/dist/deltaManagerProxy.js +14 -50
  45. package/dist/deltaManagerProxy.js.map +1 -1
  46. package/dist/deltaQueue.d.ts.map +1 -1
  47. package/dist/deltaQueue.js +4 -2
  48. package/dist/deltaQueue.js.map +1 -1
  49. package/dist/index.d.ts +4 -3
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +1 -3
  52. package/dist/index.js.map +1 -1
  53. package/dist/loader.d.ts +13 -4
  54. package/dist/loader.d.ts.map +1 -1
  55. package/dist/loader.js +38 -24
  56. package/dist/loader.js.map +1 -1
  57. package/dist/packageVersion.d.ts +1 -1
  58. package/dist/packageVersion.js +1 -1
  59. package/dist/packageVersion.js.map +1 -1
  60. package/dist/protocol.d.ts.map +1 -1
  61. package/dist/protocol.js +2 -1
  62. package/dist/protocol.js.map +1 -1
  63. package/dist/protocolTreeDocumentStorageService.d.ts +6 -2
  64. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  65. package/dist/protocolTreeDocumentStorageService.js +7 -4
  66. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  67. package/dist/quorum.d.ts.map +1 -1
  68. package/dist/quorum.js.map +1 -1
  69. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  70. package/dist/retriableDocumentStorageService.js +6 -2
  71. package/dist/retriableDocumentStorageService.js.map +1 -1
  72. package/dist/utils.d.ts.map +1 -1
  73. package/dist/utils.js +8 -5
  74. package/dist/utils.js.map +1 -1
  75. package/lib/audience.d.ts +0 -1
  76. package/lib/audience.d.ts.map +1 -1
  77. package/lib/audience.js.map +1 -1
  78. package/lib/catchUpMonitor.d.ts.map +1 -1
  79. package/lib/catchUpMonitor.js.map +1 -1
  80. package/lib/collabWindowTracker.d.ts.map +1 -1
  81. package/lib/collabWindowTracker.js.map +1 -1
  82. package/lib/connectionManager.d.ts +5 -5
  83. package/lib/connectionManager.d.ts.map +1 -1
  84. package/lib/connectionManager.js +110 -47
  85. package/lib/connectionManager.js.map +1 -1
  86. package/lib/connectionState.d.ts.map +1 -1
  87. package/lib/connectionState.js.map +1 -1
  88. package/lib/connectionStateHandler.d.ts +7 -7
  89. package/lib/connectionStateHandler.d.ts.map +1 -1
  90. package/lib/connectionStateHandler.js +50 -21
  91. package/lib/connectionStateHandler.js.map +1 -1
  92. package/lib/container.d.ts +64 -5
  93. package/lib/container.d.ts.map +1 -1
  94. package/lib/container.js +336 -144
  95. package/lib/container.js.map +1 -1
  96. package/lib/containerContext.d.ts +19 -8
  97. package/lib/containerContext.d.ts.map +1 -1
  98. package/lib/containerContext.js +59 -15
  99. package/lib/containerContext.js.map +1 -1
  100. package/lib/containerStorageAdapter.d.ts +41 -2
  101. package/lib/containerStorageAdapter.d.ts.map +1 -1
  102. package/lib/containerStorageAdapter.js +86 -14
  103. package/lib/containerStorageAdapter.js.map +1 -1
  104. package/lib/contracts.d.ts +3 -3
  105. package/lib/contracts.d.ts.map +1 -1
  106. package/lib/contracts.js.map +1 -1
  107. package/lib/deltaManager.d.ts +21 -8
  108. package/lib/deltaManager.d.ts.map +1 -1
  109. package/lib/deltaManager.js +114 -39
  110. package/lib/deltaManager.js.map +1 -1
  111. package/lib/deltaManagerProxy.d.ts +10 -22
  112. package/lib/deltaManagerProxy.d.ts.map +1 -1
  113. package/lib/deltaManagerProxy.js +14 -50
  114. package/lib/deltaManagerProxy.js.map +1 -1
  115. package/lib/deltaQueue.d.ts.map +1 -1
  116. package/lib/deltaQueue.js +4 -2
  117. package/lib/deltaQueue.js.map +1 -1
  118. package/lib/index.d.ts +4 -3
  119. package/lib/index.d.ts.map +1 -1
  120. package/lib/index.js +2 -2
  121. package/lib/index.js.map +1 -1
  122. package/lib/loader.d.ts +13 -4
  123. package/lib/loader.d.ts.map +1 -1
  124. package/lib/loader.js +37 -24
  125. package/lib/loader.js.map +1 -1
  126. package/lib/packageVersion.d.ts +1 -1
  127. package/lib/packageVersion.js +1 -1
  128. package/lib/packageVersion.js.map +1 -1
  129. package/lib/protocol.d.ts.map +1 -1
  130. package/lib/protocol.js +2 -1
  131. package/lib/protocol.js.map +1 -1
  132. package/lib/protocolTreeDocumentStorageService.d.ts +6 -2
  133. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  134. package/lib/protocolTreeDocumentStorageService.js +7 -4
  135. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  136. package/lib/quorum.d.ts.map +1 -1
  137. package/lib/quorum.js.map +1 -1
  138. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  139. package/lib/retriableDocumentStorageService.js +6 -2
  140. package/lib/retriableDocumentStorageService.js.map +1 -1
  141. package/lib/utils.d.ts.map +1 -1
  142. package/lib/utils.js +8 -5
  143. package/lib/utils.js.map +1 -1
  144. package/package.json +67 -56
  145. package/prettier.config.cjs +1 -1
  146. package/src/audience.ts +51 -46
  147. package/src/catchUpMonitor.ts +39 -37
  148. package/src/collabWindowTracker.ts +75 -70
  149. package/src/connectionManager.ts +1040 -941
  150. package/src/connectionState.ts +19 -19
  151. package/src/connectionStateHandler.ts +557 -463
  152. package/src/container.ts +2147 -1784
  153. package/src/containerContext.ts +417 -345
  154. package/src/containerStorageAdapter.ts +268 -154
  155. package/src/contracts.ts +155 -153
  156. package/src/deltaManager.ts +1074 -945
  157. package/src/deltaManagerProxy.ts +88 -137
  158. package/src/deltaQueue.ts +155 -151
  159. package/src/index.ts +13 -17
  160. package/src/loader.ts +434 -427
  161. package/src/packageVersion.ts +1 -1
  162. package/src/protocol.ts +93 -87
  163. package/src/protocolTreeDocumentStorageService.ts +34 -34
  164. package/src/quorum.ts +34 -34
  165. package/src/retriableDocumentStorageService.ts +118 -102
  166. package/src/utils.ts +93 -83
  167. package/tsconfig.esnext.json +6 -6
  168. package/tsconfig.json +8 -12
@@ -5,10 +5,11 @@
5
5
  import { default as AbortController } from "abort-controller";
6
6
  import { assert, performance, TypedEventEmitter } from "@fluidframework/common-utils";
7
7
  import { GenericError, UsageError } from "@fluidframework/container-utils";
8
- import { canRetryOnError, createWriteError, createGenericNetworkError, getRetryDelayFromError, waitForConnectedState, DeltaStreamConnectionForbiddenError, logNetworkFailure, isRuntimeMessage, } from "@fluidframework/driver-utils";
8
+ import { DriverErrorType, } from "@fluidframework/driver-definitions";
9
+ import { canRetryOnError, createWriteError, createGenericNetworkError, getRetryDelayFromError, logNetworkFailure, isRuntimeMessage, } from "@fluidframework/driver-utils";
9
10
  import { MessageType, ScopeType, } from "@fluidframework/protocol-definitions";
10
- import { TelemetryLogger, normalizeError, } from "@fluidframework/telemetry-utils";
11
- import { ReconnectMode, } from "./contracts";
11
+ import { TelemetryLogger, normalizeError } from "@fluidframework/telemetry-utils";
12
+ import { ReconnectMode } from "./contracts";
12
13
  import { DeltaQueue } from "./deltaQueue";
13
14
  import { SignalType } from "./protocol";
14
15
  const MaxReconnectDelayInMs = 8000;
@@ -46,7 +47,9 @@ class NoDeltaStream extends TypedEventEmitter {
46
47
  this.version = "";
47
48
  this.initialMessages = [];
48
49
  this.initialSignals = [];
49
- this.initialClients = [{ client: clientNoDeltaStream, clientId: clientIdNoDeltaStream }];
50
+ this.initialClients = [
51
+ { client: clientNoDeltaStream, clientId: clientIdNoDeltaStream },
52
+ ];
50
53
  this.serviceConfiguration = {
51
54
  maxMessageSize: 0,
52
55
  blockSize: 0,
@@ -68,9 +71,26 @@ class NoDeltaStream extends TypedEventEmitter {
68
71
  content: { message: "Cannot submit signal with storage-only connection", code: 403 },
69
72
  });
70
73
  }
71
- get disposed() { return this._disposed; }
72
- dispose() { this._disposed = true; }
74
+ get disposed() {
75
+ return this._disposed;
76
+ }
77
+ dispose() {
78
+ this._disposed = true;
79
+ }
73
80
  }
81
+ const waitForOnline = async () => {
82
+ var _a;
83
+ // Only wait if we have a strong signal that we're offline - otherwise assume we're online.
84
+ if (((_a = globalThis.navigator) === null || _a === void 0 ? void 0 : _a.onLine) === false && globalThis.addEventListener !== undefined) {
85
+ return new Promise((resolve) => {
86
+ const resolveAndRemoveListener = () => {
87
+ resolve();
88
+ globalThis.removeEventListener("online", resolveAndRemoveListener);
89
+ };
90
+ globalThis.addEventListener("online", resolveAndRemoveListener);
91
+ });
92
+ }
93
+ };
74
94
  /**
75
95
  * Implementation of IConnectionManager, used by Container class
76
96
  * Implements constant connectivity to relay service, by reconnecting in case of lost connection or error.
@@ -88,12 +108,12 @@ export class ConnectionManager {
88
108
  this.pendingReconnect = false;
89
109
  this.clientSequenceNumber = 0;
90
110
  this.clientSequenceNumberObserved = 0;
91
- /** Counts the number of noops sent by the client which may not be acked. */
111
+ /** Counts the number of non-runtime ops sent by the client which may not be acked. */
92
112
  this.localOpsToIgnore = 0;
93
113
  this.connectFirstConnection = true;
94
114
  this._connectionVerboseProps = {};
95
115
  this._connectionProps = {};
96
- this.closed = false;
116
+ this._disposed = false;
97
117
  this.opHandler = (documentId, messagesArg) => {
98
118
  const messages = Array.isArray(messagesArg) ? messagesArg : [messagesArg];
99
119
  this.props.incomingOpHandler(messages, "opHandler");
@@ -137,7 +157,9 @@ export class ConnectionManager {
137
157
  this.props.closeHandler(normalizeError(error));
138
158
  });
139
159
  }
140
- get connectionVerboseProps() { return this._connectionVerboseProps; }
160
+ get connectionVerboseProps() {
161
+ return this._connectionVerboseProps;
162
+ }
141
163
  /**
142
164
  * The current connection mode, initially read.
143
165
  */
@@ -145,8 +167,13 @@ export class ConnectionManager {
145
167
  var _a, _b;
146
168
  return (_b = (_a = this.connection) === null || _a === void 0 ? void 0 : _a.mode) !== null && _b !== void 0 ? _b : "read";
147
169
  }
148
- get connected() { return this.connection !== undefined; }
149
- get clientId() { var _a; return (_a = this.connection) === null || _a === void 0 ? void 0 : _a.clientId; }
170
+ get connected() {
171
+ return this.connection !== undefined;
172
+ }
173
+ get clientId() {
174
+ var _a;
175
+ return (_a = this.connection) === null || _a === void 0 ? void 0 : _a.clientId;
176
+ }
150
177
  /**
151
178
  * Automatic reconnecting enabled or disabled.
152
179
  * If set to Never, then reconnecting will never be allowed.
@@ -178,7 +205,7 @@ export class ConnectionManager {
178
205
  /**
179
206
  * Returns set of props that can be logged in telemetry that provide some insights / statistics
180
207
  * about current or last connection (if there is no connection at the moment)
181
- */
208
+ */
182
209
  get connectionProps() {
183
210
  return this.connection !== undefined
184
211
  ? this._connectionProps
@@ -188,7 +215,7 @@ export class ConnectionManager {
188
215
  }
189
216
  shouldJoinWrite() {
190
217
  // We don't have to wait for ack for topmost NoOps. So subtract those.
191
- return this.clientSequenceNumberObserved < (this.clientSequenceNumber - this.localOpsToIgnore);
218
+ return (this.clientSequenceNumberObserved < this.clientSequenceNumber - this.localOpsToIgnore);
192
219
  }
193
220
  /**
194
221
  * Tells if container is in read-only mode.
@@ -221,37 +248,38 @@ export class ConnectionManager {
221
248
  return {
222
249
  claims: connection.claims,
223
250
  clientId: connection.clientId,
224
- existing: connection.existing,
225
251
  checkpointSequenceNumber: connection.checkpointSequenceNumber,
226
- get initialClients() { return connection.initialClients; },
252
+ get initialClients() {
253
+ return connection.initialClients;
254
+ },
227
255
  mode: connection.mode,
228
256
  serviceConfiguration: connection.serviceConfiguration,
229
257
  version: connection.version,
230
258
  };
231
259
  }
232
- dispose(error) {
233
- if (this.closed) {
260
+ dispose(error, switchToReadonly = true) {
261
+ if (this._disposed) {
234
262
  return;
235
263
  }
236
- this.closed = true;
264
+ this._disposed = true;
237
265
  this.pendingConnection = undefined;
238
266
  // Ensure that things like triggerConnect() will short circuit
239
267
  this._reconnectMode = ReconnectMode.Never;
240
268
  this._outbound.clear();
241
- const disconnectReason = error !== undefined
242
- ? `Closing DeltaManager (${error.message})`
243
- : "Closing DeltaManager";
269
+ const disconnectReason = "Closing DeltaManager";
244
270
  // This raises "disconnect" event if we have active connection.
245
271
  this.disconnectFromDeltaStream(disconnectReason);
246
- // Notify everyone we are in read-only state.
247
- // Useful for data stores in case we hit some critical error,
248
- // to switch to a mode where user edits are not accepted
249
- this.set_readonlyPermissions(true);
272
+ if (switchToReadonly) {
273
+ // Notify everyone we are in read-only state.
274
+ // Useful for data stores in case we hit some critical error,
275
+ // to switch to a mode where user edits are not accepted
276
+ this.set_readonlyPermissions(true);
277
+ }
250
278
  }
251
279
  /**
252
280
  * Enables or disables automatic reconnecting.
253
281
  * Will throw an error if reconnectMode set to Never.
254
- */
282
+ */
255
283
  setAutoReconnect(mode) {
256
284
  assert(mode !== ReconnectMode.Never && this._reconnectMode !== ReconnectMode.Never, 0x278 /* "API is not supported for non-connecting or closed container" */);
257
285
  this._reconnectMode = mode;
@@ -323,8 +351,8 @@ export class ConnectionManager {
323
351
  });
324
352
  }
325
353
  async connectCore(connectionMode) {
326
- var _a, _b;
327
- assert(!this.closed, 0x26a /* "not closed" */);
354
+ var _a, _b, _c;
355
+ assert(!this._disposed, 0x26a /* "not closed" */);
328
356
  if (this.connection !== undefined) {
329
357
  return; // Connection attempt already completed successfully
330
358
  }
@@ -359,10 +387,15 @@ export class ConnectionManager {
359
387
  let lastError;
360
388
  const abortController = new AbortController();
361
389
  const abortSignal = abortController.signal;
362
- this.pendingConnection = { abort: () => { abortController.abort(); }, connectionMode: requestedMode };
390
+ this.pendingConnection = {
391
+ abort: () => {
392
+ abortController.abort();
393
+ },
394
+ connectionMode: requestedMode,
395
+ };
363
396
  // This loop will keep trying to connect until successful, with a delay between each iteration.
364
397
  while (connection === undefined) {
365
- if (this.closed) {
398
+ if (this._disposed) {
366
399
  throw new Error("Attempting to connect a closed DeltaManager");
367
400
  }
368
401
  if (abortSignal.aborted === true) {
@@ -385,8 +418,9 @@ export class ConnectionManager {
385
418
  }
386
419
  }
387
420
  catch (origError) {
388
- if (typeof origError === "object" && origError !== null &&
389
- (origError === null || origError === void 0 ? void 0 : origError.errorType) === DeltaStreamConnectionForbiddenError.errorType) {
421
+ if (typeof origError === "object" &&
422
+ origError !== null &&
423
+ (origError === null || origError === void 0 ? void 0 : origError.errorType) === DriverErrorType.deltaStreamConnectionForbidden) {
390
424
  connection = new NoDeltaStream();
391
425
  requestedMode = "read";
392
426
  break;
@@ -406,11 +440,25 @@ export class ConnectionManager {
406
440
  }, origError);
407
441
  lastError = origError;
408
442
  const retryDelayFromError = getRetryDelayFromError(origError);
409
- delayMs = retryDelayFromError !== null && retryDelayFromError !== void 0 ? retryDelayFromError : Math.min(delayMs * 2, MaxReconnectDelayInMs);
410
443
  if (retryDelayFromError !== undefined) {
444
+ // If the error told us to wait, then we wait.
411
445
  this.props.reconnectionDelayHandler(retryDelayFromError, origError);
446
+ await new Promise((resolve) => {
447
+ setTimeout(resolve, retryDelayFromError);
448
+ });
449
+ }
450
+ else if (((_c = globalThis.navigator) === null || _c === void 0 ? void 0 : _c.onLine) !== false) {
451
+ // If the error didn't tell us to wait, let's still wait a little bit before retrying.
452
+ // We skip this delay if we're confident we're offline, because we probably just need to wait to come back online.
453
+ await new Promise((resolve) => {
454
+ setTimeout(resolve, delayMs);
455
+ delayMs = Math.min(delayMs * 2, MaxReconnectDelayInMs);
456
+ });
412
457
  }
413
- await waitForConnectedState(delayMs);
458
+ // If we believe we're offline, we assume there's no point in trying until we at least think we're online.
459
+ // NOTE: This isn't strictly true for drivers that don't require network (e.g. local driver). Really this logic
460
+ // should probably live in the driver.
461
+ await waitForOnline();
414
462
  }
415
463
  }
416
464
  // If we retried more than once, log an event about how long it took (this will not log to error table)
@@ -440,7 +488,7 @@ export class ConnectionManager {
440
488
  * @param args - The connection arguments
441
489
  */
442
490
  triggerConnect(connectionMode) {
443
- // reconnect() has async await of waitForConnectedState(), and that causes potential race conditions
491
+ // reconnect() includes async awaits, and that causes potential race conditions
444
492
  // where we might already have a connection. If it were to happen, it's possible that we will connect
445
493
  // with different mode to `connectionMode`. Glancing through the caller chains, it looks like code should be
446
494
  // fine (if needed, reconnect flow will get triggered again). Places where new mode matters should encode it
@@ -479,8 +527,8 @@ export class ConnectionManager {
479
527
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
480
528
  this._outbound.pause();
481
529
  this._outbound.clear();
482
- this.props.disconnectHandler(reason);
483
530
  connection.dispose();
531
+ this.props.disconnectHandler(reason);
484
532
  this._connectionVerboseProps = {};
485
533
  return true;
486
534
  }
@@ -510,14 +558,18 @@ export class ConnectionManager {
510
558
  // But if we ask read, server can still give us write.
511
559
  const readonly = !connection.claims.scopes.includes(ScopeType.DocWrite);
512
560
  if (connection.mode !== requestedMode) {
513
- this.logger.sendTelemetryEvent({ eventName: "ConnectionModeMismatch", requestedMode, mode: connection.mode });
561
+ this.logger.sendTelemetryEvent({
562
+ eventName: "ConnectionModeMismatch",
563
+ requestedMode,
564
+ mode: connection.mode,
565
+ });
514
566
  }
515
567
  // This connection mode validation logic is moving to the driver layer in 0.44. These two asserts can be
516
568
  // removed after those packages have released and become ubiquitous.
517
569
  assert(requestedMode === "read" || readonly === (this.connectionMode === "read"), 0x0e7 /* "claims/connectionMode mismatch" */);
518
570
  assert(!readonly || this.connectionMode === "read", 0x0e8 /* "readonly perf with write connection" */);
519
571
  this.set_readonlyPermissions(readonly);
520
- if (this.closed) {
572
+ if (this._disposed) {
521
573
  // Raise proper events, Log telemetry event and close connection.
522
574
  this.disconnectFromDeltaStream("ConnectionManager already closed");
523
575
  return;
@@ -550,7 +602,8 @@ export class ConnectionManager {
550
602
  this._connectionProps.connectionMode = connection.mode;
551
603
  let last = -1;
552
604
  if (initialMessages.length !== 0) {
553
- this._connectionVerboseProps.connectionInitialOpsFrom = initialMessages[0].sequenceNumber;
605
+ this._connectionVerboseProps.connectionInitialOpsFrom =
606
+ initialMessages[0].sequenceNumber;
554
607
  last = initialMessages[initialMessages.length - 1].sequenceNumber;
555
608
  this._connectionVerboseProps.connectionInitialOpsTo = last + 1;
556
609
  // Update knowledge of how far we are behind, before raising "connect" event
@@ -603,8 +656,7 @@ export class ConnectionManager {
603
656
  * @returns A promise that resolves when the connection is reestablished or we stop trying
604
657
  */
605
658
  reconnectOnError(requestedMode, error) {
606
- this.reconnect(requestedMode, error.message, error)
607
- .catch(this.props.closeHandler);
659
+ this.reconnect(requestedMode, error.message, error).catch(this.props.closeHandler);
608
660
  }
609
661
  /**
610
662
  * Disconnect the current connection and reconnect.
@@ -635,14 +687,21 @@ export class ConnectionManager {
635
687
  this.props.closeHandler();
636
688
  }
637
689
  // If closed then we can't reconnect
638
- if (this.closed || this.reconnectMode !== ReconnectMode.Enabled) {
690
+ if (this._disposed || this.reconnectMode !== ReconnectMode.Enabled) {
639
691
  return;
640
692
  }
693
+ // If the error tells us to wait before retrying, then do so.
641
694
  const delayMs = getRetryDelayFromError(error);
642
695
  if (error !== undefined && delayMs !== undefined) {
643
696
  this.props.reconnectionDelayHandler(delayMs, error);
644
- await waitForConnectedState(delayMs);
697
+ await new Promise((resolve) => {
698
+ setTimeout(resolve, delayMs);
699
+ });
645
700
  }
701
+ // If we believe we're offline, we assume there's no point in trying again until we at least think we're online.
702
+ // NOTE: This isn't strictly true for drivers that don't require network (e.g. local driver). Really this logic
703
+ // should probably live in the driver.
704
+ await waitForOnline();
646
705
  this.triggerConnect(requestedMode);
647
706
  }
648
707
  prepareMessageToSend(message) {
@@ -694,12 +753,15 @@ export class ConnectionManager {
694
753
  if (this.connectionMode === "read") {
695
754
  if (!this.pendingReconnect) {
696
755
  this.pendingReconnect = true;
697
- Promise.resolve().then(async () => {
698
- if (this.pendingReconnect) { // still valid?
756
+ Promise.resolve()
757
+ .then(async () => {
758
+ if (this.pendingReconnect) {
759
+ // still valid?
699
760
  await this.reconnect("write", // connectionMode
700
761
  "Switch to write");
701
762
  }
702
- }).catch(() => { });
763
+ })
764
+ .catch(() => { });
703
765
  }
704
766
  return;
705
767
  }
@@ -709,7 +771,8 @@ export class ConnectionManager {
709
771
  beforeProcessingIncomingOp(message) {
710
772
  // if we have connection, and message is local, then we better treat is as local!
711
773
  assert(this.clientId !== message.clientId || this.lastSubmittedClientId === message.clientId, 0x0ee /* "Not accounting local messages correctly" */);
712
- if (this.lastSubmittedClientId !== undefined && this.lastSubmittedClientId === message.clientId) {
774
+ if (this.lastSubmittedClientId !== undefined &&
775
+ this.lastSubmittedClientId === message.clientId) {
713
776
  const clientSequenceNumber = message.clientSequenceNumber;
714
777
  assert(this.clientSequenceNumberObserved < clientSequenceNumber, 0x0ef /* "client seq# not growing" */);
715
778
  assert(clientSequenceNumber <= this.clientSequenceNumber, 0x0f0 /* "Incoming local client seq# > generated by this client" */);