@fluidframework/container-loader 1.3.0 → 2.0.0-dev.1.4.5.105745

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 (148) hide show
  1. package/.eslintrc.js +8 -21
  2. package/.mocharc.js +12 -0
  3. package/dist/audience.d.ts +2 -2
  4. package/dist/audience.d.ts.map +1 -1
  5. package/dist/audience.js.map +1 -1
  6. package/dist/catchUpMonitor.d.ts +29 -0
  7. package/dist/catchUpMonitor.d.ts.map +1 -0
  8. package/dist/catchUpMonitor.js +43 -0
  9. package/dist/catchUpMonitor.js.map +1 -0
  10. package/dist/collabWindowTracker.d.ts +1 -1
  11. package/dist/collabWindowTracker.d.ts.map +1 -1
  12. package/dist/collabWindowTracker.js +12 -4
  13. package/dist/collabWindowTracker.js.map +1 -1
  14. package/dist/connectionManager.d.ts +5 -5
  15. package/dist/connectionManager.d.ts.map +1 -1
  16. package/dist/connectionManager.js +13 -18
  17. package/dist/connectionManager.js.map +1 -1
  18. package/dist/connectionState.d.ts +0 -5
  19. package/dist/connectionState.d.ts.map +1 -1
  20. package/dist/connectionState.js +0 -5
  21. package/dist/connectionState.js.map +1 -1
  22. package/dist/connectionStateHandler.d.ts +84 -22
  23. package/dist/connectionStateHandler.d.ts.map +1 -1
  24. package/dist/connectionStateHandler.js +172 -59
  25. package/dist/connectionStateHandler.js.map +1 -1
  26. package/dist/container.d.ts +30 -17
  27. package/dist/container.d.ts.map +1 -1
  28. package/dist/container.js +173 -165
  29. package/dist/container.js.map +1 -1
  30. package/dist/containerContext.d.ts +18 -7
  31. package/dist/containerContext.d.ts.map +1 -1
  32. package/dist/containerContext.js +18 -8
  33. package/dist/containerContext.js.map +1 -1
  34. package/dist/containerStorageAdapter.d.ts +11 -25
  35. package/dist/containerStorageAdapter.d.ts.map +1 -1
  36. package/dist/containerStorageAdapter.js +51 -17
  37. package/dist/containerStorageAdapter.js.map +1 -1
  38. package/dist/contracts.d.ts +5 -5
  39. package/dist/contracts.js.map +1 -1
  40. package/dist/deltaManager.d.ts +4 -1
  41. package/dist/deltaManager.d.ts.map +1 -1
  42. package/dist/deltaManager.js +33 -6
  43. package/dist/deltaManager.js.map +1 -1
  44. package/dist/deltaQueue.js +3 -3
  45. package/dist/deltaQueue.js.map +1 -1
  46. package/dist/index.d.ts +1 -0
  47. package/dist/index.d.ts.map +1 -1
  48. package/dist/index.js.map +1 -1
  49. package/dist/loader.d.ts +8 -1
  50. package/dist/loader.d.ts.map +1 -1
  51. package/dist/loader.js +4 -3
  52. package/dist/loader.js.map +1 -1
  53. package/dist/packageVersion.d.ts +1 -1
  54. package/dist/packageVersion.d.ts.map +1 -1
  55. package/dist/packageVersion.js +1 -1
  56. package/dist/packageVersion.js.map +1 -1
  57. package/dist/protocol.d.ts +22 -0
  58. package/dist/protocol.d.ts.map +1 -0
  59. package/dist/protocol.js +53 -0
  60. package/dist/protocol.js.map +1 -0
  61. package/dist/protocolTreeDocumentStorageService.d.ts +1 -1
  62. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  63. package/dist/retriableDocumentStorageService.d.ts +2 -2
  64. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  65. package/dist/retriableDocumentStorageService.js +2 -2
  66. package/dist/retriableDocumentStorageService.js.map +1 -1
  67. package/lib/audience.d.ts +2 -2
  68. package/lib/audience.d.ts.map +1 -1
  69. package/lib/audience.js.map +1 -1
  70. package/lib/catchUpMonitor.d.ts +29 -0
  71. package/lib/catchUpMonitor.d.ts.map +1 -0
  72. package/lib/catchUpMonitor.js +39 -0
  73. package/lib/catchUpMonitor.js.map +1 -0
  74. package/lib/collabWindowTracker.d.ts +1 -1
  75. package/lib/collabWindowTracker.d.ts.map +1 -1
  76. package/lib/collabWindowTracker.js +13 -5
  77. package/lib/collabWindowTracker.js.map +1 -1
  78. package/lib/connectionManager.d.ts +5 -5
  79. package/lib/connectionManager.d.ts.map +1 -1
  80. package/lib/connectionManager.js +14 -21
  81. package/lib/connectionManager.js.map +1 -1
  82. package/lib/connectionState.d.ts +0 -5
  83. package/lib/connectionState.d.ts.map +1 -1
  84. package/lib/connectionState.js +0 -5
  85. package/lib/connectionState.js.map +1 -1
  86. package/lib/connectionStateHandler.d.ts +84 -22
  87. package/lib/connectionStateHandler.d.ts.map +1 -1
  88. package/lib/connectionStateHandler.js +171 -59
  89. package/lib/connectionStateHandler.js.map +1 -1
  90. package/lib/container.d.ts +30 -17
  91. package/lib/container.d.ts.map +1 -1
  92. package/lib/container.js +176 -168
  93. package/lib/container.js.map +1 -1
  94. package/lib/containerContext.d.ts +18 -7
  95. package/lib/containerContext.d.ts.map +1 -1
  96. package/lib/containerContext.js +19 -9
  97. package/lib/containerContext.js.map +1 -1
  98. package/lib/containerStorageAdapter.d.ts +11 -25
  99. package/lib/containerStorageAdapter.d.ts.map +1 -1
  100. package/lib/containerStorageAdapter.js +51 -16
  101. package/lib/containerStorageAdapter.js.map +1 -1
  102. package/lib/contracts.d.ts +5 -5
  103. package/lib/contracts.js.map +1 -1
  104. package/lib/deltaManager.d.ts +4 -1
  105. package/lib/deltaManager.d.ts.map +1 -1
  106. package/lib/deltaManager.js +35 -8
  107. package/lib/deltaManager.js.map +1 -1
  108. package/lib/deltaQueue.js +3 -3
  109. package/lib/deltaQueue.js.map +1 -1
  110. package/lib/index.d.ts +1 -0
  111. package/lib/index.d.ts.map +1 -1
  112. package/lib/index.js.map +1 -1
  113. package/lib/loader.d.ts +8 -1
  114. package/lib/loader.d.ts.map +1 -1
  115. package/lib/loader.js +4 -3
  116. package/lib/loader.js.map +1 -1
  117. package/lib/packageVersion.d.ts +1 -1
  118. package/lib/packageVersion.d.ts.map +1 -1
  119. package/lib/packageVersion.js +1 -1
  120. package/lib/packageVersion.js.map +1 -1
  121. package/lib/protocol.d.ts +22 -0
  122. package/lib/protocol.d.ts.map +1 -0
  123. package/lib/protocol.js +49 -0
  124. package/lib/protocol.js.map +1 -0
  125. package/lib/protocolTreeDocumentStorageService.d.ts +1 -1
  126. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  127. package/lib/retriableDocumentStorageService.d.ts +2 -2
  128. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  129. package/lib/retriableDocumentStorageService.js +2 -2
  130. package/lib/retriableDocumentStorageService.js.map +1 -1
  131. package/package.json +26 -20
  132. package/src/audience.ts +2 -2
  133. package/src/catchUpMonitor.ts +59 -0
  134. package/src/collabWindowTracker.ts +15 -6
  135. package/src/connectionManager.ts +23 -27
  136. package/src/connectionState.ts +0 -6
  137. package/src/connectionStateHandler.ts +235 -70
  138. package/src/container.ts +223 -209
  139. package/src/containerContext.ts +22 -8
  140. package/src/containerStorageAdapter.ts +71 -16
  141. package/src/contracts.ts +7 -7
  142. package/src/deltaManager.ts +42 -11
  143. package/src/deltaQueue.ts +3 -3
  144. package/src/index.ts +4 -0
  145. package/src/loader.ts +14 -3
  146. package/src/packageVersion.ts +1 -1
  147. package/src/protocol.ts +97 -0
  148. package/src/retriableDocumentStorageService.ts +8 -2
