@fluidframework/container-loader 2.0.0-dev.5.3.2.178189 → 2.0.0-dev.6.4.0.191258

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 (173) hide show
  1. package/CHANGELOG.md +131 -0
  2. package/README.md +10 -6
  3. package/dist/audience.d.ts +1 -0
  4. package/dist/audience.d.ts.map +1 -1
  5. package/dist/audience.js +5 -3
  6. package/dist/audience.js.map +1 -1
  7. package/dist/catchUpMonitor.js +2 -2
  8. package/dist/catchUpMonitor.js.map +1 -1
  9. package/dist/connectionManager.d.ts +5 -5
  10. package/dist/connectionManager.d.ts.map +1 -1
  11. package/dist/connectionManager.js +97 -93
  12. package/dist/connectionManager.js.map +1 -1
  13. package/dist/connectionStateHandler.d.ts +15 -14
  14. package/dist/connectionStateHandler.d.ts.map +1 -1
  15. package/dist/connectionStateHandler.js +50 -52
  16. package/dist/connectionStateHandler.js.map +1 -1
  17. package/dist/container.d.ts +20 -9
  18. package/dist/container.d.ts.map +1 -1
  19. package/dist/container.js +327 -277
  20. package/dist/container.js.map +1 -1
  21. package/dist/containerContext.d.ts +2 -7
  22. package/dist/containerContext.d.ts.map +1 -1
  23. package/dist/containerContext.js +2 -14
  24. package/dist/containerContext.js.map +1 -1
  25. package/dist/containerStorageAdapter.d.ts.map +1 -1
  26. package/dist/containerStorageAdapter.js +12 -13
  27. package/dist/containerStorageAdapter.js.map +1 -1
  28. package/dist/contracts.d.ts +21 -8
  29. package/dist/contracts.d.ts.map +1 -1
  30. package/dist/contracts.js +3 -3
  31. package/dist/contracts.js.map +1 -1
  32. package/dist/debugLogger.d.ts +30 -0
  33. package/dist/debugLogger.d.ts.map +1 -0
  34. package/dist/debugLogger.js +95 -0
  35. package/dist/debugLogger.js.map +1 -0
  36. package/dist/deltaManager.d.ts +21 -10
  37. package/dist/deltaManager.d.ts.map +1 -1
  38. package/dist/deltaManager.js +114 -66
  39. package/dist/deltaManager.js.map +1 -1
  40. package/dist/deltaQueue.d.ts +1 -1
  41. package/dist/deltaQueue.d.ts.map +1 -1
  42. package/dist/deltaQueue.js +10 -10
  43. package/dist/deltaQueue.js.map +1 -1
  44. package/dist/disposal.d.ts +2 -2
  45. package/dist/disposal.d.ts.map +1 -1
  46. package/dist/disposal.js +1 -1
  47. package/dist/disposal.js.map +1 -1
  48. package/dist/error.d.ts +23 -0
  49. package/dist/error.d.ts.map +1 -0
  50. package/dist/error.js +32 -0
  51. package/dist/error.js.map +1 -0
  52. package/dist/loader.d.ts +22 -3
  53. package/dist/loader.d.ts.map +1 -1
  54. package/dist/loader.js +82 -51
  55. package/dist/loader.js.map +1 -1
  56. package/dist/noopHeuristic.d.ts +2 -2
  57. package/dist/noopHeuristic.d.ts.map +1 -1
  58. package/dist/noopHeuristic.js +6 -5
  59. package/dist/noopHeuristic.js.map +1 -1
  60. package/dist/packageVersion.d.ts +1 -1
  61. package/dist/packageVersion.js +1 -1
  62. package/dist/packageVersion.js.map +1 -1
  63. package/dist/protocol.d.ts +4 -2
  64. package/dist/protocol.d.ts.map +1 -1
  65. package/dist/protocol.js +25 -4
  66. package/dist/protocol.js.map +1 -1
  67. package/dist/quorum.d.ts +4 -1
  68. package/dist/quorum.d.ts.map +1 -1
  69. package/dist/quorum.js +1 -13
  70. package/dist/quorum.js.map +1 -1
  71. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  72. package/dist/retriableDocumentStorageService.js +4 -4
  73. package/dist/retriableDocumentStorageService.js.map +1 -1
  74. package/dist/utils.d.ts +8 -1
  75. package/dist/utils.d.ts.map +1 -1
  76. package/dist/utils.js +30 -11
  77. package/dist/utils.js.map +1 -1
  78. package/lib/audience.d.ts +1 -0
  79. package/lib/audience.d.ts.map +1 -1
  80. package/lib/audience.js +4 -2
  81. package/lib/audience.js.map +1 -1
  82. package/lib/catchUpMonitor.js +1 -1
  83. package/lib/catchUpMonitor.js.map +1 -1
  84. package/lib/connectionManager.d.ts +5 -5
  85. package/lib/connectionManager.d.ts.map +1 -1
  86. package/lib/connectionManager.js +74 -67
  87. package/lib/connectionManager.js.map +1 -1
  88. package/lib/connectionStateHandler.d.ts +15 -14
  89. package/lib/connectionStateHandler.d.ts.map +1 -1
  90. package/lib/connectionStateHandler.js +27 -29
  91. package/lib/connectionStateHandler.js.map +1 -1
  92. package/lib/container.d.ts +20 -9
  93. package/lib/container.d.ts.map +1 -1
  94. package/lib/container.js +288 -238
  95. package/lib/container.js.map +1 -1
  96. package/lib/containerContext.d.ts +2 -7
  97. package/lib/containerContext.d.ts.map +1 -1
  98. package/lib/containerContext.js +2 -14
  99. package/lib/containerContext.js.map +1 -1
  100. package/lib/containerStorageAdapter.d.ts.map +1 -1
  101. package/lib/containerStorageAdapter.js +5 -6
  102. package/lib/containerStorageAdapter.js.map +1 -1
  103. package/lib/contracts.d.ts +21 -8
  104. package/lib/contracts.d.ts.map +1 -1
  105. package/lib/contracts.js +3 -3
  106. package/lib/contracts.js.map +1 -1
  107. package/lib/debugLogger.d.ts +30 -0
  108. package/lib/debugLogger.d.ts.map +1 -0
  109. package/lib/debugLogger.js +91 -0
  110. package/lib/debugLogger.js.map +1 -0
  111. package/lib/deltaManager.d.ts +21 -10
  112. package/lib/deltaManager.d.ts.map +1 -1
  113. package/lib/deltaManager.js +88 -37
  114. package/lib/deltaManager.js.map +1 -1
  115. package/lib/deltaQueue.d.ts +1 -1
  116. package/lib/deltaQueue.d.ts.map +1 -1
  117. package/lib/deltaQueue.js +3 -3
  118. package/lib/deltaQueue.js.map +1 -1
  119. package/lib/disposal.d.ts +2 -2
  120. package/lib/disposal.d.ts.map +1 -1
  121. package/lib/disposal.js +1 -1
  122. package/lib/disposal.js.map +1 -1
  123. package/lib/error.d.ts +23 -0
  124. package/lib/error.d.ts.map +1 -0
  125. package/lib/error.js +28 -0
  126. package/lib/error.js.map +1 -0
  127. package/lib/loader.d.ts +22 -3
  128. package/lib/loader.d.ts.map +1 -1
  129. package/lib/loader.js +82 -51
  130. package/lib/loader.js.map +1 -1
  131. package/lib/noopHeuristic.d.ts +2 -2
  132. package/lib/noopHeuristic.d.ts.map +1 -1
  133. package/lib/noopHeuristic.js +2 -1
  134. package/lib/noopHeuristic.js.map +1 -1
  135. package/lib/packageVersion.d.ts +1 -1
  136. package/lib/packageVersion.js +1 -1
  137. package/lib/packageVersion.js.map +1 -1
  138. package/lib/protocol.d.ts +4 -2
  139. package/lib/protocol.d.ts.map +1 -1
  140. package/lib/protocol.js +25 -4
  141. package/lib/protocol.js.map +1 -1
  142. package/lib/quorum.d.ts +4 -1
  143. package/lib/quorum.d.ts.map +1 -1
  144. package/lib/quorum.js +0 -11
  145. package/lib/quorum.js.map +1 -1
  146. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  147. package/lib/retriableDocumentStorageService.js +2 -2
  148. package/lib/retriableDocumentStorageService.js.map +1 -1
  149. package/lib/utils.d.ts +8 -1
  150. package/lib/utils.d.ts.map +1 -1
  151. package/lib/utils.js +25 -7
  152. package/lib/utils.js.map +1 -1
  153. package/package.json +26 -32
  154. package/src/audience.ts +7 -1
  155. package/src/catchUpMonitor.ts +1 -1
  156. package/src/connectionManager.ts +75 -51
  157. package/src/connectionStateHandler.ts +31 -38
  158. package/src/container.ts +335 -240
  159. package/src/containerContext.ts +0 -16
  160. package/src/containerStorageAdapter.ts +2 -1
  161. package/src/contracts.ts +27 -11
  162. package/src/debugLogger.ts +113 -0
  163. package/src/deltaManager.ts +84 -34
  164. package/src/deltaQueue.ts +2 -1
  165. package/src/disposal.ts +2 -2
  166. package/src/error.ts +44 -0
  167. package/src/loader.ts +83 -35
  168. package/src/noopHeuristic.ts +3 -2
  169. package/src/packageVersion.ts +1 -1
  170. package/src/protocol.ts +33 -2
  171. package/src/quorum.ts +0 -10
  172. package/src/retriableDocumentStorageService.ts +2 -4
  173. package/src/utils.ts +33 -8
