@fluidframework/container-loader 2.0.0-internal.1.1.3 → 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 +107 -125
  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 +110 -128
  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 +132 -147
  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.
@@ -885,12 +872,12 @@ export class Container extends EventEmitterWithErrorHandling {
885
872
  delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
886
873
  }
887
874
  const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
888
- this._storage.loadSnapshotForRehydratingContainer(snapshotTree);
889
- const attributes = await this.getDocumentAttributes(this._storage, snapshotTree);
875
+ this.storageService.loadSnapshotForRehydratingContainer(snapshotTree);
876
+ const attributes = await this.getDocumentAttributes(this.storageService, snapshotTree);
890
877
  await this.attachDeltaManagerOpHandler(attributes);
891
878
  // Initialize the protocol handler
892
879
  const baseTree = getProtocolSnapshotTree(snapshotTree);
893
- const qValues = await readAndParse(this._storage, baseTree.blobs.quorumValues);
880
+ const qValues = await readAndParse(this.storageService, baseTree.blobs.quorumValues);
894
881
  const codeDetails = getCodeDetailsFromQuorumValues(qValues);
895
882
  this.initializeProtocolState(attributes, {
896
883
  members: [],
@@ -901,23 +888,6 @@ export class Container extends EventEmitterWithErrorHandling {
901
888
  snapshotTree);
902
889
  this.setLoaded();
903
890
  }
904
- async connectStorageService() {
905
- var _a, _b;
906
- if (this._storageService !== undefined) {
907
- return;
908
- }
909
- assert(this.service !== undefined, 0x1ef /* "services must be defined" */);
910
- const storageService = await this.service.connectToStorage();
911
- this._storageService =
912
- new RetriableDocumentStorageService(storageService, this.mc.logger);
913
- if (this.options.summarizeProtocolTree === true) {
914
- this.mc.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
915
- this._storageService =
916
- new ProtocolTreeStorageService(this._storageService, () => this.captureProtocolSummary());
917
- }
918
- // ensure we did not lose that policy in the process of wrapping
919
- 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" */);
920
- }
921
891
  async getDocumentAttributes(storage, tree) {
922
892
  if (tree === undefined) {
923
893
  return {
@@ -956,7 +926,7 @@ export class Container extends EventEmitterWithErrorHandling {
956
926
  initializeProtocolState(attributes, quorumSnapshot) {
957
927
  var _a, _b;
958
928
  const protocolHandlerBuilder = (_a = this.protocolHandlerBuilder) !== null && _a !== void 0 ? _a : ((...args) => new ProtocolHandler(...args, new Audience()));
959
- 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 : []);
960
930
  this._initialClients = undefined;
961
931
  const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
962
932
  protocol.quorum.on("error", (error) => {
@@ -1070,10 +1040,7 @@ export class Container extends EventEmitterWithErrorHandling {
1070
1040
  this._protocolHandler.audience.addMember(priorClient.clientId, priorClient.client);
1071
1041
  }
1072
1042
  }
1073
- const deltaManagerForCatchingUp = this.mc.config.getBoolean("Fluid.Container.CatchUpBeforeDeclaringConnected") === true ?
1074
- this.deltaManager
1075
- : undefined;
1076
- this.connectionStateHandler.receivedConnectEvent(this.connectionMode, details, deltaManagerForCatchingUp);
1043
+ this.connectionStateHandler.receivedConnectEvent(this.connectionMode, details);
1077
1044
  });
1078
1045
  deltaManager.on("disconnect", (reason) => {
1079
1046
  var _a;
@@ -1090,6 +1057,7 @@ export class Container extends EventEmitterWithErrorHandling {
1090
1057
  this.emit("warning", warn);
1091
1058
  });
1092
1059
  deltaManager.on("readonly", (readonly) => {
1060
+ this.setContextConnectedState(this.connectionState === ConnectionState.Connected, readonly);
1093
1061
  this.emit("readonly", readonly);
1094
1062
  });
1095
1063
  deltaManager.on("closed", (error) => {
@@ -1132,12 +1100,7 @@ export class Container extends EventEmitterWithErrorHandling {
1132
1100
  opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
1133
1101
  }
1134
1102
  }
1135
- if (this.firstConnection) {
1136
- connectionInitiationReason = "InitialConnect";
1137
- }
1138
- else {
1139
- connectionInitiationReason = "AutoReconnect";
1140
- }
1103
+ connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
1141
1104
  }
1142
1105
  this.mc.logger.sendPerformanceEvent(Object.assign({ eventName: `ConnectionStateChange_${ConnectionState[value]}`, from: ConnectionState[oldState], duration,
1143
1106
  durationFromDisconnected,
@@ -1148,49 +1111,64 @@ export class Container extends EventEmitterWithErrorHandling {
1148
1111
  this.firstConnection = false;
1149
1112
  }
1150
1113
  }
1151
- propagateConnectionState() {
1114
+ propagateConnectionState(initialTransition) {
1152
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;
1153
1125
  const logOpsOnReconnect = this.connectionState === ConnectionState.Connected &&
1154
1126
  !this.firstConnection &&
1155
1127
  this.connectionMode === "write";
1156
1128
  if (logOpsOnReconnect) {
1157
1129
  this.messageCountAfterDisconnection = 0;
1158
1130
  }
1159
- const state = this.connectionState === ConnectionState.Connected;
1160
1131
  // Both protocol and context should not be undefined if we got so far.
1161
- if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1162
- this.context.setConnectionState(state, this.clientId);
1163
- }
1132
+ this.setContextConnectedState(state, (_a = this._deltaManager.connectionManager.readOnlyInfo.readonly) !== null && _a !== void 0 ? _a : false);
1164
1133
  this.protocolHandler.setConnectionState(state, this.clientId);
1165
1134
  raiseConnectedEvent(this.mc.logger, this, state, this.clientId);
1166
1135
  if (logOpsOnReconnect) {
1167
1136
  this.mc.logger.sendTelemetryEvent({ eventName: "OpsSentOnReconnect", count: this.messageCountAfterDisconnection });
1168
1137
  }
1169
1138
  }
1139
+ // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
1170
1140
  submitContainerMessage(type, contents, batch, metadata) {
1171
- const outboundMessageType = type;
1172
- switch (outboundMessageType) {
1141
+ switch (type) {
1173
1142
  case MessageType.Operation:
1174
- case MessageType.RemoteHelp:
1175
- break;
1176
- case MessageType.Summarize: {
1177
- // github #6451: this is only needed for staging so the server
1178
- // know when the protocol tree is included
1179
- // this can be removed once all clients send
1180
- // protocol tree by default
1181
- const summary = contents;
1182
- if (summary.details === undefined) {
1183
- summary.details = {};
1184
- }
1185
- summary.details.includesProtocolTree =
1186
- this.options.summarizeProtocolTree === true;
1187
- break;
1188
- }
1143
+ return this.submitMessage(type, JSON.stringify(contents), batch, metadata);
1144
+ case MessageType.Summarize:
1145
+ return this.submitSummaryMessage(contents);
1189
1146
  default:
1190
1147
  this.close(new GenericError("invalidContainerSubmitOpType", undefined /* error */, { messageType: type }));
1191
1148
  return -1;
1192
1149
  }
1193
- 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 */);
1194
1172
  }
1195
1173
  submitMessage(type, contents, batch, metadata) {
1196
1174
  var _a;
@@ -1205,22 +1183,9 @@ export class Container extends EventEmitterWithErrorHandling {
1205
1183
  processRemoteMessage(message) {
1206
1184
  const local = this.clientId === message.clientId;
1207
1185
  // Allow the protocol handler to process the message
1208
- let result = { immediateNoOp: false };
1209
- try {
1210
- result = this.protocolHandler.processMessage(message, local);
1211
- }
1212
- catch (error) {
1213
- this.close(wrapError(error, (errorMessage) => new DataCorruptionError(errorMessage, extractSafePropertiesFromMessage(message))));
1214
- }
1215
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
1216
- if (isUnpackedRuntimeMessage(message) && !isRuntimeMessage(message)) {
1217
- this.mc.logger.sendTelemetryEvent({ eventName: "UnpackedRuntimeMessage", type: message.type });
1218
- }
1219
- // Forward non system messages to the loaded runtime for processing
1220
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
1221
- if (isRuntimeMessage(message) || isUnpackedRuntimeMessage(message)) {
1222
- this.context.process(message, local, undefined);
1223
- }
1186
+ const result = this.protocolHandler.processMessage(message, local);
1187
+ // Forward messages to the loaded runtime for processing
1188
+ this.context.process(message, local, undefined);
1224
1189
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
1225
1190
  if (this.activeConnection()) {
1226
1191
  if (this.collabWindowTracker === undefined) {
@@ -1229,15 +1194,14 @@ export class Container extends EventEmitterWithErrorHandling {
1229
1194
  // clients.
1230
1195
  // All existing will continue to use settings they got earlier.
1231
1196
  assert(this.serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1232
- this.collabWindowTracker = new CollabWindowTracker((type, contents) => {
1197
+ this.collabWindowTracker = new CollabWindowTracker((type) => {
1233
1198
  assert(this.activeConnection(), 0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
1234
- this.submitMessage(type, contents);
1199
+ this.submitMessage(type);
1235
1200
  }, this.serviceConfiguration.noopTimeFrequency, this.serviceConfiguration.noopCountFrequency);
1236
1201
  }
1237
1202
  this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
1238
1203
  }
1239
1204
  this.emit("op", message);
1240
- return result;
1241
1205
  }
1242
1206
  submitSignal(message) {
1243
1207
  this._deltaManager.submitSignal(JSON.stringify(message));
@@ -1284,7 +1248,7 @@ export class Container extends EventEmitterWithErrorHandling {
1284
1248
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1285
1249
  // are set. Global requests will still go directly to the loader
1286
1250
  const loader = new RelativeLoader(this, this.loader);
1287
- 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);
1288
1252
  this.emit("contextChanged", codeDetails);
1289
1253
  }
1290
1254
  updateDirtyContainerState(dirty) {
@@ -1297,6 +1261,24 @@ export class Container extends EventEmitterWithErrorHandling {
1297
1261
  logContainerError(warning) {
1298
1262
  this.mc.logger.sendErrorEvent({ eventName: "ContainerWarning" }, warning);
1299
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
+ }
1300
1282
  }
1301
1283
  Container.version = "^0.1.0";
1302
1284
  //# sourceMappingURL=container.js.map