@fluidframework/container-loader 2.0.0-internal.5.4.0 → 2.0.0-internal.6.1.0

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 (102) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/dist/connectionManager.d.ts +4 -4
  3. package/dist/connectionManager.d.ts.map +1 -1
  4. package/dist/connectionManager.js +57 -49
  5. package/dist/connectionManager.js.map +1 -1
  6. package/dist/connectionStateHandler.d.ts +15 -14
  7. package/dist/connectionStateHandler.d.ts.map +1 -1
  8. package/dist/connectionStateHandler.js +26 -28
  9. package/dist/connectionStateHandler.js.map +1 -1
  10. package/dist/container.d.ts +10 -5
  11. package/dist/container.d.ts.map +1 -1
  12. package/dist/container.js +183 -134
  13. package/dist/container.js.map +1 -1
  14. package/dist/containerContext.d.ts +2 -12
  15. package/dist/containerContext.d.ts.map +1 -1
  16. package/dist/containerContext.js +1 -20
  17. package/dist/containerContext.js.map +1 -1
  18. package/dist/containerStorageAdapter.js +3 -5
  19. package/dist/containerStorageAdapter.js.map +1 -1
  20. package/dist/contracts.d.ts +20 -7
  21. package/dist/contracts.d.ts.map +1 -1
  22. package/dist/contracts.js +3 -3
  23. package/dist/contracts.js.map +1 -1
  24. package/dist/debugLogger.js +2 -3
  25. package/dist/debugLogger.js.map +1 -1
  26. package/dist/deltaManager.d.ts +19 -6
  27. package/dist/deltaManager.d.ts.map +1 -1
  28. package/dist/deltaManager.js +67 -28
  29. package/dist/deltaManager.js.map +1 -1
  30. package/dist/deltaQueue.js +1 -2
  31. package/dist/deltaQueue.js.map +1 -1
  32. package/dist/loader.d.ts +12 -0
  33. package/dist/loader.d.ts.map +1 -1
  34. package/dist/loader.js +57 -42
  35. package/dist/loader.js.map +1 -1
  36. package/dist/packageVersion.d.ts +1 -1
  37. package/dist/packageVersion.js +1 -1
  38. package/dist/packageVersion.js.map +1 -1
  39. package/dist/protocol.d.ts +4 -2
  40. package/dist/protocol.d.ts.map +1 -1
  41. package/dist/protocol.js +25 -4
  42. package/dist/protocol.js.map +1 -1
  43. package/dist/utils.d.ts +8 -1
  44. package/dist/utils.d.ts.map +1 -1
  45. package/dist/utils.js +24 -6
  46. package/dist/utils.js.map +1 -1
  47. package/lib/connectionManager.d.ts +4 -4
  48. package/lib/connectionManager.d.ts.map +1 -1
  49. package/lib/connectionManager.js +58 -50
  50. package/lib/connectionManager.js.map +1 -1
  51. package/lib/connectionStateHandler.d.ts +15 -14
  52. package/lib/connectionStateHandler.d.ts.map +1 -1
  53. package/lib/connectionStateHandler.js +26 -28
  54. package/lib/connectionStateHandler.js.map +1 -1
  55. package/lib/container.d.ts +10 -5
  56. package/lib/container.d.ts.map +1 -1
  57. package/lib/container.js +182 -133
  58. package/lib/container.js.map +1 -1
  59. package/lib/containerContext.d.ts +2 -12
  60. package/lib/containerContext.d.ts.map +1 -1
  61. package/lib/containerContext.js +1 -20
  62. package/lib/containerContext.js.map +1 -1
  63. package/lib/containerStorageAdapter.js +3 -5
  64. package/lib/containerStorageAdapter.js.map +1 -1
  65. package/lib/contracts.d.ts +20 -7
  66. package/lib/contracts.d.ts.map +1 -1
  67. package/lib/contracts.js +3 -3
  68. package/lib/contracts.js.map +1 -1
  69. package/lib/debugLogger.js +2 -3
  70. package/lib/debugLogger.js.map +1 -1
  71. package/lib/deltaManager.d.ts +19 -6
  72. package/lib/deltaManager.d.ts.map +1 -1
  73. package/lib/deltaManager.js +67 -28
  74. package/lib/deltaManager.js.map +1 -1
  75. package/lib/deltaQueue.js +1 -2
  76. package/lib/deltaQueue.js.map +1 -1
  77. package/lib/loader.d.ts +12 -0
  78. package/lib/loader.d.ts.map +1 -1
  79. package/lib/loader.js +57 -42
  80. package/lib/loader.js.map +1 -1
  81. package/lib/packageVersion.d.ts +1 -1
  82. package/lib/packageVersion.js +1 -1
  83. package/lib/packageVersion.js.map +1 -1
  84. package/lib/protocol.d.ts +4 -2
  85. package/lib/protocol.d.ts.map +1 -1
  86. package/lib/protocol.js +25 -4
  87. package/lib/protocol.js.map +1 -1
  88. package/lib/utils.d.ts +8 -1
  89. package/lib/utils.d.ts.map +1 -1
  90. package/lib/utils.js +22 -5
  91. package/lib/utils.js.map +1 -1
  92. package/package.json +15 -19
  93. package/src/connectionManager.ts +53 -34
  94. package/src/connectionStateHandler.ts +30 -37
  95. package/src/container.ts +156 -76
  96. package/src/containerContext.ts +0 -24
  97. package/src/contracts.ts +27 -10
  98. package/src/deltaManager.ts +41 -18
  99. package/src/loader.ts +37 -23
  100. package/src/packageVersion.ts +1 -1
  101. package/src/protocol.ts +33 -2
  102. package/src/utils.ts +29 -0