@@ -3,16 +3,14 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
 
6
- import { default as AbortController } from "abort-controller";
7
6
  import { IDisposable, ITelemetryProperties } from "@fluidframework/core-interfaces";
8
- import { assert, performance, TypedEventEmitter } from "@fluidframework/common-utils";
7
+ import { assert } from "@fluidframework/core-utils";
8
+ import { performance, TypedEventEmitter } from "@fluid-internal/client-utils";
9
9
  import {
10
+ ICriticalContainerError,
10
11
  IDeltaQueue,
11
12
  ReadOnlyInfo,
12
- IConnectionDetailsInternal,
13
- ICriticalContainerError,
14
13
  } from "@fluidframework/container-definitions";
15
- import { GenericError, UsageError } from "@fluidframework/container-utils";
16
14
  import {
17
15
  IAnyDriverError,
18
16
  IDocumentService,
@@ -26,6 +24,7 @@ import {
26
24
  getRetryDelayFromError,
27
25
  logNetworkFailure,
28
26
  isRuntimeMessage,
27
+ calculateMaxWaitTime,
29
28
  } from "@fluidframework/driver-utils";
30
29
  import {
31
30
  ConnectionMode,
@@ -44,16 +43,23 @@ import {
44
43
  ISequencedDocumentSystemMessage,
45
44
  } from "@fluidframework/protocol-definitions";
46
45
  import {
46
+ formatTick,
47
+ GenericError,
47
48
  ITelemetryLoggerExt,
48
- TelemetryLogger,
49
49
  normalizeError,
50
+ UsageError,
50
51
  } from "@fluidframework/telemetry-utils";
51
- import { ReconnectMode, IConnectionManager, IConnectionManagerFactoryArgs } from "./contracts";
52
+ import {
53
+ ReconnectMode,
54
+ IConnectionManager,
55
+ IConnectionManagerFactoryArgs,
56
+ IConnectionDetailsInternal,
57
+ IConnectionStateChangeReason,
58
+ } from "./contracts";
52
59
  import { DeltaQueue } from "./deltaQueue";
53
60
  import { SignalType } from "./protocol";
54
61
  import { isDeltaStreamConnectionForbiddenError } from "./utils";
55
62
 
56
- const MaxReconnectDelayInMs = 8000;
57
63
  const InitialReconnectDelayInMs = 1000;
58
64
  const DefaultChunkSize = 16 * 1024;
59
65
 
@@ -337,7 +343,7 @@ export class ConnectionManager implements IConnectionManager {
337
343
 
338
344
  private static detailsFromConnection(
339
345
  connection: IDocumentDeltaConnection,
340
- reason: string,
346
+ reason: IConnectionStateChangeReason,
341
347
  ): IConnectionDetailsInternal {
342
348
  return {
343
349
  claims: connection.claims,
@@ -356,7 +362,7 @@ export class ConnectionManager implements IConnectionManager {
356
362
  constructor(
357
363
  private readonly serviceProvider: () => IDocumentService | undefined,
358
364
  public readonly containerDirty: () => boolean,
359
- private client: IClient,
365
+ private readonly client: IClient,
360
366
  reconnectAllowed: boolean,
361
367
  private readonly logger: ITelemetryLoggerExt,
362
368
  private readonly props: IConnectionManagerFactoryArgs,
@@ -390,8 +396,12 @@ export class ConnectionManager implements IConnectionManager {
390
396
 
391
397
  this._outbound.clear();
392
398
 
393
- const disconnectReason = "Closing DeltaManager";
399
+ const disconnectReason: IConnectionStateChangeReason = {
400
+ text: "Closing DeltaManager",
401
+ error,
402
+ };
394
403
 
404
+ const oldReadonlyValue = this.readonly;
395
405
  // This raises "disconnect" event if we have active connection.
396
406
  this.disconnectFromDeltaStream(disconnectReason);
397
407
 
@@ -399,7 +409,7 @@ export class ConnectionManager implements IConnectionManager {
399
409
  // Notify everyone we are in read-only state.
400
410
  // Useful for data stores in case we hit some critical error,
401
411
  // to switch to a mode where user edits are not accepted
402
- this.set_readonlyPermissions(true);
412
+ this.set_readonlyPermissions(true, oldReadonlyValue);
403
413
  }
404
414
  }
405
415
 
@@ -407,7 +417,7 @@ export class ConnectionManager implements IConnectionManager {
407
417
  * Enables or disables automatic reconnecting.
408
418
  * Will throw an error if reconnectMode set to Never.
409
419
  */
410
- public setAutoReconnect(mode: ReconnectMode): void {
420
+ public setAutoReconnect(mode: ReconnectMode, reason: IConnectionStateChangeReason): void {
411
421
  assert(
412
422
  mode !== ReconnectMode.Never && this._reconnectMode !== ReconnectMode.Never,
413
423
  0x278 /* "API is not supported for non-connecting or closed container" */,
@@ -417,7 +427,7 @@ export class ConnectionManager implements IConnectionManager {
417
427
 
418
428
  if (mode !== ReconnectMode.Enabled) {
419
429
  // immediately disconnect - do not rely on service eventually dropping connection.
420
- this.disconnectFromDeltaStream("setAutoReconnect");
430
+ this.disconnectFromDeltaStream(reason);
421
431
  }
422
432
  }
423
433
 
@@ -464,32 +474,37 @@ export class ConnectionManager implements IConnectionManager {
464
474
  this.logger.sendErrorEvent({ eventName: "ForceReadonlyPendingChanged" });
465
475
  }
466
476
 
467
- reconnect = this.disconnectFromDeltaStream("Force readonly");
477
+ reconnect = this.disconnectFromDeltaStream({ text: "Force readonly" });
468
478
  }
469
479
  this.props.readonlyChangeHandler(this.readonly);
470
480
  if (reconnect) {
471
481
  // reconnect if we disconnected from before.
472
- this.triggerConnect("Force Readonly", "read");
482
+ this.triggerConnect({ text: "Force Readonly" }, "read");
473
483
  }
474
484
  }
475
485
  }
476
486
 
477
- private set_readonlyPermissions(readonly: boolean) {
478
- const oldValue = this.readonly;
479
- this._readonlyPermissions = readonly;
480
- if (oldValue !== this.readonly) {
487
+ private set_readonlyPermissions(
488
+ newReadonlyValue: boolean,
489
+ oldReadonlyValue: boolean | undefined,
490
+ ) {
491
+ this._readonlyPermissions = newReadonlyValue;
492
+ if (oldReadonlyValue !== this.readonly) {
481
493
  this.props.readonlyChangeHandler(this.readonly);
482
494
  }
483
495
  }
484
496
 
485
- public connect(reason: string, connectionMode?: ConnectionMode) {
486
- this.connectCore(reason, connectionMode).catch((error) => {
487
- const normalizedError = normalizeError(error, { props: fatalConnectErrorProp });
497
+ public connect(reason: IConnectionStateChangeReason, connectionMode?: ConnectionMode) {
498
+ this.connectCore(reason, connectionMode).catch((e) => {
499
+ const normalizedError = normalizeError(e, { props: fatalConnectErrorProp });
488
500
  this.props.closeHandler(normalizedError);
489
501
  });
490
502
  }
491
503
 
492
- private async connectCore(reason: string, connectionMode?: ConnectionMode): Promise<void> {
504
+ private async connectCore(
505
+ reason: IConnectionStateChangeReason,
506
+ connectionMode?: ConnectionMode,
507
+ ): Promise<void> {
493
508
  assert(!this._disposed, 0x26a /* "not closed" */);
494
509
 
495
510
  if (this.connection !== undefined) {
@@ -553,7 +568,7 @@ export class ConnectionManager implements IConnectionManager {
553
568
  this.logger.sendTelemetryEvent({
554
569
  eventName: "ConnectionAttemptCancelled",
555
570
  attempts: connectRepeatCount,
556
- duration: TelemetryLogger.formatTick(performance.now() - connectStartTime),
571
+ duration: formatTick(performance.now() - connectStartTime),
557
572
  connectionEstablished: false,
558
573
  });
559
574
  return;
@@ -593,7 +608,7 @@ export class ConnectionManager implements IConnectionManager {
593
608
  attempts: connectRepeatCount,
594
609
  delay: delayMs, // milliseconds
595
610
  eventName: "DeltaConnectionFailureToConnect",
596
- duration: TelemetryLogger.formatTick(performance.now() - connectStartTime),
611
+ duration: formatTick(performance.now() - connectStartTime),
597
612
  },
598
613
  origError,
599
614
  );
@@ -613,7 +628,7 @@ export class ConnectionManager implements IConnectionManager {
613
628
  // We skip this delay if we're confident we're offline, because we probably just need to wait to come back online.
614
629
  await new Promise<void>((resolve) => {
615
630
  setTimeout(resolve, delayMs);
616
- delayMs = Math.min(delayMs * 2, MaxReconnectDelayInMs);
631
+ delayMs = Math.min(delayMs * 2, calculateMaxWaitTime(origError));
617
632
  });
618
633
  }
619
634
 
@@ -639,7 +654,7 @@ export class ConnectionManager implements IConnectionManager {
639
654
  {
640
655
  eventName: "MultipleDeltaConnectionFailures",
641
656
  attempts: connectRepeatCount,
642
- duration: TelemetryLogger.formatTick(performance.now() - connectStartTime),
657
+ duration: formatTick(performance.now() - connectStartTime),
643
658
  },
644
659
  lastError,
645
660
  );
@@ -651,7 +666,7 @@ export class ConnectionManager implements IConnectionManager {
651
666
  this.logger.sendTelemetryEvent({
652
667
  eventName: "ConnectionAttemptCancelled",
653
668
  attempts: connectRepeatCount,
654
- duration: TelemetryLogger.formatTick(performance.now() - connectStartTime),
669
+ duration: formatTick(performance.now() - connectStartTime),
655
670
  connectionEstablished: true,
656
671
  });
657
672
  return;
@@ -665,7 +680,7 @@ export class ConnectionManager implements IConnectionManager {
665
680
  * And report the error if it escapes for any reason.
666
681
  * @param args - The connection arguments
667
682
  */
668
- private triggerConnect(reason: string, connectionMode: ConnectionMode) {
683
+ private triggerConnect(reason: IConnectionStateChangeReason, connectionMode: ConnectionMode) {
669
684
  // reconnect() includes async awaits, and that causes potential race conditions
670
685
  // where we might already have a connection. If it were to happen, it's possible that we will connect
671
686
  // with different mode to `connectionMode`. Glancing through the caller chains, it looks like code should be
@@ -685,7 +700,7 @@ export class ConnectionManager implements IConnectionManager {
685
700
  * @param error - Error causing the disconnect if any.
686
701
  * @returns A boolean that indicates if there was an existing connection (or pending connection) to disconnect
687
702
  */
688
- private disconnectFromDeltaStream(reason: string, error?: IAnyDriverError): boolean {
703
+ private disconnectFromDeltaStream(reason: IConnectionStateChangeReason): boolean {
689
704
  this.pendingReconnect = false;
690
705
 
691
706
  if (this.connection === undefined) {
@@ -718,7 +733,7 @@ export class ConnectionManager implements IConnectionManager {
718
733
  this._outbound.clear();
719
734
  connection.dispose();
720
735
 
721
- this.props.disconnectHandler(reason, error);
736
+ this.props.disconnectHandler(reason);
722
737
 
723
738
  this._connectionVerboseProps = {};
724
739
 
@@ -728,7 +743,7 @@ export class ConnectionManager implements IConnectionManager {
728
743
  /**
729
744
  * Cancel in-progress connection attempt.
730
745
  */
731
- private cancelConnection(reason: string) {
746
+ private cancelConnection(reason: IConnectionStateChangeReason) {
732
747
  assert(
733
748
  this.pendingConnection !== undefined,
734
749
  0x345 /* this.pendingConnection is undefined when trying to cancel */,
@@ -736,7 +751,10 @@ export class ConnectionManager implements IConnectionManager {
736
751
  this.pendingConnection.abort();
737
752
  this.pendingConnection = undefined;
738
753
  this.logger.sendTelemetryEvent({ eventName: "ConnectionCancelReceived" });
739
- this.props.cancelConnectionHandler(`Cancel Pending Connection due to ${reason}`);
754
+ this.props.cancelConnectionHandler({
755
+ text: `Cancel Pending Connection due to ${reason.text}`,
756
+ error: reason.error,
757
+ });
740
758
  }
741
759
 
742
760
  /**
@@ -747,7 +765,7 @@ export class ConnectionManager implements IConnectionManager {
747
765
  private setupNewSuccessfulConnection(
748
766
  connection: IDocumentDeltaConnection,
749
767
  requestedMode: ConnectionMode,
750
- reason: string,
768
+ reason: IConnectionStateChangeReason,
751
769
  ) {
752
770
  // Old connection should have been cleaned up before establishing a new one
753
771
  assert(
@@ -761,6 +779,7 @@ export class ConnectionManager implements IConnectionManager {
761
779
 
762
780
  this.pendingConnection = undefined;
763
781
 
782
+ const oldReadonlyValue = this.readonly;
764
783
  this.connection = connection;
765
784
 
766
785
  // Does information in scopes & mode matches?
@@ -786,11 +805,11 @@ export class ConnectionManager implements IConnectionManager {
786
805
  0x0e8 /* "readonly perf with write connection" */,
787
806
  );
788
807
 
789
- this.set_readonlyPermissions(readonly);
808
+ this.set_readonlyPermissions(readonly, oldReadonlyValue);
790
809
 
791
810
  if (this._disposed) {
792
811
  // Raise proper events, Log telemetry event and close connection.
793
- this.disconnectFromDeltaStream("ConnectionManager already closed");
812
+ this.disconnectFromDeltaStream({ text: "ConnectionManager already closed" });
794
813
  return;
795
814
  }
796
815
 
@@ -895,7 +914,9 @@ export class ConnectionManager implements IConnectionManager {
895
914
  * @returns A promise that resolves when the connection is reestablished or we stop trying
896
915
  */
897
916
  private reconnectOnError(requestedMode: ConnectionMode, error: IAnyDriverError) {
898
- this.reconnect(requestedMode, error.message, error).catch(this.props.closeHandler);
917
+ this.reconnect(requestedMode, { text: error.message, error }).catch(
918
+ this.props.closeHandler,
919
+ );
899
920
  }
900
921
 
901
922
  /**
@@ -907,26 +928,25 @@ export class ConnectionManager implements IConnectionManager {
907
928
  */
908
929
  private async reconnect(
909
930
  requestedMode: ConnectionMode,
910
- disconnectMessage: string,
911
- error?: IAnyDriverError,
931
+ reason: IConnectionStateChangeReason<IAnyDriverError>,
912
932
  ) {
913
933
  // We quite often get protocol errors before / after observing nack/disconnect
914
934
  // we do not want to run through same sequence twice.
915
935
  // If we're already disconnected/disconnecting it's not appropriate to call this again.
916
936
  assert(this.connection !== undefined, 0x0eb /* "Missing connection for reconnect" */);
917
937
 
918
- this.disconnectFromDeltaStream(disconnectMessage, error);
938
+ this.disconnectFromDeltaStream(reason);
919
939
 
920
940
  // We will always trigger reconnect, even if canRetry is false.
921
941
  // Any truly fatal error state will result in container close upon attempted reconnect,
922
942
  // which is a preferable to closing abruptly when a live connection fails.
923
- if (error !== undefined && !error.canRetry) {
943
+ if (reason.error?.canRetry === false) {
924
944
  this.logger.sendTelemetryEvent(
925
945
  {
926
946
  eventName: "reconnectingDespiteFatalError",
927
947
  reconnectMode: this.reconnectMode,
928
948
  },
929
- error,
949
+ reason.error,
930
950
  );
931
951
  }
932
952
 
@@ -943,9 +963,9 @@ export class ConnectionManager implements IConnectionManager {
943
963
  }
944
964
 
945
965
  // If the error tells us to wait before retrying, then do so.
946
- const delayMs = getRetryDelayFromError(error);
947
- if (error !== undefined && delayMs !== undefined) {
948
- this.props.reconnectionDelayHandler(delayMs, error);
966
+ const delayMs = getRetryDelayFromError(reason.error);
967
+ if (reason.error !== undefined && delayMs !== undefined) {
968
+ this.props.reconnectionDelayHandler(delayMs, reason.error);
949
969
  await new Promise<void>((resolve) => {
950
970
  setTimeout(resolve, delayMs);
951
971
  });
@@ -957,9 +977,13 @@ export class ConnectionManager implements IConnectionManager {
957
977
  await waitForOnline();
958
978
 
959
979
  this.triggerConnect(
960
- error !== undefined
961
- ? "Reconnecting due to Error"
962
- : `Reconnecting due to: ${disconnectMessage}`,
980
+ {
981
+ text:
982
+ reason.error !== undefined
983
+ ? "Reconnecting due to Error"
984
+ : `Reconnecting due to: ${reason.text}`,
985
+ error: reason.error,
986
+ },
963
987
  requestedMode,
964
988
  );
965
989
  }
@@ -1030,7 +1054,7 @@ export class ConnectionManager implements IConnectionManager {
1030
1054
  // still valid?
1031
1055
  await this.reconnect(
1032
1056
  "write", // connectionMode
1033
- "Switch to write", // message
1057
+ { text: "Switch to write" }, // message
1034
1058
  );
1035
1059
  }
1036
1060
  })
@@ -1084,7 +1108,7 @@ export class ConnectionManager implements IConnectionManager {
1084
1108
  // Note - this may close container!
1085
1109
  this.reconnect(
1086
1110
  "read", // connectionMode
1087
- "Switch to read", // message
1111
+ { text: "Switch to read" }, // message
1088
1112
  ).catch((error) => {
1089
1113
  this.logger.sendErrorEvent({ eventName: "SwitchToReadConnection" }, error);
1090
1114
  });
@@ -4,17 +4,18 @@
4
4
  */
5
5
 
6
6
  import { ITelemetryProperties, TelemetryEventCategory } from "@fluidframework/core-interfaces";
7
- import { assert, Timer } from "@fluidframework/common-utils";
8
- import { IConnectionDetailsInternal, IDeltaManager } from "@fluidframework/container-definitions";
9
- import { IAnyDriverError } from "@fluidframework/driver-definitions";
7
+ import { assert, Timer } from "@fluidframework/core-utils";
8
+ import { IDeltaManager } from "@fluidframework/container-definitions";
10
9
  import { ISequencedClient, IClient } from "@fluidframework/protocol-definitions";
11
10
  import {
12
11
  ITelemetryLoggerExt,
13
12
  PerformanceEvent,
14
13
  loggerToMonitoringContext,
15
14
  } from "@fluidframework/telemetry-utils";
16
- import { ConnectionState } from "./connectionState";
15
+ import { IAnyDriverError } from "@fluidframework/driver-definitions";
17
16
  import { CatchUpMonitor, ICatchUpMonitor } from "./catchUpMonitor";
17
+ import { ConnectionState } from "./connectionState";
18
+ import { IConnectionDetailsInternal, IConnectionStateChangeReason } from "./contracts";
18
19
  import { IProtocolHandler } from "./protocol";
19
20
 
20
21
  // Based on recent data, it looks like majority of cases where we get stuck are due to really slow or
@@ -32,8 +33,7 @@ export interface IConnectionStateHandlerInputs {
32
33
  connectionStateChanged: (
33
34
  value: ConnectionState,
34
35
  oldState: ConnectionState,
35
- reason?: string | undefined,
36
- error?: IAnyDriverError,
36
+ reason?: IConnectionStateChangeReason,
37
37
  ) => void;
38
38
  /** Whether to expect the client to join in write mode on next connection */
39
39
  shouldClientJoinWrite: () => boolean;
@@ -60,14 +60,14 @@ export interface IConnectionStateHandler {
60
60
  dispose(): void;
61
61
  initProtocol(protocol: IProtocolHandler): void;
62
62
  receivedConnectEvent(details: IConnectionDetailsInternal): void;
63
- receivedDisconnectEvent(reason: string, error?: IAnyDriverError): void;
64
- establishingConnection(reason: string): void;
63
+ receivedDisconnectEvent(reason: IConnectionStateChangeReason): void;
64
+ establishingConnection(reason: IConnectionStateChangeReason): void;
65
65
  /**
66
66
  * Switches state to disconnected when we are still establishing connection during container.load(),
67
67
  * container connect() or reconnect and the container gets closed or disposed or disconnect happens.
68
68
  * @param reason - reason for cancelling the connection.
69
69
  */
70
- cancelEstablishingConnection(reason: string): void;
70
+ cancelEstablishingConnection(reason: IConnectionStateChangeReason): void;
71
71
  }
72
72
 
73
73
  export function createConnectionStateHandler(
@@ -149,15 +149,15 @@ class ConnectionStateHandlerPassThrough
149
149
  public initProtocol(protocol: IProtocolHandler) {
150
150
  return this.pimpl.initProtocol(protocol);
151
151
  }
152
- public receivedDisconnectEvent(reason: string, error?: IAnyDriverError) {
153
- return this.pimpl.receivedDisconnectEvent(reason, error);
152
+ public receivedDisconnectEvent(reason: IConnectionStateChangeReason<IAnyDriverError>) {
153
+ return this.pimpl.receivedDisconnectEvent(reason);
154
154
  }
155
155
 
156
- public establishingConnection(reason: string) {
156
+ public establishingConnection(reason: IConnectionStateChangeReason) {
157
157
  return this.pimpl.establishingConnection(reason);
158
158
  }
159
159
 
160
- public cancelEstablishingConnection(reason: string) {
160
+ public cancelEstablishingConnection(reason: IConnectionStateChangeReason) {
161
161
  return this.pimpl.cancelEstablishingConnection(reason);
162
162
  }
163
163
 
@@ -175,10 +175,9 @@ class ConnectionStateHandlerPassThrough
175
175
  public connectionStateChanged(
176
176
  value: ConnectionState,
177
177
  oldState: ConnectionState,
178
- reason?: string | undefined,
179
- error?: IAnyDriverError,
178
+ reason?: IConnectionStateChangeReason,
180
179
  ) {
181
- return this.inputs.connectionStateChanged(value, oldState, reason, error);
180
+ return this.inputs.connectionStateChanged(value, oldState, reason);
182
181
  }
183
182
  public shouldClientJoinWrite() {
184
183
  return this.inputs.shouldClientJoinWrite();
@@ -222,8 +221,7 @@ class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
222
221
  public connectionStateChanged(
223
222
  value: ConnectionState,
224
223
  oldState: ConnectionState,
225
- reason?: string | undefined,
226
- error?: IAnyDriverError,
224
+ reason?: IConnectionStateChangeReason<IAnyDriverError>,
227
225
  ) {
228
226
  switch (value) {
229
227
  case ConnectionState.Connected:
@@ -267,7 +265,7 @@ class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
267
265
  default:
268
266
  }
269
267
  this._connectionState = value;
270
- this.inputs.connectionStateChanged(value, oldState, reason, error);
268
+ this.inputs.connectionStateChanged(value, oldState, reason);
271
269
  }
272
270
 
273
271
  private readonly transitionToConnectedState = () => {
@@ -276,11 +274,9 @@ class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
276
274
  assert(state === ConnectionState.Connected, 0x3e5 /* invariant broken */);
277
275
  assert(this._connectionState === ConnectionState.CatchingUp, 0x3e6 /* invariant broken */);
278
276
  this._connectionState = ConnectionState.Connected;
279
- this.inputs.connectionStateChanged(
280
- ConnectionState.Connected,
281
- ConnectionState.CatchingUp,
282
- "caught up",
283
- );
277
+ this.inputs.connectionStateChanged(ConnectionState.Connected, ConnectionState.CatchingUp, {
278
+ text: "caught up",
279
+ });
284
280
  };
285
281
  }
286
282
 
@@ -494,12 +490,12 @@ class ConnectionStateHandler implements IConnectionStateHandler {
494
490
  }
495
491
  }
496
492
 
497
- public receivedDisconnectEvent(reason: string, error?: IAnyDriverError) {
493
+ public receivedDisconnectEvent(reason: IConnectionStateChangeReason<IAnyDriverError>) {
498
494
  this.connection = undefined;
499
- this.setConnectionState(ConnectionState.Disconnected, reason, error);
495
+ this.setConnectionState(ConnectionState.Disconnected, reason);
500
496
  }
501
497
 
502
- public cancelEstablishingConnection(reason: string) {
498
+ public cancelEstablishingConnection(reason: IConnectionStateChangeReason) {
503
499
  assert(
504
500
  this._connectionState === ConnectionState.EstablishingConnection,
505
501
  0x6d3 /* Connection state should be EstablishingConnection */,
@@ -510,14 +506,13 @@ class ConnectionStateHandler implements IConnectionStateHandler {
510
506
  this.handler.connectionStateChanged(ConnectionState.Disconnected, oldState, reason);
511
507
  }
512
508
 
513
- public establishingConnection(reason: string) {
509
+ public establishingConnection(reason: IConnectionStateChangeReason) {
514
510
  const oldState = this._connectionState;
515
511
  this._connectionState = ConnectionState.EstablishingConnection;
516
- this.handler.connectionStateChanged(
517
- ConnectionState.EstablishingConnection,
518
- oldState,
519
- `Establishing Connection due to ${reason}`,
520
- );
512
+ this.handler.connectionStateChanged(ConnectionState.EstablishingConnection, oldState, {
513
+ text: `Establishing Connection due to ${reason.text}`,
514
+ error: reason.error,
515
+ });
521
516
  }
522
517
 
523
518
  private shouldWaitForJoinSignal() {
@@ -582,14 +577,12 @@ class ConnectionStateHandler implements IConnectionStateHandler {
582
577
 
583
578
  private setConnectionState(
584
579
  value: ConnectionState.Disconnected,
585
- reason: string,
586
- error?: IAnyDriverError,
580
+ reason: IConnectionStateChangeReason,
587
581
  ): void;
588
582
  private setConnectionState(value: ConnectionState.Connected): void;
589
583
  private setConnectionState(
590
584
  value: ConnectionState.Disconnected | ConnectionState.Connected,
591
- reason?: string,
592
- error?: IAnyDriverError,
585
+ reason?: IConnectionStateChangeReason,
593
586
  ): void {
594
587
  if (this.connectionState === value) {
595
588
  // Already in the desired state - exit early
@@ -650,7 +643,7 @@ class ConnectionStateHandler implements IConnectionStateHandler {
650
643
  }
651
644
 
652
645
  // Report transition before we propagate event across layers
653
- this.handler.connectionStateChanged(this._connectionState, oldState, reason, error);
646
+ this.handler.connectionStateChanged(this._connectionState, oldState, reason);
654
647
  }
655
648
 
656
649
  // Helper method to switch between quorum and audience.