@fluidframework/container-loader 2.0.0-internal.1.1.2 → 2.0.0-internal.1.2.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 (92) hide show
  1. package/dist/catchUpMonitor.d.ts +6 -17
  2. package/dist/catchUpMonitor.d.ts.map +1 -1
  3. package/dist/catchUpMonitor.js +5 -36
  4. package/dist/catchUpMonitor.js.map +1 -1
  5. package/dist/collabWindowTracker.d.ts +1 -1
  6. package/dist/collabWindowTracker.d.ts.map +1 -1
  7. package/dist/collabWindowTracker.js +2 -1
  8. package/dist/collabWindowTracker.js.map +1 -1
  9. package/dist/connectionManager.d.ts +1 -1
  10. package/dist/connectionManager.d.ts.map +1 -1
  11. package/dist/connectionManager.js +8 -11
  12. package/dist/connectionManager.js.map +1 -1
  13. package/dist/connectionStateHandler.d.ts +80 -26
  14. package/dist/connectionStateHandler.d.ts.map +1 -1
  15. package/dist/connectionStateHandler.js +170 -89
  16. package/dist/connectionStateHandler.js.map +1 -1
  17. package/dist/container.d.ts +22 -11
  18. package/dist/container.d.ts.map +1 -1
  19. package/dist/container.js +130 -142
  20. package/dist/container.js.map +1 -1
  21. package/dist/containerContext.d.ts +18 -7
  22. package/dist/containerContext.d.ts.map +1 -1
  23. package/dist/containerContext.js +18 -8
  24. package/dist/containerContext.js.map +1 -1
  25. package/dist/containerStorageAdapter.d.ts +10 -24
  26. package/dist/containerStorageAdapter.d.ts.map +1 -1
  27. package/dist/containerStorageAdapter.js +50 -16
  28. package/dist/containerStorageAdapter.js.map +1 -1
  29. package/dist/deltaManager.d.ts +1 -1
  30. package/dist/deltaManager.d.ts.map +1 -1
  31. package/dist/deltaManager.js +18 -6
  32. package/dist/deltaManager.js.map +1 -1
  33. package/dist/loader.d.ts +1 -1
  34. package/dist/loader.js.map +1 -1
  35. package/dist/packageVersion.d.ts +1 -1
  36. package/dist/packageVersion.js +1 -1
  37. package/dist/packageVersion.js.map +1 -1
  38. package/dist/protocol.d.ts.map +1 -1
  39. package/dist/protocol.js +2 -1
  40. package/dist/protocol.js.map +1 -1
  41. package/lib/catchUpMonitor.d.ts +6 -17
  42. package/lib/catchUpMonitor.d.ts.map +1 -1
  43. package/lib/catchUpMonitor.js +5 -35
  44. package/lib/catchUpMonitor.js.map +1 -1
  45. package/lib/collabWindowTracker.d.ts +1 -1
  46. package/lib/collabWindowTracker.d.ts.map +1 -1
  47. package/lib/collabWindowTracker.js +3 -2
  48. package/lib/collabWindowTracker.js.map +1 -1
  49. package/lib/connectionManager.d.ts +1 -1
  50. package/lib/connectionManager.d.ts.map +1 -1
  51. package/lib/connectionManager.js +9 -14
  52. package/lib/connectionManager.js.map +1 -1
  53. package/lib/connectionStateHandler.d.ts +80 -26
  54. package/lib/connectionStateHandler.d.ts.map +1 -1
  55. package/lib/connectionStateHandler.js +170 -90
  56. package/lib/connectionStateHandler.js.map +1 -1
  57. package/lib/container.d.ts +22 -11
  58. package/lib/container.d.ts.map +1 -1
  59. package/lib/container.js +133 -145
  60. package/lib/container.js.map +1 -1
  61. package/lib/containerContext.d.ts +18 -7
  62. package/lib/containerContext.d.ts.map +1 -1
  63. package/lib/containerContext.js +19 -9
  64. package/lib/containerContext.js.map +1 -1
  65. package/lib/containerStorageAdapter.d.ts +10 -24
  66. package/lib/containerStorageAdapter.d.ts.map +1 -1
  67. package/lib/containerStorageAdapter.js +50 -15
  68. package/lib/containerStorageAdapter.js.map +1 -1
  69. package/lib/deltaManager.d.ts +1 -1
  70. package/lib/deltaManager.d.ts.map +1 -1
  71. package/lib/deltaManager.js +18 -6
  72. package/lib/deltaManager.js.map +1 -1
  73. package/lib/loader.d.ts +1 -1
  74. package/lib/loader.js.map +1 -1
  75. package/lib/packageVersion.d.ts +1 -1
  76. package/lib/packageVersion.js +1 -1
  77. package/lib/packageVersion.js.map +1 -1
  78. package/lib/protocol.d.ts.map +1 -1
  79. package/lib/protocol.js +2 -1
  80. package/lib/protocol.js.map +1 -1
  81. package/package.json +13 -13
  82. package/src/catchUpMonitor.ts +7 -47
  83. package/src/collabWindowTracker.ts +4 -3
  84. package/src/connectionManager.ts +9 -11
  85. package/src/connectionStateHandler.ts +231 -106
  86. package/src/container.ts +156 -168
  87. package/src/containerContext.ts +22 -8
  88. package/src/containerStorageAdapter.ts +64 -15
  89. package/src/deltaManager.ts +20 -7
  90. package/src/loader.ts +1 -1
  91. package/src/packageVersion.ts +1 -1
  92. package/src/protocol.ts +2 -1