package/src/container.ts CHANGED
@@ -23,46 +23,42 @@ import {
23
23
  FluidObject,
24
24
  } from "@fluidframework/core-interfaces";
25
25
  import {
26
+ AttachState,
27
+ ContainerWarning,
26
28
  IAudience,
27
- IConnectionDetailsInternal,
29
+ IBatchMessage,
30
+ ICodeDetailsLoader,
28
31
  IContainer,
29
32
  IContainerEvents,
30
- IDeltaManager,
31
- ICriticalContainerError,
32
- ContainerWarning,
33
- AttachState,
34
- IThrottlingWarning,
35
- ReadOnlyInfo,
36
33
  IContainerLoadMode,
34
+ ICriticalContainerError,
35
+ IDeltaManager,
37
36
  IFluidCodeDetails,
38
- isFluidCodeDetails,
39
- IBatchMessage,
40
- ICodeDetailsLoader,
41
37
  IHostLoader,
42
38
  IFluidModuleWithDetails,
43
39
  IProvideRuntimeFactory,
44
40
  IProvideFluidCodeDetailsComparer,
45
41
  IFluidCodeDetailsComparer,
46
42
  IRuntime,
43
+ ReadOnlyInfo,
44
+ isFluidCodeDetails,
47
45
  } from "@fluidframework/container-definitions";
48
46
  import { GenericError, UsageError } from "@fluidframework/container-utils";
49
47
  import {
50
- IAnyDriverError,
51
48
  IDocumentService,
52
49
  IDocumentServiceFactory,
53
50
  IDocumentStorageService,
54
51
  IResolvedUrl,
52
+ IThrottlingWarning,
55
53
  IUrlResolver,
56
54
  } from "@fluidframework/driver-definitions";
57
55
  import {
58
56
  readAndParse,
59
57
  OnlineStatus,
60
58
  isOnline,
61
- combineAppAndProtocolSummary,
62
59
  runWithRetry,
63
60
  isCombinedAppAndProtocolSummary,
64
61
  MessageType2,
65
- canBeCoalescedByService,
66
62
  } from "@fluidframework/driver-utils";
67
63
  import { IQuorumSnapshot } from "@fluidframework/protocol-base";