package/dist/container.js CHANGED
@@ -15,7 +15,6 @@ const common_utils_1 = require("@fluidframework/common-utils");
15
15
  const container_definitions_1 = require("@fluidframework/container-definitions");
16
16
  const container_utils_1 = require("@fluidframework/container-utils");
17
17
  const driver_utils_1 = require("@fluidframework/driver-utils");
18
- const protocol_base_1 = require("@fluidframework/protocol-base");
19
18
  const protocol_definitions_1 = require("@fluidframework/protocol-definitions");
20
19
  const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
21
20
  const audience_1 = require("./audience");
@@ -25,27 +24,31 @@ const deltaManager_1 = require("./deltaManager");
25
24
  const deltaManagerProxy_1 = require("./deltaManagerProxy");
26
25
  const loader_1 = require("./loader");
27
26
  const packageVersion_1 = require("./packageVersion");
28
- const connectionStateHandler_1 = require("./connectionStateHandler");
29
- const retriableDocumentStorageService_1 = require("./retriableDocumentStorageService");
30
- const protocolTreeDocumentStorageService_1 = require("./protocolTreeDocumentStorageService");
31
27
  const containerStorageAdapter_1 = require("./containerStorageAdapter");
28
+ const connectionStateHandler_1 = require("./connectionStateHandler");
32
29
  const utils_1 = require("./utils");
33
30
  const quorum_1 = require("./quorum");
34
31
  const collabWindowTracker_1 = require("./collabWindowTracker");
35
32
  const connectionManager_1 = require("./connectionManager");
36
33
  const connectionState_1 = require("./connectionState");
34
+ const protocol_1 = require("./protocol");
37
35
  const detachedContainerRefSeqNumber = 0;
38
36
  const dirtyContainerEvent = "dirty";
39
37
  const savedContainerEvent = "saved";
40
38
  /**
41
- * Waits until container connects to delta storage and gets up-to-date
39
+ * Waits until container connects to delta storage and gets up-to-date.
40
+ *
42
41
  * Useful when resolving URIs and hitting 404, due to container being loaded from (stale) snapshot and not being
43
42
  * up to date. Host may chose to wait in such case and retry resolving URI.
43
+ *
44
44
  * Warning: Will wait infinitely for connection to establish if there is no connection.
45
45
  * May result in deadlock if Container.disconnect() is called and never followed by a call to Container.connect().
46
- * @returns true: container is up to date, it processed all the ops that were know at the time of first connection
47
- * false: storage does not provide indication of how far the client is. Container processed
48
- * all the ops known to it, but it maybe still behind.
46
+ *
47
+ * @returns `true`: container is up to date, it processed all the ops that were know at the time of first connection.
48
+ *
49
+ * `false`: storage does not provide indication of how far the client is. Container processed all the ops known to it,
50
+ * but it maybe still behind.
51
+ *
49
52
  * @throws an error beginning with `"Container closed"` if the container is closed before it catches up.
50
53
  */