package/lib/container.js CHANGED
@@ -7,8 +7,8 @@ import merge from "lodash/merge";
7
7
  import { v4 as uuid } from "uuid";
8
8
  import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
9
9
  import { AttachState, isFluidCodeDetails, } from "@fluidframework/container-definitions";
10
- import { DataCorruptionError, extractSafePropertiesFromMessage, GenericError, UsageError, } from "@fluidframework/container-utils";
11
- import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl, isRuntimeMessage, isUnpackedRuntimeMessage, } from "@fluidframework/driver-utils";
10
+ import { GenericError, UsageError, } from "@fluidframework/container-utils";
11
+ import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl, } from "@fluidframework/driver-utils";
12
12
  import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
13
13
  import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName, disconnectedEventName, normalizeError, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
14
14
  import { Audience } from "./audience";
@@ -18,10 +18,8 @@ import { DeltaManager } from "./deltaManager";
18
18
  import { DeltaManagerProxy } from "./deltaManagerProxy";
19
19
  import { RelativeLoader } from "./loader";
20
20
  import { pkgVersion } from "./packageVersion";
21
- import { ConnectionStateHandler } from "./connectionStateHandler";
22
- import { RetriableDocumentStorageService } from "./retriableDocumentStorageService";
23
- import { ProtocolTreeStorageService } from "./protocolTreeDocumentStorageService";
24
- import { BlobOnlyStorage, ContainerStorageAdapter } from "./containerStorageAdapter";
21
+ import { ContainerStorageAdapter } from "./containerStorageAdapter";
22
+ import { createConnectionStateHandler, } from "./connectionStateHandler";
25
23
  import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
26
24
  import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy } from "./quorum";
27
25
  import { CollabWindowTracker } from "./collabWindowTracker";
@@ -32,14 +30,19 @@ const detachedContainerRefSeqNumber = 0;
32
30
  const dirtyContainerEvent = "dirty";
33
31
  const savedContainerEvent = "saved";
34
32
  /**
35
- * Waits until container connects to delta storage and gets up-to-date
33
+ * Waits until container connects to delta storage and gets up-to-date.
34
+ *
36
35
  * Useful when resolving URIs and hitting 404, due to container being loaded from (stale) snapshot and not being
37
36
  * up to date. Host may chose to wait in such case and retry resolving URI.
37
+ *
38
38
  * Warning: Will wait infinitely for connection to establish if there is no connection.
39
39
  * May result in deadlock if Container.disconnect() is called and never followed by a call to Container.connect().
40
- * @returns true: container is up to date, it processed all the ops that were know at the time of first connection
41
- * false: storage does not provide indication of how far the client is. Container processed
42
- * all the ops known to it, but it maybe still behind.
40
+ *
41
+ * @returns `true`: container is up to date, it processed all the ops that were know at the time of first connection.
42
+ *
43
+ * `false`: storage does not provide indication of how far the client is. Container processed all the ops known to it,
44
+ * but it maybe still behind.
45
+ *
43
46
  * @throws an error beginning with `"Container closed"` if the container is closed before it catches up.
44
47
  */