68
64
  import {
@@ -99,7 +95,13 @@ import {
99
95
  } from "@fluidframework/telemetry-utils";
100
96
  import { Audience } from "./audience";
101
97
  import { ContainerContext } from "./containerContext";
102
- import { ReconnectMode, IConnectionManagerFactoryArgs, getPackageName } from "./contracts";
98
+ import {
99
+ ReconnectMode,
100
+ IConnectionManagerFactoryArgs,
101
+ getPackageName,
102
+ IConnectionDetailsInternal,
103
+ IConnectionStateChangeReason,
104
+ } from "./contracts";
103
105
  import { DeltaManager, IConnectionArgs } from "./deltaManager";
104
106
  import { IDetachedBlobStorage, ILoaderOptions, RelativeLoader } from "./loader";
105
107
  import { pkgVersion } from "./packageVersion";
@@ -110,7 +112,11 @@ import {
110
112
  ISerializableBlobContents,
111
113
  } from "./containerStorageAdapter";
112
114
  import { IConnectionStateHandler, createConnectionStateHandler } from "./connectionStateHandler";
113
- import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
115
+ import {
116
+ combineAppAndProtocolSummary,
117
+ getProtocolSnapshotTree,
118
+ getSnapshotTreeFromSerializedContainer,
119
+ } from "./utils";
114
120
  import { initQuorumValuesFromCodeDetails } from "./quorum";
115
121
  import { NoopHeuristic } from "./noopHeuristic";
116
122
  import { ConnectionManager } from "./connectionManager";
@@ -151,6 +157,11 @@ export interface IContainerLoadProps {
151
157
  * The pending state serialized from a pervious container instance
152
158
  */
153
159
  readonly pendingLocalState?: IPendingContainerState;
160
+
161
+ /**
162
+ * Load the container to at least this sequence number.
163
+ */
164
+ readonly loadToSequenceNumber?: number;
154
165
  }
155
166
 
156
167
  /**
@@ -369,7 +380,8 @@ export class Container
369
380
  loadProps: IContainerLoadProps,
370
381
  createProps: IContainerCreateProps,
371
382
  ): Promise<Container> {
372
- const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
383
+ const { version, pendingLocalState, loadMode, resolvedUrl, loadToSequenceNumber } =
384
+ loadProps;
373
385
 
374
386
  const container = new Container(createProps, loadProps);
375
387
 
@@ -398,7 +410,7 @@ export class Container
398
410
  container.on("closed", onClosed);
399
411
 
400
412
  container
401
- .load(version, mode, resolvedUrl, pendingLocalState)
413
+ .load(version, mode, resolvedUrl, pendingLocalState, loadToSequenceNumber)
402
414
  .finally(() => {
403
415
  container.removeListener("closed", onClosed);
404
416
  })
@@ -748,7 +760,19 @@ export class Container
748
760
  this.scope = scope;
749
761
  this.detachedBlobStorage = detachedBlobStorage;
750
762
  this.protocolHandlerBuilder =
751
- protocolHandlerBuilder ?? ((...args) => new ProtocolHandler(...args, new Audience()));
763
+ protocolHandlerBuilder ??
764
+ ((
765
+ attributes: IDocumentAttributes,
766
+ quorumSnapshot: IQuorumSnapshot,
767
+ sendProposal: (key: string, value: any) => number,
768
+ ) =>
769
+ new ProtocolHandler(
770
+ attributes,
771
+ quorumSnapshot,
772
+ sendProposal,
773
+ new Audience(),
774
+ (clientId: string) => this.clientsWhoShouldHaveLeft.has(clientId),
775
+ ));
752
776
 
753
777
  // Note that we capture the createProps here so we can replicate the creation call when we want to clone.
754
778
  this.clone = async (
@@ -814,11 +838,11 @@ export class Container
814
838
  this.connectionStateHandler = createConnectionStateHandler(
815
839
  {
816
840
  logger: this.mc.logger,
817
- connectionStateChanged: (value, oldState, reason, error) => {
841
+ connectionStateChanged: (value, oldState, reason) => {
818
842
  if (value === ConnectionState.Connected) {
819
843
  this._clientId = this.connectionStateHandler.pendingClientId;
820
844
  }
821
- this.logConnectionStateChangeTelemetry(value, oldState, reason, error);
845
+ this.logConnectionStateChangeTelemetry(value, oldState, reason);
822
846
  if (this._lifecycleState === "loaded") {
823
847
  this.propagateConnectionState(
824
848
  false /* initial transition */,
@@ -860,8 +884,9 @@ export class Container
860
884
  // Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
861
885
  // to call this.applyForConnectedState("addMemberEvent") for "read" connections)
862
886
  if (mode === "read") {
863
- this.disconnect();
864
- this.connect();
887
+ const reason = { text: "NoJoinSignal" };
888
+ this.disconnectInternal(reason);
889
+ this.connectInternal({ reason, fetchOpsFromStorage: false });
865
890
  }
866
891
  },
867
892
  clientShouldHaveLeft: (clientId: string) => {
@@ -1054,16 +1079,21 @@ export class Container
1054
1079
  }
1055
1080
  }
1056
1081
 