51
54
  async function waitContainerToCatchUp(container) {
@@ -63,6 +66,10 @@ async function waitContainerToCatchUp(container) {
63
66
  : new container_utils_1.GenericError(baseMessage));
64
67
  };
65
68
  container.on("closed", closedCallback);
69
+ // Depending on config, transition to "connected" state may include the guarantee
70
+ // that all known ops have been processed. If so, we may introduce additional wait here.
71
+ // Waiting for "connected" state in either case gets us at least to our own Join op
72
+ // which is a reasonable approximation of "caught up"
66
73
  const waitForOps = () => {
67
74
  (0, common_utils_1.assert)(container.connectionState === connectionState_1.ConnectionState.CatchingUp
68
75
  || container.connectionState === connectionState_1.ConnectionState.Connected, 0x0cd /* "Container disconnected while waiting for ops!" */);
@@ -120,7 +127,7 @@ async function ReportIfTooLong(logger, eventName, action) {
120
127
  }
121
128
  const summarizerClientType = "summarizer";
122
129
  class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
123
- constructor(loader, config) {
130
+ constructor(loader, config, protocolHandlerBuilder) {
124
131
  var _a, _b;
125
132
  super((name, error) => {
126
133
  this.mc.logger.sendErrorEvent({
@@ -129,6 +136,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
129
136
  }, error);
130
137
  });
131
138
  this.loader = loader;
139
+ this.protocolHandlerBuilder = protocolHandlerBuilder;
132
140
  // Tells if container can reconnect on losing fist connection
133
141
  // If false, container gets closed on loss of connection.
134
142
  this._canReconnect = true;
@@ -142,7 +150,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
142
150
  this.attachStarted = false;
143
151
  this._dirtyContainer = false;
144
152
  this.setAutoReconnectTime = common_utils_1.performance.now();
145
- this._audience = new audience_1.Audience();
146
153
  this.clientDetailsOverride = config.clientDetailsOverride;
147
154
  this._resolvedUrl = config.resolvedUrl;
148
155
  if (config.canReconnect !== undefined) {
@@ -186,9 +193,19 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
186
193
  this.mc = (0, telemetry_utils_1.loggerToMonitoringContext)(telemetry_utils_1.ChildLogger.create(this.subLogger, "Container"));
187
194
  const summarizeProtocolTree = (_a = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree")) !== null && _a !== void 0 ? _a : this.loader.services.options.summarizeProtocolTree;
188
195
  this.options = Object.assign(Object.assign({}, this.loader.services.options), { summarizeProtocolTree });
189
- this.connectionStateHandler = new connectionStateHandler_1.ConnectionStateHandler({
190
- quorumClients: () => { var _a; return (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.quorum; },
191
- logConnectionStateChangeTelemetry: (value, oldState, reason) => this.logConnectionStateChangeTelemetry(value, oldState, reason),
196
+ this._deltaManager = this.createDeltaManager();
197
+ this._clientId = (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId;
198
+ this.connectionStateHandler = (0, connectionStateHandler_1.createConnectionStateHandler)({
199
+ logger: this.mc.logger,
200
+ connectionStateChanged: (value, oldState, reason) => {
201
+ if (value === connectionState_1.ConnectionState.Connected) {
202
+ this._clientId = this.connectionStateHandler.pendingClientId;
203
+ }
204
+ this.logConnectionStateChangeTelemetry(value, oldState, reason);
205
+ if (this._lifecycleState === "loaded") {
206
+ this.propagateConnectionState(false /* initial transition */);
207
+ }
208
+ },
192
209
  shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
193
210
  maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
194
211
  logConnectionIssue: (eventName, details) => {
@@ -196,29 +213,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
196
213
  // its own join op. Attempt recovery option.
197
214
  this._deltaManager.logConnectionIssue(Object.assign({ eventName, duration: common_utils_1.performance.now() - this.connectionTransitionTimes[connectionState_1.ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
198
215
  },
199
- connectionStateChanged: () => {
200
- // Fire events only if container is fully loaded and not closed
201
- if (this._lifecycleState === "loaded") {
202
- this.propagateConnectionState();
203
- }
204
- },
205
- }, this.mc.logger, (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId);
216
+ }, this.deltaManager, this._clientId);
206
217
  this.on(savedContainerEvent, () => {
207
218
  this.connectionStateHandler.containerSaved();
208
219
  });
209
- this._deltaManager = this.createDeltaManager();
210
- this._storage = new containerStorageAdapter_1.ContainerStorageAdapter(() => {
211
- if (this.attachState !== container_definitions_1.AttachState.Attached) {
212
- if (this.loader.services.detachedBlobStorage !== undefined) {
213
- return new containerStorageAdapter_1.BlobOnlyStorage(this.loader.services.detachedBlobStorage, this.mc.logger);
214
- }
215
- this.mc.logger.sendErrorEvent({
216
- eventName: "NoRealStorageInDetachedContainer",
217
- });
218
- throw new Error("Real storage calls not allowed in Unattached container");
219
- }
220
- return this.storageService;
221
- });
220
+ this.storageService = new containerStorageAdapter_1.ContainerStorageAdapter(this.loader.services.detachedBlobStorage, this.mc.logger, this.options.summarizeProtocolTree === true
221
+ ? () => this.captureProtocolSummary()
222
+ : undefined);
222
223
  const isDomAvailable = typeof document === "object" &&
223
224
  document !== null &&
224
225
  typeof document.addEventListener === "function" &&
@@ -275,13 +276,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
275
276
  /**
276
277
  * Load an existing container.
277
278
  */
278
- static async load(loader, loadOptions, pendingLocalState) {
279
+ static async load(loader, loadOptions, pendingLocalState, protocolHandlerBuilder) {
279
280
  const container = new Container(loader, {
280
281
  clientDetailsOverride: loadOptions.clientDetailsOverride,
281
282
  resolvedUrl: loadOptions.resolvedUrl,
282
283
  canReconnect: loadOptions.canReconnect,
283
284
  serializedContainerState: pendingLocalState,
284
- });
285
+ }, protocolHandlerBuilder);
285
286
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
286
287
  var _a, _b;
287
288
  const version = loadOptions.version;
@@ -315,8 +316,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
315
316
  /**
316
317
  * Create a new container in a detached state.
317
318
  */
318
- static async createDetached(loader, codeDetails) {
319
- const container = new Container(loader, {});
319
+ static async createDetached(loader, codeDetails, protocolHandlerBuilder) {
320
+ const container = new Container(loader, {}, protocolHandlerBuilder);
320
321
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "CreateDetached" }, async (_event) => {
321
322
  await container.createDetached(codeDetails);
322
323
  return container;
@@ -326,8 +327,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
326
327
  * Create a new container in a detached state that is initialized with a
327
328
  * snapshot from a previous detached container.
328
329
  */
329
- static async rehydrateDetachedFromSnapshot(loader, snapshot) {
330
- const container = new Container(loader, {});
330
+ static async rehydrateDetachedFromSnapshot(loader, snapshot, protocolHandlerBuilder) {
331
+ const container = new Container(loader, {}, protocolHandlerBuilder);
331
332
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "RehydrateDetachedFromSnapshot" }, async (_event) => {
332
333
  const deserializedSummary = JSON.parse(snapshot);
333
334
  await container.rehydrateDetachedFromSnapshot(deserializedSummary);
@@ -339,7 +340,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
339
340
  // Only transition states if currently loading
340
341
  if (this._lifecycleState === "loading") {
341
342
  // Propagate current connection state through the system.
342
- this.propagateConnectionState();
343
+ this.propagateConnectionState(true /* initial transition */);
343
344
  this._lifecycleState = "loaded";
344
345
  }
345
346
  }
@@ -347,13 +348,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
347
348
  return (this._lifecycleState === "closing" || this._lifecycleState === "closed");
348
349
  }
349
350
  get storage() {
350
- return this._storage;
351
- }
352
- get storageService() {
353
- if (this._storageService === undefined) {
354
- throw new Error("Attempted to access storageService before it was defined");
355
- }
356
- return this._storageService;
351
+ return this.storageService;
357
352
  }
358
353
  get context() {
359
354
  if (this._context === undefined) {
@@ -394,7 +389,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
394
389
  return this.connectionStateHandler.connectionState;
395
390
  }
396
391
  get connected() {
397
- return this.connectionStateHandler.connected;
392
+ return this.connectionStateHandler.connectionState === connectionState_1.ConnectionState.Connected;
398
393
  }
399
394
  /**
400
395
  * Service configuration details. If running in offline mode will be undefined otherwise will contain service
@@ -408,7 +403,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
408
403
  * Set once this.connected is true, otherwise undefined
409
404
  */
410
405
  get clientId() {
411
- return this.connectionStateHandler.clientId;
406
+ return this._clientId;
412
407
  }
413
408
  /**
414
409
  * The server provided claims of the client.
@@ -440,12 +435,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
440
435
  * Retrieves the audience associated with the document
441
436
  */
442
437
  get audience() {
443
- return this._audience;
438
+ return this.protocolHandler.audience;
444
439
  }
445
440
  /**
446
441
  * Returns true if container is dirty.
447
442
  * Which means data loss if container is closed at that same moment
448
- * Most likely that happens when there is no network connection to ordering service
443
+ * Most likely that happens when there is no network connection to Relay Service
449
444
  */
450
445
  get isDirty() {
451
446
  return this._dirtyContainer;
@@ -470,7 +465,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
470
465
  (0, common_utils_1.assert)(this._lifecycleState === "closed", 0x314 /* Container properly closed */);
471
466
  }
472
467
  closeCore(error) {
473
- var _a, _b, _c, _d;
468
+ var _a, _b, _c;
474
469
  (0, common_utils_1.assert)(!this.closed, 0x315 /* re-entrancy */);
475
470
  try {
476
471
  // Ensure that we raise all key events even if one of these throws
@@ -485,11 +480,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
485
480
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
486
481
  this.connectionStateHandler.dispose();
487
482
  (_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
488
- (_c = this._storageService) === null || _c === void 0 ? void 0 : _c.dispose();
483
+ this.storageService.dispose();
489
484
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
490
485
  // about file, like file being overwritten in storage, but client having stale local cache.
491
486
  // Driver need to ensure all caches are cleared on critical errors
492
- (_d = this.service) === null || _d === void 0 ? void 0 : _d.dispose(error);
487
+ (_c = this.service) === null || _c === void 0 ? void 0 : _c.dispose(error);
493
488
  }
494
489
  catch (exception) {
495
490
  this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
@@ -511,7 +506,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
511
506
  (0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
512
507
  (0, common_utils_1.assert)(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
513
508
  (0, common_utils_1.assert)(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
514
- (0, common_utils_1.assert)(this._protocolHandler.attributes.term !== undefined, 0x30b /* Must have a valid protocol handler instance */);
509
+ (0, common_utils_1.assert)(this._protocolHandler.attributes.term !== undefined, 0x37e /* Must have a valid protocol handler instance */);
515
510
  const pendingState = {
516
511
  pendingRuntimeState: this.context.getPendingLocalState(),
517
512
  url: this.resolvedUrl.url,
@@ -519,6 +514,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
519
514
  term: this._protocolHandler.attributes.term,
520
515
  clientId: this.clientId,
521
516
  };
517
+ this.mc.logger.sendTelemetryEvent({ eventName: "CloseAndGetPendingLocalState" });
522
518
  this.close();
523
519
  return JSON.stringify(pendingState);
524
520
  }
@@ -575,7 +571,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
575
571
  const resolvedUrl = this.service.resolvedUrl;
576
572
  (0, driver_utils_1.ensureFluidResolvedUrl)(resolvedUrl);
577
573
  this._resolvedUrl = resolvedUrl;
578
- await this.connectStorageService();
574
+ await this.storageService.connectToService(this.service);
579
575
  if (hasAttachmentBlobs) {
580
576
  // upload blobs to storage
581
577
  (0, common_utils_1.assert)(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
@@ -605,8 +601,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
605
601
  }
606
602
  this._attachState = container_definitions_1.AttachState.Attached;
607
603
  this.emit("attached");
608
- // Propagate current connection state through the system.
609
- this.propagateConnectionState();
610
604
  if (!this.closed) {
611
605
  this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
612
606
  }
@@ -745,9 +739,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
745
739
  /**
746
740
  * Load container.
747
741
  *
748
- * @param specifiedVersion - one of the following
749
- * - undefined - fetch latest snapshot
750
- * - otherwise, version sha to load snapshot
742
+ * @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
751
743
  */
752
744
  async load(specifiedVersion, loadMode, pendingLocalState) {
753
745
  if (this._resolvedUrl === undefined) {
@@ -770,11 +762,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
770
762
  this.connectToDeltaStream(connectionArgs);
771
763
  }
772
764
  if (!pendingLocalState) {
773
- await this.connectStorageService();
765
+ await this.storageService.connectToService(this.service);
774
766
  }
775
767
  else {
776
768
  // if we have pendingLocalState we can load without storage; don't wait for connection
777
- this.connectStorageService().catch((error) => this.close(error));
769
+ this.storageService.connectToService(this.service).catch((error) => this.close(error));
778
770
  }
779
771
  this._attachState = container_definitions_1.AttachState.Attached;
780
772
  // Fetch specified snapshot.
@@ -809,9 +801,16 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
809
801
  }
810
802
  // ...load in the existing quorum
811
803
  // Initialize the protocol handler
812
- this._protocolHandler = pendingLocalState === undefined
813
- ? await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot)
814
- : await this.initializeProtocolState(attributes, pendingLocalState.protocol.members, pendingLocalState.protocol.proposals, pendingLocalState.protocol.values);
804
+ if (pendingLocalState === undefined) {
805
+ await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot);
806
+ }
807
+ else {
808
+ this.initializeProtocolState(attributes, {
809
+ members: pendingLocalState.protocol.members,
810
+ proposals: pendingLocalState.protocol.proposals,
811
+ values: pendingLocalState.protocol.values,
812
+ });
813
+ }
815
814
  const codeDetails = this.getCodeDetailsFromQuorum();
816
815
  await this.instantiateContext(true, // existing
817
816
  codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
@@ -865,9 +864,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
865
864
  await this.attachDeltaManagerOpHandler(attributes);
866
865
  // Need to just seed the source data in the code quorum. Quorum itself is empty
867
866
  const qValues = (0, quorum_1.initQuorumValuesFromCodeDetails)(source);
868
- this._protocolHandler = await this.initializeProtocolState(attributes, [], // members
869
- [], // proposals
870
- qValues);
867
+ this.initializeProtocolState(attributes, {
868
+ members: [],
869
+ proposals: [],
870
+ values: qValues,
871
+ });
871
872
  // The load context - given we seeded the quorum - will be great
872
873
  await this.instantiateContextDetached(false);
873
874
  this.setLoaded();
@@ -878,38 +879,22 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
878
879
  delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
879
880
  }
880
881
  const snapshotTree = (0, utils_1.getSnapshotTreeFromSerializedContainer)(detachedContainerSnapshot);
881
- this._storage.loadSnapshotForRehydratingContainer(snapshotTree);
882
- const attributes = await this.getDocumentAttributes(this._storage, snapshotTree);
882
+ this.storageService.loadSnapshotForRehydratingContainer(snapshotTree);
883
+ const attributes = await this.getDocumentAttributes(this.storageService, snapshotTree);
883
884
  await this.attachDeltaManagerOpHandler(attributes);
884
885
  // Initialize the protocol handler
885
886
  const baseTree = (0, utils_1.getProtocolSnapshotTree)(snapshotTree);
886
- const qValues = await (0, driver_utils_1.readAndParse)(this._storage, baseTree.blobs.quorumValues);
887
+ const qValues = await (0, driver_utils_1.readAndParse)(this.storageService, baseTree.blobs.quorumValues);
887
888
  const codeDetails = (0, quorum_1.getCodeDetailsFromQuorumValues)(qValues);
888
- this._protocolHandler =
889
- await this.initializeProtocolState(attributes, [], // members
890
- [], // proposals
891
- codeDetails !== undefined ? (0, quorum_1.initQuorumValuesFromCodeDetails)(codeDetails) : []);
889
+ this.initializeProtocolState(attributes, {
890
+ members: [],
891
+ proposals: [],
892
+ values: codeDetails !== undefined ? (0, quorum_1.initQuorumValuesFromCodeDetails)(codeDetails) : [],
893
+ });
892
894
  await this.instantiateContextDetached(true, // existing
893
895
  snapshotTree);
894
896
  this.setLoaded();
895
897
  }
896
- async connectStorageService() {
897
- var _a, _b;
898
- if (this._storageService !== undefined) {
899
- return;
900
- }
901
- (0, common_utils_1.assert)(this.service !== undefined, 0x1ef /* "services must be defined" */);
902
- const storageService = await this.service.connectToStorage();
903
- this._storageService =
904
- new retriableDocumentStorageService_1.RetriableDocumentStorageService(storageService, this.mc.logger);
905
- if (this.options.summarizeProtocolTree === true) {
906
- this.mc.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
907
- this._storageService =
908
- new protocolTreeDocumentStorageService_1.ProtocolTreeStorageService(this._storageService, () => this.captureProtocolSummary());
909
- }
910
- // ensure we did not lose that policy in the process of wrapping
911
- (0, common_utils_1.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" */);
912
- }
913
898
  async getDocumentAttributes(storage, tree) {
914
899
  if (tree === undefined) {
915
900
  return {
@@ -930,22 +915,26 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
930
915
  return attributes;
931
916
  }
932
917
  async initializeProtocolStateFromSnapshot(attributes, storage, snapshot) {
933
- let members = [];
934
- let proposals = [];
935
- let values = [];
918
+ const quorumSnapshot = {
919
+ members: [],
920
+ proposals: [],
921
+ values: [],
922
+ };
936
923
  if (snapshot !== undefined) {
937
924
  const baseTree = (0, utils_1.getProtocolSnapshotTree)(snapshot);
938
- [members, proposals, values] = await Promise.all([
925
+ [quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] = await Promise.all([
939
926
  (0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumMembers),
940
927
  (0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumProposals),
941
928
  (0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumValues),
942
929
  ]);
943
930
  }
944
- const protocolHandler = await this.initializeProtocolState(attributes, members, proposals, values);
945
- return protocolHandler;
931
+ this.initializeProtocolState(attributes, quorumSnapshot);
946
932
  }
947
- async initializeProtocolState(attributes, members, proposals, values) {
948
- const protocol = new protocol_base_1.ProtocolOpHandlerWithClientValidation(attributes.minimumSequenceNumber, attributes.sequenceNumber, attributes.term, members, proposals, values, (key, value) => this.submitMessage(protocol_definitions_1.MessageType.Propose, { key, value }));
933
+ initializeProtocolState(attributes, quorumSnapshot) {
934
+ var _a, _b;
935
+ const protocolHandlerBuilder = (_a = this.protocolHandlerBuilder) !== null && _a !== void 0 ? _a : ((...args) => new protocol_1.ProtocolHandler(...args, new audience_1.Audience()));
936
+ const protocol = protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) => this.submitMessage(protocol_definitions_1.MessageType.Propose, JSON.stringify({ key, value })), (_b = this._initialClients) !== null && _b !== void 0 ? _b : []);
937
+ this._initialClients = undefined;
949
938
  const protocolLogger = telemetry_utils_1.ChildLogger.create(this.subLogger, "ProtocolHandler");
950
939
  protocol.quorum.on("error", (error) => {
951
940
  protocolLogger.sendErrorEvent(error);
@@ -970,7 +959,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
970
959
  });
971
960
  }
972
961
  });
973
- return protocol;
962
+ // we need to make sure this member get set in a synchronous context,
963
+ // or other things can happen after the object that will be set is created, but not yet set
964
+ // this was breaking this._initialClients handling
965
+ //
966
+ this._protocolHandler = protocol;
974
967
  }
975
968
  captureProtocolSummary() {
976
969
  const quorumSnapshot = this.protocolHandler.snapshot();
@@ -1039,12 +1032,20 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1039
1032
  deltaManager.inbound.pause();
1040
1033
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1041
1034
  deltaManager.inboundSignal.pause();
1042
- deltaManager.on("connect", (details, opsBehind) => {
1035
+ deltaManager.on("connect", (details, _opsBehind) => {
1043
1036
  var _a;
1044
- // Back-compat for new client and old server.
1045
- this._audience.clear();
1046
- for (const priorClient of (_a = details.initialClients) !== null && _a !== void 0 ? _a : []) {
1047
- this._audience.addMember(priorClient.clientId, priorClient.client);
1037
+ if (this._protocolHandler === undefined) {
1038
+ // Store the initial clients so that they can be submitted to the
1039
+ // protocol handler when it is created.
1040
+ this._initialClients = details.initialClients;
1041
+ }
1042
+ else {
1043
+ // When reconnecting, the protocol handler is already created,
1044
+ // so we can update the audience right now.
1045
+ this._protocolHandler.audience.clear();
1046
+ for (const priorClient of (_a = details.initialClients) !== null && _a !== void 0 ? _a : []) {
1047
+ this._protocolHandler.audience.addMember(priorClient.clientId, priorClient.client);
1048
+ }
1048
1049
  }
1049
1050
  this.connectionStateHandler.receivedConnectEvent(this.connectionMode, details);
1050
1051
  });
@@ -1063,6 +1064,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1063
1064
  this.emit("warning", warn);
1064
1065
  });
1065
1066
  deltaManager.on("readonly", (readonly) => {
1067
+ this.setContextConnectedState(this.connectionState === connectionState_1.ConnectionState.Connected, readonly);
1066
1068
  this.emit("readonly", readonly);
1067
1069
  });
1068
1070
  deltaManager.on("closed", (error) => {
@@ -1105,12 +1107,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1105
1107
  opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
1106
1108
  }
1107
1109
  }
1108
- if (this.firstConnection) {
1109
- connectionInitiationReason = "InitialConnect";
1110
- }
1111
- else {
1112
- connectionInitiationReason = "AutoReconnect";
1113
- }
1110
+ connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
1114
1111
  }
1115
1112
  this.mc.logger.sendPerformanceEvent(Object.assign({ eventName: `ConnectionStateChange_${connectionState_1.ConnectionState[value]}`, from: connectionState_1.ConnectionState[oldState], duration,
1116
1113
  durationFromDisconnected,
@@ -1121,49 +1118,64 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1121
1118
  this.firstConnection = false;
1122
1119
  }
1123
1120
  }
1124
- propagateConnectionState() {
1121
+ propagateConnectionState(initialTransition) {
1125
1122
  var _a;
1123
+ // When container loaded, we want to propagate initial connection state.
1124
+ // After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
1125
+ // This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
1126
+ if (!initialTransition &&
1127
+ this.connectionState !== connectionState_1.ConnectionState.Connected &&
1128
+ this.connectionState !== connectionState_1.ConnectionState.Disconnected) {
1129
+ return;
1130
+ }
1131
+ const state = this.connectionState === connectionState_1.ConnectionState.Connected;
1126
1132
  const logOpsOnReconnect = this.connectionState === connectionState_1.ConnectionState.Connected &&
1127
1133
  !this.firstConnection &&
1128
1134
  this.connectionMode === "write";
1129
1135
  if (logOpsOnReconnect) {
1130
1136
  this.messageCountAfterDisconnection = 0;
1131
1137
  }
1132
- const state = this.connectionState === connectionState_1.ConnectionState.Connected;
1133
1138
  // Both protocol and context should not be undefined if we got so far.
1134
- if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1135
- this.context.setConnectionState(state, this.clientId);
1136
- }
1139
+ this.setContextConnectedState(state, (_a = this._deltaManager.connectionManager.readOnlyInfo.readonly) !== null && _a !== void 0 ? _a : false);
1137
1140
  this.protocolHandler.setConnectionState(state, this.clientId);
1138
1141
  (0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, state, this.clientId);
1139
1142
  if (logOpsOnReconnect) {
1140
1143
  this.mc.logger.sendTelemetryEvent({ eventName: "OpsSentOnReconnect", count: this.messageCountAfterDisconnection });
1141
1144
  }
1142
1145
  }
1146
+ // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
1143
1147
  submitContainerMessage(type, contents, batch, metadata) {
1144
- const outboundMessageType = type;
1145
- switch (outboundMessageType) {
1148
+ switch (type) {
1146
1149
  case protocol_definitions_1.MessageType.Operation:
1147
- case protocol_definitions_1.MessageType.RemoteHelp:
1148
- break;
1149
- case protocol_definitions_1.MessageType.Summarize: {
1150
- // github #6451: this is only needed for staging so the server
1151
- // know when the protocol tree is included
1152
- // this can be removed once all clients send
1153
- // protocol tree by default
1154
- const summary = contents;
1155
- if (summary.details === undefined) {
1156
- summary.details = {};
1157
- }
1158
- summary.details.includesProtocolTree =
1159
- this.options.summarizeProtocolTree === true;
1160
- break;
1161
- }
1150
+ return this.submitMessage(type, JSON.stringify(contents), batch, metadata);
1151
+ case protocol_definitions_1.MessageType.Summarize:
1152
+ return this.submitSummaryMessage(contents);
1162
1153
  default:
1163
1154
  this.close(new container_utils_1.GenericError("invalidContainerSubmitOpType", undefined /* error */, { messageType: type }));
1164
1155
  return -1;
1165
1156
  }
1166
- return this.submitMessage(type, contents, batch, metadata);
1157
+ }
1158
+ /** @returns clientSequenceNumber of last message in a batch */
1159
+ submitBatch(batch) {
1160
+ let clientSequenceNumber = -1;
1161
+ for (const message of batch) {
1162
+ clientSequenceNumber = this.submitMessage(protocol_definitions_1.MessageType.Operation, message.contents, true, // batch
1163
+ message.metadata);
1164
+ }
1165
+ this._deltaManager.flush();
1166
+ return clientSequenceNumber;
1167
+ }
1168
+ submitSummaryMessage(summary) {
1169
+ // github #6451: this is only needed for staging so the server
1170
+ // know when the protocol tree is included
1171
+ // this can be removed once all clients send
1172
+ // protocol tree by default
1173
+ if (summary.details === undefined) {
1174
+ summary.details = {};
1175
+ }
1176
+ summary.details.includesProtocolTree =
1177
+ this.options.summarizeProtocolTree === true;
1178
+ return this.submitMessage(protocol_definitions_1.MessageType.Summarize, JSON.stringify(summary), false /* batch */);
1167
1179
  }
1168
1180
  submitMessage(type, contents, batch, metadata) {
1169
1181
  var _a;
@@ -1178,22 +1190,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1178
1190
  processRemoteMessage(message) {
1179
1191
  const local = this.clientId === message.clientId;
1180
1192
  // Allow the protocol handler to process the message
1181
- let result = { immediateNoOp: false };
1182
- try {
1183
- result = this.protocolHandler.processMessage(message, local);
1184
- }
1185
- catch (error) {
1186
- this.close((0, telemetry_utils_1.wrapError)(error, (errorMessage) => new container_utils_1.DataCorruptionError(errorMessage, (0, container_utils_1.extractSafePropertiesFromMessage)(message))));
1187
- }
1188
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
1189
- if ((0, driver_utils_1.isUnpackedRuntimeMessage)(message) && !(0, driver_utils_1.isRuntimeMessage)(message)) {
1190
- this.mc.logger.sendTelemetryEvent({ eventName: "UnpackedRuntimeMessage", type: message.type });
1191
- }
1192
- // Forward non system messages to the loaded runtime for processing
1193
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
1194
- if ((0, driver_utils_1.isRuntimeMessage)(message) || (0, driver_utils_1.isUnpackedRuntimeMessage)(message)) {
1195
- this.context.process(message, local, undefined);
1196
- }
1193
+ const result = this.protocolHandler.processMessage(message, local);
1194
+ // Forward messages to the loaded runtime for processing
1195
+ this.context.process(message, local, undefined);
1197
1196
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
1198
1197
  if (this.activeConnection()) {
1199
1198
  if (this.collabWindowTracker === undefined) {
@@ -1202,15 +1201,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1202
1201
  // clients.
1203
1202
  // All existing will continue to use settings they got earlier.
1204
1203
  (0, common_utils_1.assert)(this.serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1205
- this.collabWindowTracker = new collabWindowTracker_1.CollabWindowTracker((type, contents) => {
1204
+ this.collabWindowTracker = new collabWindowTracker_1.CollabWindowTracker((type) => {
1206
1205
  (0, common_utils_1.assert)(this.activeConnection(), 0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
1207
- this.submitMessage(type, contents);
1206
+ this.submitMessage(type);
1208
1207
  }, this.serviceConfiguration.noopTimeFrequency, this.serviceConfiguration.noopCountFrequency);
1209
1208
  }
1210
1209
  this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
1211
1210
  }
1212
1211
  this.emit("op", message);
1213
- return result;
1214
1212
  }
1215
1213
  submitSignal(message) {
1216
1214
  this._deltaManager.submitSignal(JSON.stringify(message));
@@ -1218,15 +1216,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1218
1216
  processSignal(message) {
1219
1217
  // No clientId indicates a system signal message.
1220
1218
  if (message.clientId === null) {
1221
- const innerContent = message.content;
1222
- if (innerContent.type === protocol_definitions_1.MessageType.ClientJoin) {
1223
- const newClient = innerContent.content;
1224
- this._audience.addMember(newClient.clientId, newClient.client);
1225
- }
1226
- else if (innerContent.type === protocol_definitions_1.MessageType.ClientLeave) {
1227
- const leftClientId = innerContent.content;
1228
- this._audience.removeMember(leftClientId);
1229
- }
1219
+ this.protocolHandler.processSignal(message);
1230
1220
  }
1231
1221
  else {
1232
1222
  const local = this.clientId === message.clientId;
@@ -1265,7 +1255,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1265
1255
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1266
1256
  // are set. Global requests will still go directly to the loader
1267
1257
  const loader = new loader_1.RelativeLoader(this, this.loader);
1268
- this._context = await containerContext_1.ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new deltaManagerProxy_1.DeltaManagerProxy(this._deltaManager), new quorum_1.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);
1258
+ this._context = await containerContext_1.ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new deltaManagerProxy_1.DeltaManagerProxy(this._deltaManager), new quorum_1.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);
1269
1259
  this.emit("contextChanged", codeDetails);
1270
1260
  }
1271
1261
  updateDirtyContainerState(dirty) {
@@ -1278,6 +1268,24 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1278
1268
  logContainerError(warning) {
1279
1269
  this.mc.logger.sendErrorEvent({ eventName: "ContainerWarning" }, warning);
1280
1270
  }
1271
+ /**
1272
+ * Set the connected state of the ContainerContext
1273
+ * This controls the "connected" state of the ContainerRuntime as well
1274
+ * @param state - Is the container currently connected?
1275
+ * @param readonly - Is the container in readonly mode?
1276
+ */
1277
+ setContextConnectedState(state, readonly) {
1278
+ var _a;
1279
+ if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1280
+ /**
1281
+ * We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
1282
+ * ops getting through to the DeltaManager.
1283
+ * The ContainerRuntime's "connected" state simply means it is ok to send ops
1284
+ * See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
1285
+ */
1286
+ this.context.setConnectionState(state && !readonly, this.clientId);
1287
+ }
1288
+ }
1281
1289
  }
1282
1290
  exports.Container = Container;
1283
1291
  Container.version = "^0.1.0";