45
48
  export async function waitContainerToCatchUp(container) {
@@ -183,9 +186,19 @@ export class Container extends EventEmitterWithErrorHandling {
183
186
  this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
184
187
  const summarizeProtocolTree = (_a = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree")) !== null && _a !== void 0 ? _a : this.loader.services.options.summarizeProtocolTree;
185
188
  this.options = Object.assign(Object.assign({}, this.loader.services.options), { summarizeProtocolTree });
186
- this.connectionStateHandler = new ConnectionStateHandler({
187
- quorumClients: () => { var _a; return (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.quorum; },
188
- logConnectionStateChangeTelemetry: (value, oldState, reason) => this.logConnectionStateChangeTelemetry(value, oldState, reason),
189
+ this._deltaManager = this.createDeltaManager();
190
+ this._clientId = (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId;
191
+ this.connectionStateHandler = createConnectionStateHandler({
192
+ logger: this.mc.logger,
193
+ connectionStateChanged: (value, oldState, reason) => {
194
+ if (value === ConnectionState.Connected) {
195
+ this._clientId = this.connectionStateHandler.pendingClientId;
196
+ }
197
+ this.logConnectionStateChangeTelemetry(value, oldState, reason);
198
+ if (this._lifecycleState === "loaded") {
199
+ this.propagateConnectionState(false /* initial transition */);
200
+ }
201
+ },
189
202
  shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
190
203
  maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
191
204
  logConnectionIssue: (eventName, details) => {
@@ -193,29 +206,13 @@ export class Container extends EventEmitterWithErrorHandling {
193
206
  // its own join op. Attempt recovery option.
194
207
  this._deltaManager.logConnectionIssue(Object.assign({ eventName, duration: performance.now() - this.connectionTransitionTimes[ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
195
208
  },
196
- connectionStateChanged: () => {
197
- // Fire events only if container is fully loaded and not closed
198
- if (this._lifecycleState === "loaded") {
199
- this.propagateConnectionState();
200
- }
201
- },
202
- }, this.mc.logger, (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId);
209
+ }, this.deltaManager, this._clientId);
203
210
  this.on(savedContainerEvent, () => {
204
211
  this.connectionStateHandler.containerSaved();
205
212
  });
206
- this._deltaManager = this.createDeltaManager();
207
- this._storage = new ContainerStorageAdapter(() => {
208
- if (this.attachState !== AttachState.Attached) {
209
- if (this.loader.services.detachedBlobStorage !== undefined) {
210
- return new BlobOnlyStorage(this.loader.services.detachedBlobStorage, this.mc.logger);
211
- }
212
- this.mc.logger.sendErrorEvent({
213
- eventName: "NoRealStorageInDetachedContainer",
214
- });
215
- throw new Error("Real storage calls not allowed in Unattached container");
216
- }
217
- return this.storageService;
218
- });
213
+ this.storageService = new ContainerStorageAdapter(this.loader.services.detachedBlobStorage, this.mc.logger, this.options.summarizeProtocolTree === true
214
+ ? () => this.captureProtocolSummary()
215
+ : undefined);
219
216
  const isDomAvailable = typeof document === "object" &&
220
217
  document !== null &&
221
218
  typeof document.addEventListener === "function" &&
@@ -336,7 +333,7 @@ export class Container extends EventEmitterWithErrorHandling {
336
333
  // Only transition states if currently loading
337
334
  if (this._lifecycleState === "loading") {
338
335
  // Propagate current connection state through the system.
339
- this.propagateConnectionState();
336
+ this.propagateConnectionState(true /* initial transition */);
340
337
  this._lifecycleState = "loaded";
341
338
  }
342
339
  }
@@ -344,13 +341,7 @@ export class Container extends EventEmitterWithErrorHandling {
344
341
  return (this._lifecycleState === "closing" || this._lifecycleState === "closed");
345
342
  }
346
343
  get storage() {
347
- return this._storage;
348
- }
349
- get storageService() {
350
- if (this._storageService === undefined) {
351
- throw new Error("Attempted to access storageService before it was defined");
352
- }
353
- return this._storageService;
344
+ return this.storageService;
354
345
  }
355
346
  get context() {
356
347
  if (this._context === undefined) {
@@ -391,7 +382,7 @@ export class Container extends EventEmitterWithErrorHandling {
391
382
  return this.connectionStateHandler.connectionState;
392
383
  }
393
384
  get connected() {
394
- return this.connectionStateHandler.connected;
385
+ return this.connectionStateHandler.connectionState === ConnectionState.Connected;
395
386
  }
396
387
  /**
397
388
  * Service configuration details. If running in offline mode will be undefined otherwise will contain service
@@ -405,7 +396,7 @@ export class Container extends EventEmitterWithErrorHandling {
405
396
  * Set once this.connected is true, otherwise undefined
406
397
  */
407
398
  get clientId() {
408
- return this.connectionStateHandler.clientId;
399
+ return this._clientId;
409
400
  }
410
401
  /**
411
402
  * The server provided claims of the client.
@@ -467,7 +458,7 @@ export class Container extends EventEmitterWithErrorHandling {
467
458
  assert(this._lifecycleState === "closed", 0x314 /* Container properly closed */);
468
459
  }
469
460
  closeCore(error) {
470
- var _a, _b, _c, _d;
461
+ var _a, _b, _c;
471
462
  assert(!this.closed, 0x315 /* re-entrancy */);
472
463
  try {
473
464
  // Ensure that we raise all key events even if one of these throws
@@ -482,11 +473,11 @@ export class Container extends EventEmitterWithErrorHandling {
482
473
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
483
474
  this.connectionStateHandler.dispose();
484
475
  (_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
485
- (_c = this._storageService) === null || _c === void 0 ? void 0 : _c.dispose();
476
+ this.storageService.dispose();
486
477
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
487
478
  // about file, like file being overwritten in storage, but client having stale local cache.
488
479
  // Driver need to ensure all caches are cleared on critical errors
489
- (_d = this.service) === null || _d === void 0 ? void 0 : _d.dispose(error);
480
+ (_c = this.service) === null || _c === void 0 ? void 0 : _c.dispose(error);
490
481
  }
491
482
  catch (exception) {
492
483
  this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
@@ -573,7 +564,7 @@ export class Container extends EventEmitterWithErrorHandling {
573
564
  const resolvedUrl = this.service.resolvedUrl;
574
565
  ensureFluidResolvedUrl(resolvedUrl);
575
566
  this._resolvedUrl = resolvedUrl;
576
- await this.connectStorageService();
567
+ await this.storageService.connectToService(this.service);
577
568
  if (hasAttachmentBlobs) {
578
569
  // upload blobs to storage
579
570
  assert(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
@@ -603,8 +594,6 @@ export class Container extends EventEmitterWithErrorHandling {
603
594
  }
604
595
  this._attachState = AttachState.Attached;
605
596
  this.emit("attached");
606
- // Propagate current connection state through the system.
607
- this.propagateConnectionState();
608
597
  if (!this.closed) {
609
598
  this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
610
599
  }
@@ -743,9 +732,7 @@ export class Container extends EventEmitterWithErrorHandling {
743
732
  /**
744
733
  * Load container.
745
734
  *
746
- * @param specifiedVersion - one of the following
747
- * - undefined - fetch latest snapshot
748
- * - otherwise, version sha to load snapshot
735
+ * @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
749
736
  */
750
737
  async load(specifiedVersion, loadMode, pendingLocalState) {
751
738
  if (this._resolvedUrl === undefined) {
@@ -768,11 +755,11 @@ export class Container extends EventEmitterWithErrorHandling {
768
755
  this.connectToDeltaStream(connectionArgs);
769
756
  }
770
757
  if (!pendingLocalState) {
771
- await this.connectStorageService();
758
+ await this.storageService.connectToService(this.service);
772
759
  }
773
760
  else {
774
761
  // if we have pendingLocalState we can load without storage; don't wait for connection
775
- this.connectStorageService().catch((error) => this.close(error));
762
+ this.storageService.connectToService(this.service).catch((error) => this.close(error));
776
763
  }
777
764
  this._attachState = AttachState.Attached;
778
765
  // Fetch specified snapshot.
@@ -807,12 +794,16 @@ export class Container extends EventEmitterWithErrorHandling {
807
794
  }
808
795
  // ...load in the existing quorum
809
796
  // Initialize the protocol handler
810
- this._protocolHandler = pendingLocalState === undefined
811
- ? await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot) : await this.initializeProtocolState(attributes, {
812
- members: pendingLocalState.protocol.members,
813
- proposals: pendingLocalState.protocol.proposals,
814
- values: pendingLocalState.protocol.values,
815
- });
797
+ if (pendingLocalState === undefined) {
798
+ await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot);
799
+ }
800
+ else {
801
+ this.initializeProtocolState(attributes, {
802
+ members: pendingLocalState.protocol.members,
803
+ proposals: pendingLocalState.protocol.proposals,
804
+ values: pendingLocalState.protocol.values,
805
+ });
806
+ }
816
807
  const codeDetails = this.getCodeDetailsFromQuorum();
817
808
  await this.instantiateContext(true, // existing
818
809
  codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
@@ -866,7 +857,7 @@ export class Container extends EventEmitterWithErrorHandling {
866
857
  await this.attachDeltaManagerOpHandler(attributes);
867
858
  // Need to just seed the source data in the code quorum. Quorum itself is empty
868
859
  const qValues = initQuorumValuesFromCodeDetails(source);
869
- this._protocolHandler = await this.initializeProtocolState(attributes, {
860
+ this.initializeProtocolState(attributes, {
870
861
  members: [],
871
862
  proposals: [],
872
863
  values: qValues,
@@ -881,40 +872,22 @@ export class Container extends EventEmitterWithErrorHandling {
881
872
  delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
882
873
  }
883
874
  const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
884
- this._storage.loadSnapshotForRehydratingContainer(snapshotTree);
885
- const attributes = await this.getDocumentAttributes(this._storage, snapshotTree);
875
+ this.storageService.loadSnapshotForRehydratingContainer(snapshotTree);
876
+ const attributes = await this.getDocumentAttributes(this.storageService, snapshotTree);
886
877
  await this.attachDeltaManagerOpHandler(attributes);
887
878
  // Initialize the protocol handler
888
879
  const baseTree = getProtocolSnapshotTree(snapshotTree);
889
- const qValues = await readAndParse(this._storage, baseTree.blobs.quorumValues);
880
+ const qValues = await readAndParse(this.storageService, baseTree.blobs.quorumValues);
890
881
  const codeDetails = getCodeDetailsFromQuorumValues(qValues);
891
- this._protocolHandler =
892
- await this.initializeProtocolState(attributes, {
893
- members: [],
894
- proposals: [],
895
- values: codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
896
- });
882
+ this.initializeProtocolState(attributes, {
883
+ members: [],
884
+ proposals: [],
885
+ values: codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
886
+ });
897
887
  await this.instantiateContextDetached(true, // existing
898
888
  snapshotTree);
899
889
  this.setLoaded();
900
890
  }
901
- async connectStorageService() {
902
- var _a, _b;
903
- if (this._storageService !== undefined) {
904
- return;
905
- }
906
- assert(this.service !== undefined, 0x1ef /* "services must be defined" */);
907
- const storageService = await this.service.connectToStorage();
908
- this._storageService =
909
- new RetriableDocumentStorageService(storageService, this.mc.logger);
910
- if (this.options.summarizeProtocolTree === true) {
911
- this.mc.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
912
- this._storageService =
913
- new ProtocolTreeStorageService(this._storageService, () => this.captureProtocolSummary());
914
- }
915
- // ensure we did not lose that policy in the process of wrapping
916
- assert(((_a = storageService.policies) === null || _a === void 0 ? void 0 : _a.minBlobSize) === ((_b = this.storageService.policies) === null || _b === void 0 ? void 0 : _b.minBlobSize), 0x0e0 /* "lost minBlobSize policy" */);
917
- }
918
891
  async getDocumentAttributes(storage, tree) {
919
892
  if (tree === undefined) {
920
893
  return {
@@ -948,13 +921,12 @@ export class Container extends EventEmitterWithErrorHandling {
948
921
  readAndParse(storage, baseTree.blobs.quorumValues),
949
922
  ]);
950
923
  }
951
- const protocolHandler = await this.initializeProtocolState(attributes, quorumSnapshot);
952
- return protocolHandler;
924
+ this.initializeProtocolState(attributes, quorumSnapshot);
953
925
  }
954
- async initializeProtocolState(attributes, quorumSnapshot) {
926
+ initializeProtocolState(attributes, quorumSnapshot) {
955
927
  var _a, _b;
956
928
  const protocolHandlerBuilder = (_a = this.protocolHandlerBuilder) !== null && _a !== void 0 ? _a : ((...args) => new ProtocolHandler(...args, new Audience()));
957
- const protocol = protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) => this.submitMessage(MessageType.Propose, { key, value }), (_b = this._initialClients) !== null && _b !== void 0 ? _b : []);
929
+ const protocol = protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) => this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })), (_b = this._initialClients) !== null && _b !== void 0 ? _b : []);
958
930
  this._initialClients = undefined;
959
931
  const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
960
932
  protocol.quorum.on("error", (error) => {
@@ -980,7 +952,11 @@ export class Container extends EventEmitterWithErrorHandling {
980
952
  });
981
953
  }
982
954
  });
983
- return protocol;
955
+ // we need to make sure this member get set in a synchronous context,
956
+ // or other things can happen after the object that will be set is created, but not yet set
957
+ // this was breaking this._initialClients handling
958
+ //
959
+ this._protocolHandler = protocol;
984
960
  }
985
961
  captureProtocolSummary() {
986
962
  const quorumSnapshot = this.protocolHandler.snapshot();
@@ -1064,10 +1040,7 @@ export class Container extends EventEmitterWithErrorHandling {
1064
1040
  this._protocolHandler.audience.addMember(priorClient.clientId, priorClient.client);
1065
1041
  }
1066
1042
  }
1067
- const deltaManagerForCatchingUp = this.mc.config.getBoolean("Fluid.Container.CatchUpBeforeDeclaringConnected") === true ?
1068
- this.deltaManager
1069
- : undefined;
1070
- this.connectionStateHandler.receivedConnectEvent(this.connectionMode, details, deltaManagerForCatchingUp);
1043
+ this.connectionStateHandler.receivedConnectEvent(this.connectionMode, details);
1071
1044
  });
1072
1045
  deltaManager.on("disconnect", (reason) => {
1073
1046
  var _a;
@@ -1084,6 +1057,7 @@ export class Container extends EventEmitterWithErrorHandling {
1084
1057
  this.emit("warning", warn);
1085
1058
  });
1086
1059
  deltaManager.on("readonly", (readonly) => {
1060
+ this.setContextConnectedState(this.connectionState === ConnectionState.Connected, readonly);
1087
1061
  this.emit("readonly", readonly);
1088
1062
  });
1089
1063
  deltaManager.on("closed", (error) => {
@@ -1126,12 +1100,7 @@ export class Container extends EventEmitterWithErrorHandling {
1126
1100
  opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
1127
1101
  }
1128
1102
  }
1129
- if (this.firstConnection) {
1130
- connectionInitiationReason = "InitialConnect";
1131
- }
1132
- else {
1133
- connectionInitiationReason = "AutoReconnect";
1134
- }
1103
+ connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
1135
1104
  }
1136
1105
  this.mc.logger.sendPerformanceEvent(Object.assign({ eventName: `ConnectionStateChange_${ConnectionState[value]}`, from: ConnectionState[oldState], duration,
1137
1106
  durationFromDisconnected,
@@ -1142,49 +1111,64 @@ export class Container extends EventEmitterWithErrorHandling {
1142
1111
  this.firstConnection = false;
1143
1112
  }
1144
1113
  }
1145
- propagateConnectionState() {
1114
+ propagateConnectionState(initialTransition) {
1146
1115
  var _a;
1116
+ // When container loaded, we want to propagate initial connection state.
1117
+ // After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
1118
+ // This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
1119
+ if (!initialTransition &&
1120
+ this.connectionState !== ConnectionState.Connected &&
1121
+ this.connectionState !== ConnectionState.Disconnected) {
1122
+ return;
1123
+ }
1124
+ const state = this.connectionState === ConnectionState.Connected;
1147
1125
  const logOpsOnReconnect = this.connectionState === ConnectionState.Connected &&
1148
1126
  !this.firstConnection &&
1149
1127
  this.connectionMode === "write";
1150
1128
  if (logOpsOnReconnect) {
1151
1129
  this.messageCountAfterDisconnection = 0;
1152
1130
  }
1153
- const state = this.connectionState === ConnectionState.Connected;
1154
1131
  // Both protocol and context should not be undefined if we got so far.
1155
- if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1156
- this.context.setConnectionState(state, this.clientId);
1157
- }
1132
+ this.setContextConnectedState(state, (_a = this._deltaManager.connectionManager.readOnlyInfo.readonly) !== null && _a !== void 0 ? _a : false);
1158
1133
  this.protocolHandler.setConnectionState(state, this.clientId);
1159
1134
  raiseConnectedEvent(this.mc.logger, this, state, this.clientId);
1160
1135
  if (logOpsOnReconnect) {
1161
1136
  this.mc.logger.sendTelemetryEvent({ eventName: "OpsSentOnReconnect", count: this.messageCountAfterDisconnection });
1162
1137
  }
1163
1138
  }
1139
+ // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
1164
1140
  submitContainerMessage(type, contents, batch, metadata) {
1165
- const outboundMessageType = type;
1166
- switch (outboundMessageType) {
1141
+ switch (type) {
1167
1142
  case MessageType.Operation:
1168
- case MessageType.RemoteHelp:
1169
- break;
1170
- case MessageType.Summarize: {
1171
- // github #6451: this is only needed for staging so the server
1172
- // know when the protocol tree is included
1173
- // this can be removed once all clients send
1174
- // protocol tree by default
1175
- const summary = contents;
1176
- if (summary.details === undefined) {
1177
- summary.details = {};
1178
- }
1179
- summary.details.includesProtocolTree =
1180
- this.options.summarizeProtocolTree === true;
1181
- break;
1182
- }
1143
+ return this.submitMessage(type, JSON.stringify(contents), batch, metadata);
1144
+ case MessageType.Summarize:
1145
+ return this.submitSummaryMessage(contents);
1183
1146
  default:
1184
1147
  this.close(new GenericError("invalidContainerSubmitOpType", undefined /* error */, { messageType: type }));
1185
1148
  return -1;
1186
1149
  }
1187
- return this.submitMessage(type, contents, batch, metadata);
1150
+ }
1151
+ /** @returns clientSequenceNumber of last message in a batch */
1152
+ submitBatch(batch) {
1153
+ let clientSequenceNumber = -1;
1154
+ for (const message of batch) {
1155
+ clientSequenceNumber = this.submitMessage(MessageType.Operation, message.contents, true, // batch
1156
+ message.metadata);
1157
+ }
1158
+ this._deltaManager.flush();
1159
+ return clientSequenceNumber;
1160
+ }
1161
+ submitSummaryMessage(summary) {
1162
+ // github #6451: this is only needed for staging so the server
1163
+ // know when the protocol tree is included
1164
+ // this can be removed once all clients send
1165
+ // protocol tree by default
1166
+ if (summary.details === undefined) {
1167
+ summary.details = {};
1168
+ }
1169
+ summary.details.includesProtocolTree =
1170
+ this.options.summarizeProtocolTree === true;
1171
+ return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */);
1188
1172
  }
1189
1173
  submitMessage(type, contents, batch, metadata) {
1190
1174
  var _a;
@@ -1199,22 +1183,9 @@ export class Container extends EventEmitterWithErrorHandling {
1199
1183
  processRemoteMessage(message) {
1200
1184
  const local = this.clientId === message.clientId;
1201
1185
  // Allow the protocol handler to process the message
1202
- let result = { immediateNoOp: false };
1203
- try {
1204
- result = this.protocolHandler.processMessage(message, local);
1205
- }
1206
- catch (error) {
1207
- this.close(wrapError(error, (errorMessage) => new DataCorruptionError(errorMessage, extractSafePropertiesFromMessage(message))));
1208
- }
1209
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
1210
- if (isUnpackedRuntimeMessage(message) && !isRuntimeMessage(message)) {
1211
- this.mc.logger.sendTelemetryEvent({ eventName: "UnpackedRuntimeMessage", type: message.type });
1212
- }
1213
- // Forward non system messages to the loaded runtime for processing
1214
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
1215
- if (isRuntimeMessage(message) || isUnpackedRuntimeMessage(message)) {
1216
- this.context.process(message, local, undefined);
1217
- }
1186
+ const result = this.protocolHandler.processMessage(message, local);
1187
+ // Forward messages to the loaded runtime for processing
1188
+ this.context.process(message, local, undefined);
1218
1189
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
1219
1190
  if (this.activeConnection()) {
1220
1191
  if (this.collabWindowTracker === undefined) {
@@ -1223,15 +1194,14 @@ export class Container extends EventEmitterWithErrorHandling {
1223
1194
  // clients.
1224
1195
  // All existing will continue to use settings they got earlier.
1225
1196
  assert(this.serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1226
- this.collabWindowTracker = new CollabWindowTracker((type, contents) => {
1197
+ this.collabWindowTracker = new CollabWindowTracker((type) => {
1227
1198
  assert(this.activeConnection(), 0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
1228
- this.submitMessage(type, contents);
1199
+ this.submitMessage(type);
1229
1200
  }, this.serviceConfiguration.noopTimeFrequency, this.serviceConfiguration.noopCountFrequency);
1230
1201
  }
1231
1202
  this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
1232
1203
  }
1233
1204
  this.emit("op", message);
1234
- return result;
1235
1205
  }
1236
1206
  submitSignal(message) {
1237
1207
  this._deltaManager.submitSignal(JSON.stringify(message));
@@ -1278,7 +1248,7 @@ export class Container extends EventEmitterWithErrorHandling {
1278
1248
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1279
1249
  // are set. Global requests will still go directly to the loader
1280
1250
  const loader = new RelativeLoader(this, this.loader);
1281
- this._context = await ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new DeltaManagerProxy(this._deltaManager), new QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (message) => this.submitSignal(message), (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
1251
+ this._context = await ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new DeltaManagerProxy(this._deltaManager), new QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp) => this.submitSummaryMessage(summaryOp), (batch) => this.submitBatch(batch), (message) => this.submitSignal(message), (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
1282
1252
  this.emit("contextChanged", codeDetails);
1283
1253
  }
1284
1254
  updateDirtyContainerState(dirty) {
@@ -1291,6 +1261,24 @@ export class Container extends EventEmitterWithErrorHandling {
1291
1261
  logContainerError(warning) {
1292
1262
  this.mc.logger.sendErrorEvent({ eventName: "ContainerWarning" }, warning);
1293
1263
  }
1264
+ /**
1265
+ * Set the connected state of the ContainerContext
1266
+ * This controls the "connected" state of the ContainerRuntime as well
1267
+ * @param state - Is the container currently connected?
1268
+ * @param readonly - Is the container in readonly mode?
1269
+ */
1270
+ setContextConnectedState(state, readonly) {
1271
+ var _a;
1272
+ if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1273
+ /**
1274
+ * We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
1275
+ * ops getting through to the DeltaManager.
1276
+ * The ContainerRuntime's "connected" state simply means it is ok to send ops
1277
+ * See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
1278
+ */
1279
+ this.context.setConnectionState(state && !readonly, this.clientId);
1280
+ }
1281
+ }
1294
1282
  }
1295
1283
  Container.version = "^0.1.0";
1296
1284
  //# sourceMappingURL=container.js.map