1057
- public closeAndGetPendingLocalState(): string {
1082
+ public async closeAndGetPendingLocalState(): Promise<string> {
1058
1083
  // runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
1059
1084
  // container at the same time we get pending state, otherwise this container could reconnect and resubmit with
1060
1085
  // a new clientId and a future container using stale pending state without the new clientId would resubmit them
1061
- const pendingState = this.getPendingLocalState();
1086
+ this.disconnectInternal({ text: "closeAndGetPendingLocalState" }); // TODO https://dev.azure.com/fluidframework/internal/_workitems/edit/5127
1087
+ const pendingState = await this.getPendingLocalStateCore({ notifyImminentClosure: true });
1062
1088
  this.close();
1063
1089
  return pendingState;
1064
1090
  }
1065
1091
 
1066
- public getPendingLocalState(): string {
1092
+ public async getPendingLocalState(): Promise<string> {
1093
+ return this.getPendingLocalStateCore({ notifyImminentClosure: false });
1094
+ }
1095
+
1096
+ private async getPendingLocalStateCore(props: { notifyImminentClosure: boolean }) {
1067
1097
  if (!this.offlineLoadEnabled) {
1068
1098
  throw new UsageError("Can't get pending local state unless offline load is enabled");
1069
1099
  }
@@ -1082,8 +1112,9 @@ export class Container
1082
1112
  );
1083
1113
  assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
1084
1114
  assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
1115
+ const pendingRuntimeState = await this.runtime.getPendingLocalState(props);
1085
1116
  const pendingState: IPendingContainerState = {
1086
- pendingRuntimeState: this.runtime.getPendingLocalState(),
1117
+ pendingRuntimeState,
1087
1118
  baseSnapshot: this.baseSnapshot,
1088
1119
  snapshotBlobs: this.baseSnapshotBlobs,
1089
1120
  savedOps: this.savedOps,
@@ -1248,7 +1279,7 @@ export class Container
1248
1279
  if (!this.closed) {
1249
1280
  this.resumeInternal({
1250
1281
  fetchOpsFromStorage: false,
1251
- reason: "createDetached",
1282
+ reason: { text: "createDetached" },
1252
1283
  });
1253
1284
  }
1254
1285
  } catch (error) {
@@ -1272,7 +1303,7 @@ export class Container
1272
1303
  );
1273
1304
  }
1274
1305
 
1275
- private setAutoReconnectInternal(mode: ReconnectMode) {
1306
+ private setAutoReconnectInternal(mode: ReconnectMode, reason: IConnectionStateChangeReason) {
1276
1307
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
1277
1308
 
1278
1309
  if (currentMode === mode) {
@@ -1291,7 +1322,7 @@ export class Container
1291
1322
  duration,
1292
1323
  });
1293
1324
 
1294
- this._deltaManager.connectionManager.setAutoReconnect(mode);
1325
+ this._deltaManager.connectionManager.setAutoReconnect(mode, reason);
1295
1326
  }
1296
1327
 
1297
1328
  public connect() {
@@ -1303,7 +1334,10 @@ export class Container
1303
1334
  // Note: no need to fetch ops as we do it preemptively as part of DeltaManager.attachOpHandler().
1304
1335
  // If there is gap, we will learn about it once connected, but the gap should be small (if any),
1305
1336
  // assuming that connect() is called quickly after initial container boot.
1306
- this.connectInternal({ reason: "DocumentConnect", fetchOpsFromStorage: false });
1337
+ this.connectInternal({
1338
+ reason: { text: "DocumentConnect" },
1339
+ fetchOpsFromStorage: false,
1340
+ });
1307
1341
  }
1308
1342
  }
1309
1343
 
@@ -1319,23 +1353,23 @@ export class Container
1319
1353
 
1320
1354
  // Set Auto Reconnect Mode
1321
1355
  const mode = ReconnectMode.Enabled;
1322
- this.setAutoReconnectInternal(mode);
1356
+ this.setAutoReconnectInternal(mode, args.reason);
1323
1357
  }
1324
1358
 
1325
1359
  public disconnect() {
1326
1360
  if (this.closed) {
1327
1361
  throw new UsageError(`The Container is closed and cannot be disconnected`);
1328
1362
  } else {
1329
- this.disconnectInternal();
1363
+ this.disconnectInternal({ text: "DocumentDisconnect" });
1330
1364
  }
1331
1365
  }
1332
1366
 
1333
- private disconnectInternal() {
1367
+ private disconnectInternal(reason: IConnectionStateChangeReason) {
1334
1368
  assert(!this.closed, 0x2c7 /* "Attempting to disconnect() a closed Container" */);
1335
1369
 
1336
1370
  // Set Auto Reconnect Mode
1337
1371
  const mode = ReconnectMode.Disabled;
1338
- this.setAutoReconnectInternal(mode);
1372
+ this.setAutoReconnectInternal(mode, reason);
1339
1373
  }
1340
1374
 
1341
1375
  private resumeInternal(args: IConnectionArgs) {
@@ -1468,7 +1502,8 @@ export class Container
1468
1502
  specifiedVersion: string | undefined,
1469
1503
  loadMode: IContainerLoadMode,
1470
1504
  resolvedUrl: IResolvedUrl,
1471
- pendingLocalState?: IPendingContainerState,
1505
+ pendingLocalState: IPendingContainerState | undefined,
1506
+ loadToSequenceNumber: number | undefined,
1472
1507
  ) {
1473
1508
  this.service = await this.serviceFactory.createDocumentService(
1474
1509
  resolvedUrl,
@@ -1486,7 +1521,7 @@ export class Container
1486
1521
  // A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
1487
1522
  // B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
1488
1523
  const connectionArgs: IConnectionArgs = {
1489
- reason: "DocumentOpen",
1524
+ reason: { text: "DocumentOpen" },
1490
1525
  mode: "write",
1491
1526
  fetchOpsFromStorage: false,
1492
1527
  };
@@ -1542,6 +1577,57 @@ export class Container
1542
1577
 
1543
1578
  let opsBeforeReturnP: Promise<void> | undefined;
1544
1579
 
1580
+ if (loadMode.pauseAfterLoad === true) {
1581
+ // If we are trying to pause at a specific sequence number, ensure the latest snapshot is not newer than the desired sequence number.
1582
+ if (loadMode.opsBeforeReturn === "sequenceNumber") {
1583
+ assert(
1584
+ loadToSequenceNumber !== undefined,
1585
+ 0x727 /* sequenceNumber should be defined */,
1586
+ );
1587
+ // Note: It is possible that we think the latest snapshot is newer than the specified sequence number
1588
+ // due to saved ops that may be replayed after the snapshot.
1589
+ // https://dev.azure.com/fluidframework/internal/_workitems/edit/5055
1590
+ if (dmAttributes.sequenceNumber > loadToSequenceNumber) {
1591
+ throw new Error(
1592
+ "Cannot satisfy request to pause the container at the specified sequence number. Most recent snapshot is newer than the specified sequence number.",
1593
+ );
1594
+ }
1595
+ }
1596
+
1597
+ // Force readonly mode - this will ensure we don't receive an error for the lack of join op
1598
+ this.forceReadonly(true);
1599
+
1600
+ // We need to setup a listener to stop op processing once we reach the desired sequence number (if specified).
1601
+ const opHandler = () => {
1602
+ if (loadToSequenceNumber === undefined) {
1603
+ // If there is no specified sequence number, pause after the inbound queue is empty.
1604
+ if (this.deltaManager.inbound.length !== 0) {
1605
+ return;
1606
+ }
1607
+ } else {
1608
+ // If there is a specified sequence number, keep processing until we reach it.
1609
+ if (this.deltaManager.lastSequenceNumber < loadToSequenceNumber) {
1610
+ return;
1611
+ }
1612
+ }
1613
+
1614
+ // Pause op processing once we have processed the desired number of ops.
1615
+ void this.deltaManager.inbound.pause();
1616
+ void this.deltaManager.outbound.pause();
1617
+ this.off("op", opHandler);
1618
+ };
1619
+ if (
1620
+ (loadToSequenceNumber === undefined && this.deltaManager.inbound.length === 0) ||
1621
+ this.deltaManager.lastSequenceNumber === loadToSequenceNumber
1622
+ ) {
1623
+ // If we have already reached the desired sequence number, call opHandler() to pause immediately.
1624
+ opHandler();
1625
+ } else {
1626
+ // If we have not yet reached the desired sequence number, setup a listener to pause once we reach it.
1627
+ this.on("op", opHandler);
1628
+ }
1629
+ }
1630
+
1545
1631
  // Attach op handlers to finish initialization and be able to start processing ops
1546
1632
  // Kick off any ops fetching if required.
1547
1633
  switch (loadMode.opsBeforeReturn) {
@@ -1553,6 +1639,9 @@ export class Container
1553
1639
  loadMode.deltaConnection !== "none" ? "all" : "none",
1554
1640
  );
1555
1641
  break;
1642
+ case "sequenceNumber":
1643
+ opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "sequenceNumber");
1644
+ break;
1556
1645
  case "cached":
1557
1646
  opsBeforeReturnP = this.attachDeltaManagerOpHandler(dmAttributes, "cached");
1558
1647
  break;
@@ -1636,6 +1725,22 @@ export class Container
1636
1725
  }
1637
1726
  }
1638
1727
 
1728
+ // If we have not yet reached `loadToSequenceNumber`, we will wait for ops to arrive until we reach it
1729
+ if (
1730
+ loadToSequenceNumber !== undefined &&
1731
+ this.deltaManager.lastSequenceNumber < loadToSequenceNumber
1732
+ ) {
1733
+ await new Promise<void>((resolve, reject) => {
1734
+ const opHandler = (message: ISequencedDocumentMessage) => {
1735
+ if (message.sequenceNumber >= loadToSequenceNumber) {
1736
+ resolve();
1737
+ this.off("op", opHandler);
1738
+ }
1739
+ };
1740
+ this.on("op", opHandler);
1741
+ });
1742
+ }
1743
+
1639
1744
  // Safety net: static version of Container.load() should have learned about it through "closed" handler.
1640
1745
  // But if that did not happen for some reason, fail load for sure.
1641
1746
  // Otherwise we can get into situations where container is closed and does not try to connect to ordering
@@ -1919,18 +2024,18 @@ export class Container
1919
2024
  this.connectionStateHandler.receivedConnectEvent(details);
1920
2025
  });
1921
2026
 
1922
- deltaManager.on("establishingConnection", (reason: string) => {
2027
+ deltaManager.on("establishingConnection", (reason: IConnectionStateChangeReason) => {
1923
2028
  this.connectionStateHandler.establishingConnection(reason);
1924
2029
  });
1925
2030
 
1926
- deltaManager.on("cancelEstablishingConnection", (reason: string) => {
2031
+ deltaManager.on("cancelEstablishingConnection", (reason: IConnectionStateChangeReason) => {
1927
2032
  this.connectionStateHandler.cancelEstablishingConnection(reason);
1928
2033
  });
1929
2034
 
1930
- deltaManager.on("disconnect", (reason: string, error?: IAnyDriverError) => {
2035
+ deltaManager.on("disconnect", (reason: IConnectionStateChangeReason) => {
1931
2036
  this.noopHeuristic?.notifyDisconnect();
1932
2037
  if (!this.closed) {
1933
- this.connectionStateHandler.receivedDisconnectEvent(reason, error);
2038
+ this.connectionStateHandler.receivedDisconnectEvent(reason);
1934
2039
  }
1935
2040
  });
1936
2041
 
@@ -1965,7 +2070,7 @@ export class Container
1965
2070
 
1966
2071
  private async attachDeltaManagerOpHandler(
1967
2072
  attributes: IDocumentAttributes,
1968
- prefetchType?: "cached" | "all" | "none",
2073
+ prefetchType?: "sequenceNumber" | "cached" | "all" | "none",
1969
2074
  ) {
1970
2075
  return this._deltaManager.attachOpHandler(
1971
2076
  attributes.minimumSequenceNumber,
@@ -1983,8 +2088,7 @@ export class Container
1983
2088
  private logConnectionStateChangeTelemetry(
1984
2089
  value: ConnectionState,
1985
2090
  oldState: ConnectionState,
1986
- reason?: string,
1987
- error?: IAnyDriverError,
2091
+ reason?: IConnectionStateChangeReason,
1988
2092
  ) {
1989
2093
  // Log actual event
1990
2094
  const time = performance.now();
@@ -2023,7 +2127,7 @@ export class Container
2023
2127
  from: ConnectionState[oldState],
2024
2128
  duration,
2025
2129
  durationFromDisconnected,
2026
- reason,
2130
+ reason: reason?.text,
2027
2131
  connectionInitiationReason,
2028
2132
  pendingClientId: this.connectionStateHandler.pendingClientId,
2029
2133
  clientId: this.clientId,
@@ -2039,7 +2143,7 @@ export class Container
2039
2143
  isDirty: this.isDirty,
2040
2144
  ...this._deltaManager.connectionProps,
2041
2145
  },
2042
- error,
2146
+ reason?.error,
2043
2147
  );
2044
2148
 
2045
2149
  if (value === ConnectionState.Connected) {
@@ -2047,7 +2151,10 @@ export class Container
2047
2151
  }
2048
2152
  }
2049
2153
 
2050
- private propagateConnectionState(initialTransition: boolean, disconnectedReason?: string) {
2154
+ private propagateConnectionState(
2155
+ initialTransition: boolean,
2156
+ disconnectedReason?: IConnectionStateChangeReason,
2157
+ ) {
2051
2158
  // When container loaded, we want to propagate initial connection state.
2052
2159
  // After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
2053
2160
  // This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
@@ -2064,7 +2171,7 @@ export class Container
2064
2171
 
2065
2172
  this.setContextConnectedState(state, this.readOnlyInfo.readonly ?? false);
2066
2173
  this.protocolHandler.setConnectionState(state, this.clientId);
2067
- raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason);
2174
+ raiseConnectedEvent(this.mc.logger, this, state, this.clientId, disconnectedReason?.text);
2068
2175
  }
2069
2176
 
2070
2177
  // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
@@ -2157,28 +2264,6 @@ export class Container
2157
2264
  }
2158
2265
  const local = this.clientId === message.clientId;
2159
2266
 
2160
- // Check and report if we're getting messages from a clientId that we previously
2161
- // flagged should have left, or from a client that's not in the quorum but should be
2162
- if (message.clientId != null) {
2163
- const client = this.protocolHandler.quorum.getMember(message.clientId);
2164
-
2165
- if (client === undefined && message.type !== MessageType.ClientJoin) {
2166
- // pre-0.58 error message: messageClientIdMissingFromQuorum
2167
- throw new Error("Remote message's clientId is missing from the quorum");
2168
- }
2169
-
2170
- // Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
2171
- // It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
2172
- // document we don't need to blow up aggressively.
2173
- if (
2174
- this.clientsWhoShouldHaveLeft.has(message.clientId) &&
2175
- !canBeCoalescedByService(message)
2176
- ) {
2177
- // pre-0.58 error message: messageClientIdShouldHaveLeft
2178
- throw new Error("Remote message's clientId already should have left");
2179
- }
2180
- }
2181
-
2182
2267
  // Allow the protocol handler to process the message
2183
2268
  const result = this.protocolHandler.processMessage(message, local);
2184
2269
 
@@ -2323,9 +2408,7 @@ export class Container
2323
2408
  (error?: ICriticalContainerError) => this.close(error),
2324
2409
  this.updateDirtyContainerState,
2325
2410
  this.getAbsoluteUrl,
2326
- () => this.resolvedUrl?.id,
2327
2411
  () => this.clientId,
2328
- () => this._deltaManager.serviceConfiguration,
2329
2412
  () => this.attachState,
2330
2413
  () => this.connected,
2331
2414
  getSpecifiedCodeDetails,
@@ -2334,9 +2417,6 @@ export class Container
2334
2417
  this.subLogger,
2335
2418
  pendingLocalState,
2336
2419
  );
2337
- this._lifecycleEvents.once("disposed", () => {
2338
- context.dispose();
2339
- });
2340
2420
 
2341
2421
  this._runtime = await PerformanceEvent.timedExecAsync(
2342
2422
  this.subLogger,
@@ -2388,7 +2468,7 @@ export interface IContainerExperimental extends IContainer {
2388
2468
  * @experimental misuse of this API can result in duplicate op submission and potential document corruption
2389
2469
  * {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
2390
2470
  */
2391
- getPendingLocalState?(): string;
2471
+ getPendingLocalState?(): Promise<string>;
2392
2472
 
2393
2473
  /**
2394
2474
  * Closes the container and returns serialized local state intended to be
@@ -2396,5 +2476,5 @@ export interface IContainerExperimental extends IContainer {
2396
2476
  * @experimental
2397
2477
  * {@link https://github.com/microsoft/FluidFramework/blob/main/packages/loader/container-loader/closeAndGetPendingLocalState.md}
2398
2478
  */
2399
- closeAndGetPendingLocalState(): string;
2479
+ closeAndGetPendingLocalState?(): Promise<string>;
2400
2480
  }
@@ -18,7 +18,6 @@ import {
18
18
  import { FluidObject } from "@fluidframework/core-interfaces";
19
19
  import { IDocumentStorageService } from "@fluidframework/driver-definitions";
20
20
  import {
21
- IClientConfiguration,
22
21
  IClientDetails,
23
22
  IDocumentMessage,
24
23
  IQuorumClients,
@@ -46,13 +45,6 @@ export class ContainerContext implements IContainerContext {
46
45
  return this._getClientId();
47
46
  }
48
47
 
49
- /**
50
- * DISCLAIMER: this id is only for telemetry purposes. Not suitable for any other usages.
51
- */
52
- public get id(): string {
53
- return this._getContainerDiagnosticId() ?? "";
54
- }
55
-
56
48
  /**
57
49
  * When true, ops are free to flow
58
50
  * When false, ops should be kept as pending or rejected
@@ -61,16 +53,6 @@ export class ContainerContext implements IContainerContext {
61
53
  return this._getConnected();
62
54
  }
63
55
 
64
- public get serviceConfiguration(): IClientConfiguration | undefined {
65
- return this._getServiceConfiguration();
66
- }
67
-
68
- private _disposed = false;
69
-
70
- public get disposed() {
71
- return this._disposed;
72
- }
73
-
74
56
  constructor(
75
57
  public readonly options: ILoaderOptions,
76
58
  public readonly scope: FluidObject,
@@ -101,9 +83,7 @@ export class ContainerContext implements IContainerContext {
101
83
  public readonly closeFn: (error?: ICriticalContainerError) => void,
102
84
  public readonly updateDirtyContainerState: (dirty: boolean) => void,
103
85
  public readonly getAbsoluteUrl: (relativeUrl: string) => Promise<string | undefined>,
104
- private readonly _getContainerDiagnosticId: () => string | undefined,
105
86
  private readonly _getClientId: () => string | undefined,
106
- private readonly _getServiceConfiguration: () => IClientConfiguration | undefined,
107
87
  private readonly _getAttachState: () => AttachState,
108
88
  private readonly _getConnected: () => boolean,
109
89
  public readonly getSpecifiedCodeDetails: () => IFluidCodeDetails | undefined,
@@ -113,10 +93,6 @@ export class ContainerContext implements IContainerContext {
113
93
  public readonly pendingLocalState?: unknown,
114
94
  ) {}
115
95
 
116
- public dispose(error?: Error): void {
117
- this._disposed = true;
118
- }
119
-
120
96
  public getLoadedFromVersion(): IVersion | undefined {
121
97
  return this._version;
122
98
  }
package/src/contracts.ts CHANGED
@@ -5,22 +5,24 @@
5
5
 
6
6
  import { ITelemetryProperties } from "@fluidframework/core-interfaces";
7
7
  import {
8
- IDeltaQueue,
9
- ReadOnlyInfo,
10
- IConnectionDetailsInternal,
8
+ IConnectionDetails,
11
9
  ICriticalContainerError,
10
+ IDeltaQueue,
11
+ IErrorBase,
12
12
  IFluidCodeDetails,
13
13
  isFluidPackage,
14
+ ReadOnlyInfo,
14
15
  } from "@fluidframework/container-definitions";
15
16
  import {
16
17
  ConnectionMode,
17
- IDocumentMessage,
18
- ISequencedDocumentMessage,
19
18
  IClientConfiguration,
20
19
  IClientDetails,
20
+ IDocumentMessage,
21
+ ISequencedDocumentMessage,
22
+ ISignalClient,
21
23
  ISignalMessage,
22
24
  } from "@fluidframework/protocol-definitions";
23
- import { IAnyDriverError, IContainerPackageInfo } from "@fluidframework/driver-definitions";
25
+ import { IContainerPackageInfo } from "@fluidframework/driver-definitions";
24
26
 
25
27
  export enum ReconnectMode {
26
28
  Never = "Never",
@@ -28,6 +30,21 @@ export enum ReconnectMode {
28
30
  Enabled = "Enabled",
29
31
  }
30
32
 
33
+ export interface IConnectionStateChangeReason<T extends IErrorBase = IErrorBase> {
34
+ text: string;
35
+ error?: T;
36
+ }
37
+
38
+ /**
39
+ * Internal version of IConnectionDetails with props are only exposed internally
40
+ */
41
+ export interface IConnectionDetailsInternal extends IConnectionDetails {
42
+ mode: ConnectionMode;
43
+ version: string;
44
+ initialClients: ISignalClient[];
45
+ reason: IConnectionStateChangeReason;
46
+ }
47
+
31
48
  /**
32
49
  * Connection manager (implements this interface) is responsible for maintaining connection
33
50
  * to relay service.
@@ -95,7 +112,7 @@ export interface IConnectionManager {
95
112
  /**
96
113
  * Initiates connection to relay service (noop if already connected).
97
114
  */
98
- connect(reason: string, connectionMode?: ConnectionMode): void;
115
+ connect(reason: IConnectionStateChangeReason, connectionMode?: ConnectionMode): void;
99
116
 
100
117
  /**
101
118
  * Disposed connection manager
@@ -139,7 +156,7 @@ export interface IConnectionManagerFactoryArgs {
139
156
  /**
140
157
  * Called whenever connection to relay service is lost.
141
158
  */
142
- readonly disconnectHandler: (reason: string, error?: IAnyDriverError) => void;
159
+ readonly disconnectHandler: (reason: IConnectionStateChangeReason) => void;
143
160
 
144
161
  /**
145
162
  * Called whenever new connection to rely service is established
@@ -169,12 +186,12 @@ export interface IConnectionManagerFactoryArgs {
169
186
  /**
170
187
  * Called whenever we try to start establishing a new connection.
171
188
  */
172
- readonly establishConnectionHandler: (reason: string) => void;
189
+ readonly establishConnectionHandler: (reason: IConnectionStateChangeReason) => void;
173
190
 
174
191
  /**
175
192
  * Called whenever we cancel the connection in progress.
176
193
  */
177
- readonly cancelConnectionHandler: (reason: string) => void;
194
+ readonly cancelConnectionHandler: (reason: IConnectionStateChangeReason) => void;
178
195
  }
179
196
 
180
197
  /**