@fluidframework/container-loader 1.2.6 → 2.0.0-dev.1.3.0.96595

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 (157) hide show
  1. package/.mocharc.js +12 -0
  2. package/dist/audience.d.ts +2 -6
  3. package/dist/audience.d.ts.map +1 -1
  4. package/dist/audience.js +6 -11
  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 +43 -22
  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 +29 -17
  27. package/dist/container.d.ts.map +1 -1
  28. package/dist/container.js +181 -171
  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 +39 -12
  43. package/dist/deltaManager.js.map +1 -1
  44. package/dist/deltaManagerProxy.d.ts +4 -1
  45. package/dist/deltaManagerProxy.d.ts.map +1 -1
  46. package/dist/deltaQueue.d.ts +9 -2
  47. package/dist/deltaQueue.d.ts.map +1 -1
  48. package/dist/deltaQueue.js +31 -26
  49. package/dist/deltaQueue.js.map +1 -1
  50. package/dist/index.d.ts +1 -0
  51. package/dist/index.d.ts.map +1 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/loader.d.ts +8 -1
  54. package/dist/loader.d.ts.map +1 -1
  55. package/dist/loader.js +4 -3
  56. package/dist/loader.js.map +1 -1
  57. package/dist/packageVersion.d.ts +1 -1
  58. package/dist/packageVersion.d.ts.map +1 -1
  59. package/dist/packageVersion.js +1 -1
  60. package/dist/packageVersion.js.map +1 -1
  61. package/dist/protocol.d.ts +27 -0
  62. package/dist/protocol.d.ts.map +1 -0
  63. package/dist/protocol.js +79 -0
  64. package/dist/protocol.js.map +1 -0
  65. package/dist/protocolTreeDocumentStorageService.d.ts +1 -1
  66. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  67. package/dist/retriableDocumentStorageService.d.ts +2 -2
  68. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  69. package/dist/retriableDocumentStorageService.js +2 -2
  70. package/dist/retriableDocumentStorageService.js.map +1 -1
  71. package/lib/audience.d.ts +2 -6
  72. package/lib/audience.d.ts.map +1 -1
  73. package/lib/audience.js +6 -11
  74. package/lib/audience.js.map +1 -1
  75. package/lib/catchUpMonitor.d.ts +29 -0
  76. package/lib/catchUpMonitor.d.ts.map +1 -0
  77. package/lib/catchUpMonitor.js +39 -0
  78. package/lib/catchUpMonitor.js.map +1 -0
  79. package/lib/collabWindowTracker.d.ts +1 -1
  80. package/lib/collabWindowTracker.d.ts.map +1 -1
  81. package/lib/collabWindowTracker.js +13 -5
  82. package/lib/collabWindowTracker.js.map +1 -1
  83. package/lib/connectionManager.d.ts +5 -5
  84. package/lib/connectionManager.d.ts.map +1 -1
  85. package/lib/connectionManager.js +44 -25
  86. package/lib/connectionManager.js.map +1 -1
  87. package/lib/connectionState.d.ts +0 -5
  88. package/lib/connectionState.d.ts.map +1 -1
  89. package/lib/connectionState.js +0 -5
  90. package/lib/connectionState.js.map +1 -1
  91. package/lib/connectionStateHandler.d.ts +84 -22
  92. package/lib/connectionStateHandler.d.ts.map +1 -1
  93. package/lib/connectionStateHandler.js +171 -59
  94. package/lib/connectionStateHandler.js.map +1 -1
  95. package/lib/container.d.ts +29 -17
  96. package/lib/container.d.ts.map +1 -1
  97. package/lib/container.js +184 -174
  98. package/lib/container.js.map +1 -1
  99. package/lib/containerContext.d.ts +18 -7
  100. package/lib/containerContext.d.ts.map +1 -1
  101. package/lib/containerContext.js +19 -9
  102. package/lib/containerContext.js.map +1 -1
  103. package/lib/containerStorageAdapter.d.ts +11 -25
  104. package/lib/containerStorageAdapter.d.ts.map +1 -1
  105. package/lib/containerStorageAdapter.js +51 -16
  106. package/lib/containerStorageAdapter.js.map +1 -1
  107. package/lib/contracts.d.ts +5 -5
  108. package/lib/contracts.js.map +1 -1
  109. package/lib/deltaManager.d.ts +4 -1
  110. package/lib/deltaManager.d.ts.map +1 -1
  111. package/lib/deltaManager.js +41 -14
  112. package/lib/deltaManager.js.map +1 -1
  113. package/lib/deltaManagerProxy.d.ts +4 -1
  114. package/lib/deltaManagerProxy.d.ts.map +1 -1
  115. package/lib/deltaQueue.d.ts +9 -2
  116. package/lib/deltaQueue.d.ts.map +1 -1
  117. package/lib/deltaQueue.js +32 -27
  118. package/lib/deltaQueue.js.map +1 -1
  119. package/lib/index.d.ts +1 -0
  120. package/lib/index.d.ts.map +1 -1
  121. package/lib/index.js.map +1 -1
  122. package/lib/loader.d.ts +8 -1
  123. package/lib/loader.d.ts.map +1 -1
  124. package/lib/loader.js +4 -3
  125. package/lib/loader.js.map +1 -1
  126. package/lib/packageVersion.d.ts +1 -1
  127. package/lib/packageVersion.d.ts.map +1 -1
  128. package/lib/packageVersion.js +1 -1
  129. package/lib/packageVersion.js.map +1 -1
  130. package/lib/protocol.d.ts +27 -0
  131. package/lib/protocol.d.ts.map +1 -0
  132. package/lib/protocol.js +75 -0
  133. package/lib/protocol.js.map +1 -0
  134. package/lib/protocolTreeDocumentStorageService.d.ts +1 -1
  135. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  136. package/lib/retriableDocumentStorageService.d.ts +2 -2
  137. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  138. package/lib/retriableDocumentStorageService.js +2 -2
  139. package/lib/retriableDocumentStorageService.js.map +1 -1
  140. package/package.json +27 -19
  141. package/src/audience.ts +8 -14
  142. package/src/catchUpMonitor.ts +59 -0
  143. package/src/collabWindowTracker.ts +15 -6
  144. package/src/connectionManager.ts +56 -33
  145. package/src/connectionState.ts +0 -6
  146. package/src/connectionStateHandler.ts +235 -70
  147. package/src/container.ts +241 -218
  148. package/src/containerContext.ts +22 -8
  149. package/src/containerStorageAdapter.ts +71 -16
  150. package/src/contracts.ts +7 -7
  151. package/src/deltaManager.ts +48 -15
  152. package/src/deltaQueue.ts +34 -28
  153. package/src/index.ts +4 -0
  154. package/src/loader.ts +14 -3
  155. package/src/packageVersion.ts +1 -1
  156. package/src/protocol.ts +120 -0
  157. 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!" */);
@@ -105,9 +112,22 @@ exports.waitContainerToCatchUp = waitContainerToCatchUp;
105
112
  const getCodeProposal =
106
113
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
107
114
  (quorum) => { var _a; return (_a = quorum.get("code")) !== null && _a !== void 0 ? _a : quorum.get("code2"); };
115
+ /**
116
+ * Helper function to report to telemetry cases where operation takes longer than expected (1s)
117
+ * @param logger - logger to use
118
+ * @param eventName - event name
119
+ * @param action - functor to call and measure
120
+ */
121
+ async function ReportIfTooLong(logger, eventName, action) {
122
+ const event = telemetry_utils_1.PerformanceEvent.start(logger, { eventName });
123
+ const props = await action();
124
+ if (event.duration > 1000) {
125
+ event.end(props);
126
+ }
127
+ }
108
128
  const summarizerClientType = "summarizer";
109
129
  class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
110
- constructor(loader, config) {
130
+ constructor(loader, config, protocolHandlerBuilder) {
111
131
  var _a, _b;
112
132
  super((name, error) => {
113
133
  this.mc.logger.sendErrorEvent({
@@ -116,6 +136,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
116
136
  }, error);
117
137
  });
118
138
  this.loader = loader;
139
+ this.protocolHandlerBuilder = protocolHandlerBuilder;
119
140
  // Tells if container can reconnect on losing fist connection
120
141
  // If false, container gets closed on loss of connection.
121
142
  this._canReconnect = true;
@@ -129,7 +150,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
129
150
  this.attachStarted = false;
130
151
  this._dirtyContainer = false;
131
152
  this.setAutoReconnectTime = common_utils_1.performance.now();
132
- this._audience = new audience_1.Audience();
133
153
  this.clientDetailsOverride = config.clientDetailsOverride;
134
154
  this._resolvedUrl = config.resolvedUrl;
135
155
  if (config.canReconnect !== undefined) {
@@ -149,6 +169,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
149
169
  containerAttachState: () => this._attachState,
150
170
  containerLifecycleState: () => this._lifecycleState,
151
171
  containerConnectionState: () => connectionState_1.ConnectionState[this.connectionState],
172
+ serializedContainer: config.serializedContainerState !== undefined,
152
173
  },
153
174
  // we need to be judicious with our logging here to avoid generating too much data
154
175
  // all data logged here should be broadly applicable, and not specific to a
@@ -161,6 +182,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
161
182
  containerLoadedFromVersionId: () => { var _a; return (_a = this.loadedFromVersion) === null || _a === void 0 ? void 0 : _a.id; },
162
183
  containerLoadedFromVersionDate: () => { var _a; return (_a = this.loadedFromVersion) === null || _a === void 0 ? void 0 : _a.date; },
163
184
  // message information to associate errors with the specific execution state
185
+ // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
164
186
  dmLastMsqSeqNumber: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.sequenceNumber; },
165
187
  dmLastMsqSeqTimestamp: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.timestamp; },
166
188
  dmLastMsqSeqClientId: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientId; },
@@ -171,9 +193,19 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
171
193
  this.mc = (0, telemetry_utils_1.loggerToMonitoringContext)(telemetry_utils_1.ChildLogger.create(this.subLogger, "Container"));
172
194
  const summarizeProtocolTree = (_a = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree")) !== null && _a !== void 0 ? _a : this.loader.services.options.summarizeProtocolTree;
173
195
  this.options = Object.assign(Object.assign({}, this.loader.services.options), { summarizeProtocolTree });
174
- this.connectionStateHandler = new connectionStateHandler_1.ConnectionStateHandler({
175
- quorumClients: () => { var _a; return (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.quorum; },
176
- 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
+ },
177
209
  shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
178
210
  maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
179
211
  logConnectionIssue: (eventName, details) => {
@@ -181,29 +213,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
181
213
  // its own join op. Attempt recovery option.
182
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) })));
183
215
  },
184
- connectionStateChanged: () => {
185
- // Fire events only if container is fully loaded and not closed
186
- if (this._lifecycleState === "loaded") {
187
- this.propagateConnectionState();
188
- }
189
- },
190
- }, this.mc.logger, (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId);
216
+ }, this.deltaManager, this._clientId);
191
217
  this.on(savedContainerEvent, () => {
192
218
  this.connectionStateHandler.containerSaved();
193
219
  });
194
- this._deltaManager = this.createDeltaManager();
195
- this._storage = new containerStorageAdapter_1.ContainerStorageAdapter(() => {
196
- if (this.attachState !== container_definitions_1.AttachState.Attached) {
197
- if (this.loader.services.detachedBlobStorage !== undefined) {
198
- return new containerStorageAdapter_1.BlobOnlyStorage(this.loader.services.detachedBlobStorage, this.mc.logger);
199
- }
200
- this.mc.logger.sendErrorEvent({
201
- eventName: "NoRealStorageInDetachedContainer",
202
- });
203
- throw new Error("Real storage calls not allowed in Unattached container");
204
- }
205
- return this.storageService;
206
- });
220
+ this.storageService = new containerStorageAdapter_1.ContainerStorageAdapter(this.loader.services.detachedBlobStorage, this.mc.logger, this.options.summarizeProtocolTree === true
221
+ ? () => this.captureProtocolSummary()
222
+ : undefined);
207
223
  const isDomAvailable = typeof document === "object" &&
208
224
  document !== null &&
209
225
  typeof document.addEventListener === "function" &&
@@ -260,13 +276,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
260
276
  /**
261
277
  * Load an existing container.
262
278
  */
263
- static async load(loader, loadOptions, pendingLocalState) {
279
+ static async load(loader, loadOptions, pendingLocalState, protocolHandlerBuilder) {
264
280
  const container = new Container(loader, {
265
281
  clientDetailsOverride: loadOptions.clientDetailsOverride,
266
282
  resolvedUrl: loadOptions.resolvedUrl,
267
283
  canReconnect: loadOptions.canReconnect,
268
284
  serializedContainerState: pendingLocalState,
269
- });
285
+ }, protocolHandlerBuilder);
270
286
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
271
287
  var _a, _b;
272
288
  const version = loadOptions.version;
@@ -300,8 +316,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
300
316
  /**
301
317
  * Create a new container in a detached state.
302
318
  */
303
- static async createDetached(loader, codeDetails) {
304
- const container = new Container(loader, {});
319
+ static async createDetached(loader, codeDetails, protocolHandlerBuilder) {
320
+ const container = new Container(loader, {}, protocolHandlerBuilder);
305
321
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "CreateDetached" }, async (_event) => {
306
322
  await container.createDetached(codeDetails);
307
323
  return container;
@@ -311,8 +327,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
311
327
  * Create a new container in a detached state that is initialized with a
312
328
  * snapshot from a previous detached container.
313
329
  */
314
- static async rehydrateDetachedFromSnapshot(loader, snapshot) {
315
- const container = new Container(loader, {});
330
+ static async rehydrateDetachedFromSnapshot(loader, snapshot, protocolHandlerBuilder) {
331
+ const container = new Container(loader, {}, protocolHandlerBuilder);
316
332
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "RehydrateDetachedFromSnapshot" }, async (_event) => {
317
333
  const deserializedSummary = JSON.parse(snapshot);
318
334
  await container.rehydrateDetachedFromSnapshot(deserializedSummary);
@@ -324,7 +340,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
324
340
  // Only transition states if currently loading
325
341
  if (this._lifecycleState === "loading") {
326
342
  // Propagate current connection state through the system.
327
- this.propagateConnectionState();
343
+ this.propagateConnectionState(true /* initial transition */);
328
344
  this._lifecycleState = "loaded";
329
345
  }
330
346
  }
@@ -332,13 +348,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
332
348
  return (this._lifecycleState === "closing" || this._lifecycleState === "closed");
333
349
  }
334
350
  get storage() {
335
- return this._storage;
336
- }
337
- get storageService() {
338
- if (this._storageService === undefined) {
339
- throw new Error("Attempted to access storageService before it was defined");
340
- }
341
- return this._storageService;
351
+ return this.storageService;
342
352
  }
343
353
  get context() {
344
354
  if (this._context === undefined) {
@@ -379,7 +389,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
379
389
  return this.connectionStateHandler.connectionState;
380
390
  }
381
391
  get connected() {
382
- return this.connectionStateHandler.connected;
392
+ return this.connectionStateHandler.connectionState === connectionState_1.ConnectionState.Connected;
383
393
  }
384
394
  /**
385
395
  * Service configuration details. If running in offline mode will be undefined otherwise will contain service
@@ -393,7 +403,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
393
403
  * Set once this.connected is true, otherwise undefined
394
404
  */
395
405
  get clientId() {
396
- return this.connectionStateHandler.clientId;
406
+ return this._clientId;
397
407
  }
398
408
  /**
399
409
  * The server provided claims of the client.
@@ -425,12 +435,12 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
425
435
  * Retrieves the audience associated with the document
426
436
  */
427
437
  get audience() {
428
- return this._audience;
438
+ return this.protocolHandler.audience;
429
439
  }
430
440
  /**
431
441
  * Returns true if container is dirty.
432
442
  * Which means data loss if container is closed at that same moment
433
- * 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
434
444
  */
435
445
  get isDirty() {
436
446
  return this._dirtyContainer;
@@ -455,7 +465,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
455
465
  (0, common_utils_1.assert)(this._lifecycleState === "closed", 0x314 /* Container properly closed */);
456
466
  }
457
467
  closeCore(error) {
458
- var _a, _b, _c, _d;
468
+ var _a, _b, _c;
459
469
  (0, common_utils_1.assert)(!this.closed, 0x315 /* re-entrancy */);
460
470
  try {
461
471
  // Ensure that we raise all key events even if one of these throws
@@ -470,11 +480,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
470
480
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
471
481
  this.connectionStateHandler.dispose();
472
482
  (_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
473
- (_c = this._storageService) === null || _c === void 0 ? void 0 : _c.dispose();
483
+ this.storageService.dispose();
474
484
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
475
485
  // about file, like file being overwritten in storage, but client having stale local cache.
476
486
  // Driver need to ensure all caches are cleared on critical errors
477
- (_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);
478
488
  }
479
489
  catch (exception) {
480
490
  this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
@@ -496,7 +506,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
496
506
  (0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
497
507
  (0, common_utils_1.assert)(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
498
508
  (0, common_utils_1.assert)(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
499
- (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 */);
500
510
  const pendingState = {
501
511
  pendingRuntimeState: this.context.getPendingLocalState(),
502
512
  url: this.resolvedUrl.url,
@@ -504,6 +514,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
504
514
  term: this._protocolHandler.attributes.term,
505
515
  clientId: this.clientId,
506
516
  };
517
+ this.mc.logger.sendTelemetryEvent({ eventName: "CloseAndGetPendingLocalState" });
507
518
  this.close();
508
519
  return JSON.stringify(pendingState);
509
520
  }
@@ -560,7 +571,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
560
571
  const resolvedUrl = this.service.resolvedUrl;
561
572
  (0, driver_utils_1.ensureFluidResolvedUrl)(resolvedUrl);
562
573
  this._resolvedUrl = resolvedUrl;
563
- await this.connectStorageService();
574
+ await this.storageService.connectToService(this.service);
564
575
  if (hasAttachmentBlobs) {
565
576
  // upload blobs to storage
566
577
  (0, common_utils_1.assert)(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
@@ -590,8 +601,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
590
601
  }
591
602
  this._attachState = container_definitions_1.AttachState.Attached;
592
603
  this.emit("attached");
593
- // Propagate current connection state through the system.
594
- this.propagateConnectionState();
595
604
  if (!this.closed) {
596
605
  this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
597
606
  }
@@ -730,9 +739,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
730
739
  /**
731
740
  * Load container.
732
741
  *
733
- * @param specifiedVersion - one of the following
734
- * - undefined - fetch latest snapshot
735
- * - otherwise, version sha to load snapshot
742
+ * @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
736
743
  */
737
744
  async load(specifiedVersion, loadMode, pendingLocalState) {
738
745
  if (this._resolvedUrl === undefined) {
@@ -755,11 +762,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
755
762
  this.connectToDeltaStream(connectionArgs);
756
763
  }
757
764
  if (!pendingLocalState) {
758
- await this.connectStorageService();
765
+ await this.storageService.connectToService(this.service);
759
766
  }
760
767
  else {
761
768
  // if we have pendingLocalState we can load without storage; don't wait for connection
762
- this.connectStorageService().catch((error) => this.close(error));
769
+ this.storageService.connectToService(this.service).catch((error) => this.close(error));
763
770
  }
764
771
  this._attachState = container_definitions_1.AttachState.Attached;
765
772
  // Fetch specified snapshot.
@@ -794,21 +801,26 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
794
801
  }
795
802
  // ...load in the existing quorum
796
803
  // Initialize the protocol handler
797
- this._protocolHandler = pendingLocalState === undefined
798
- ? await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot)
799
- : 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
+ }
800
814
  const codeDetails = this.getCodeDetailsFromQuorum();
801
815
  await this.instantiateContext(true, // existing
802
816
  codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
803
- // Internal context is fully loaded at this point
804
- this.setLoaded();
805
817
  // We might have hit some failure that did not manifest itself in exception in this flow,
806
818
  // do not start op processing in such case - static version of Container.load() will handle it correctly.
807
819
  if (!this.closed) {
808
820
  if (opsBeforeReturnP !== undefined) {
809
821
  this._deltaManager.inbound.resume();
810
- await opsBeforeReturnP;
811
- await this._deltaManager.inbound.waitTillProcessingDone();
822
+ await ReportIfTooLong(this.mc.logger, "WaitOps", async () => { await opsBeforeReturnP; return {}; });
823
+ await ReportIfTooLong(this.mc.logger, "WaitOpProcessing", async () => this._deltaManager.inbound.waitTillProcessingDone());
812
824
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
813
825
  this._deltaManager.inbound.pause();
814
826
  }
@@ -834,9 +846,13 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
834
846
  if (this.closed) {
835
847
  throw new Error("Container was closed while load()");
836
848
  }
849
+ // Internal context is fully loaded at this point
850
+ this.setLoaded();
837
851
  return {
838
852
  sequenceNumber: attributes.sequenceNumber,
839
853
  version: versionId,
854
+ dmLastProcessedSeqNumber: this._deltaManager.lastSequenceNumber,
855
+ dmLastKnownSeqNumber: this._deltaManager.lastKnownSeqNumber,
840
856
  };
841
857
  }
842
858
  async createDetached(source) {
@@ -848,9 +864,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
848
864
  await this.attachDeltaManagerOpHandler(attributes);
849
865
  // Need to just seed the source data in the code quorum. Quorum itself is empty
850
866
  const qValues = (0, quorum_1.initQuorumValuesFromCodeDetails)(source);
851
- this._protocolHandler = await this.initializeProtocolState(attributes, [], // members
852
- [], // proposals
853
- qValues);
867
+ this.initializeProtocolState(attributes, {
868
+ members: [],
869
+ proposals: [],
870
+ values: qValues,
871
+ });
854
872
  // The load context - given we seeded the quorum - will be great
855
873
  await this.instantiateContextDetached(false);
856
874
  this.setLoaded();
@@ -861,38 +879,22 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
861
879
  delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
862
880
  }
863
881
  const snapshotTree = (0, utils_1.getSnapshotTreeFromSerializedContainer)(detachedContainerSnapshot);
864
- this._storage.loadSnapshotForRehydratingContainer(snapshotTree);
865
- const attributes = await this.getDocumentAttributes(this._storage, snapshotTree);
882
+ this.storageService.loadSnapshotForRehydratingContainer(snapshotTree);
883
+ const attributes = await this.getDocumentAttributes(this.storageService, snapshotTree);
866
884
  await this.attachDeltaManagerOpHandler(attributes);
867
885
  // Initialize the protocol handler
868
886
  const baseTree = (0, utils_1.getProtocolSnapshotTree)(snapshotTree);
869
- 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);
870
888
  const codeDetails = (0, quorum_1.getCodeDetailsFromQuorumValues)(qValues);
871
- this._protocolHandler =
872
- await this.initializeProtocolState(attributes, [], // members
873
- [], // proposals
874
- 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
+ });
875
894
  await this.instantiateContextDetached(true, // existing
876
895
  snapshotTree);
877
896
  this.setLoaded();
878
897
  }
879
- async connectStorageService() {
880
- var _a, _b;
881
- if (this._storageService !== undefined) {
882
- return;
883
- }
884
- (0, common_utils_1.assert)(this.service !== undefined, 0x1ef /* "services must be defined" */);
885
- const storageService = await this.service.connectToStorage();
886
- this._storageService =
887
- new retriableDocumentStorageService_1.RetriableDocumentStorageService(storageService, this.mc.logger);
888
- if (this.options.summarizeProtocolTree === true) {
889
- this.mc.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
890
- this._storageService =
891
- new protocolTreeDocumentStorageService_1.ProtocolTreeStorageService(this._storageService, () => this.captureProtocolSummary());
892
- }
893
- // ensure we did not lose that policy in the process of wrapping
894
- (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" */);
895
- }
896
898
  async getDocumentAttributes(storage, tree) {
897
899
  if (tree === undefined) {
898
900
  return {
@@ -913,22 +915,25 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
913
915
  return attributes;
914
916
  }
915
917
  async initializeProtocolStateFromSnapshot(attributes, storage, snapshot) {
916
- let members = [];
917
- let proposals = [];
918
- let values = [];
918
+ const quorumSnapshot = {
919
+ members: [],
920
+ proposals: [],
921
+ values: [],
922
+ };
919
923
  if (snapshot !== undefined) {
920
924
  const baseTree = (0, utils_1.getProtocolSnapshotTree)(snapshot);
921
- [members, proposals, values] = await Promise.all([
925
+ [quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] = await Promise.all([
922
926
  (0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumMembers),
923
927
  (0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumProposals),
924
928
  (0, driver_utils_1.readAndParse)(storage, baseTree.blobs.quorumValues),
925
929
  ]);
926
930
  }
927
- const protocolHandler = await this.initializeProtocolState(attributes, members, proposals, values);
928
- return protocolHandler;
931
+ this.initializeProtocolState(attributes, quorumSnapshot);
929
932
  }
930
- async initializeProtocolState(attributes, members, proposals, values) {
931
- 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;
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 })));
932
937
  const protocolLogger = telemetry_utils_1.ChildLogger.create(this.subLogger, "ProtocolHandler");
933
938
  protocol.quorum.on("error", (error) => {
934
939
  protocolLogger.sendErrorEvent(error);
@@ -953,7 +958,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
953
958
  });
954
959
  }
955
960
  });
956
- return protocol;
961
+ // we need to make sure this member get set in a synchronous context,
962
+ // or other things can happen after the object that will be set is created, but not yet set
963
+ // this was breaking this._initialClients handling
964
+ //
965
+ this._protocolHandler = protocol;
957
966
  }
958
967
  captureProtocolSummary() {
959
968
  const quorumSnapshot = this.protocolHandler.snapshot();
@@ -1022,13 +1031,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1022
1031
  deltaManager.inbound.pause();
1023
1032
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1024
1033
  deltaManager.inboundSignal.pause();
1025
- deltaManager.on("connect", (details, opsBehind) => {
1026
- var _a;
1027
- // Back-compat for new client and old server.
1028
- this._audience.clear();
1029
- for (const priorClient of (_a = details.initialClients) !== null && _a !== void 0 ? _a : []) {
1030
- this._audience.addMember(priorClient.clientId, priorClient.client);
1031
- }
1034
+ deltaManager.on("connect", (details, _opsBehind) => {
1032
1035
  this.connectionStateHandler.receivedConnectEvent(this.connectionMode, details);
1033
1036
  });
1034
1037
  deltaManager.on("disconnect", (reason) => {
@@ -1046,6 +1049,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1046
1049
  this.emit("warning", warn);
1047
1050
  });
1048
1051
  deltaManager.on("readonly", (readonly) => {
1052
+ this.setContextConnectedState(this.connectionState === connectionState_1.ConnectionState.Connected, readonly);
1049
1053
  this.emit("readonly", readonly);
1050
1054
  });
1051
1055
  deltaManager.on("closed", (error) => {
@@ -1088,12 +1092,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1088
1092
  opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
1089
1093
  }
1090
1094
  }
1091
- if (this.firstConnection) {
1092
- connectionInitiationReason = "InitialConnect";
1093
- }
1094
- else {
1095
- connectionInitiationReason = "AutoReconnect";
1096
- }
1095
+ connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
1097
1096
  }
1098
1097
  this.mc.logger.sendPerformanceEvent(Object.assign({ eventName: `ConnectionStateChange_${connectionState_1.ConnectionState[value]}`, from: connectionState_1.ConnectionState[oldState], duration,
1099
1098
  durationFromDisconnected,
@@ -1104,49 +1103,64 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1104
1103
  this.firstConnection = false;
1105
1104
  }
1106
1105
  }
1107
- propagateConnectionState() {
1106
+ propagateConnectionState(initialTransition) {
1108
1107
  var _a;
1108
+ // When container loaded, we want to propagate initial connection state.
1109
+ // After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
1110
+ // This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
1111
+ if (!initialTransition &&
1112
+ this.connectionState !== connectionState_1.ConnectionState.Connected &&
1113
+ this.connectionState !== connectionState_1.ConnectionState.Disconnected) {
1114
+ return;
1115
+ }
1116
+ const state = this.connectionState === connectionState_1.ConnectionState.Connected;
1109
1117
  const logOpsOnReconnect = this.connectionState === connectionState_1.ConnectionState.Connected &&
1110
1118
  !this.firstConnection &&
1111
1119
  this.connectionMode === "write";
1112
1120
  if (logOpsOnReconnect) {
1113
1121
  this.messageCountAfterDisconnection = 0;
1114
1122
  }
1115
- const state = this.connectionState === connectionState_1.ConnectionState.Connected;
1116
1123
  // Both protocol and context should not be undefined if we got so far.
1117
- if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1118
- this.context.setConnectionState(state, this.clientId);
1119
- }
1124
+ this.setContextConnectedState(state, (_a = this._deltaManager.connectionManager.readOnlyInfo.readonly) !== null && _a !== void 0 ? _a : false);
1120
1125
  this.protocolHandler.setConnectionState(state, this.clientId);
1121
1126
  (0, telemetry_utils_1.raiseConnectedEvent)(this.mc.logger, this, state, this.clientId);
1122
1127
  if (logOpsOnReconnect) {
1123
1128
  this.mc.logger.sendTelemetryEvent({ eventName: "OpsSentOnReconnect", count: this.messageCountAfterDisconnection });
1124
1129
  }
1125
1130
  }
1131
+ // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
1126
1132
  submitContainerMessage(type, contents, batch, metadata) {
1127
- const outboundMessageType = type;
1128
- switch (outboundMessageType) {
1133
+ switch (type) {
1129
1134
  case protocol_definitions_1.MessageType.Operation:
1130
- case protocol_definitions_1.MessageType.RemoteHelp:
1131
- break;
1132
- case protocol_definitions_1.MessageType.Summarize: {
1133
- // github #6451: this is only needed for staging so the server
1134
- // know when the protocol tree is included
1135
- // this can be removed once all clients send
1136
- // protocol tree by default
1137
- const summary = contents;
1138
- if (summary.details === undefined) {
1139
- summary.details = {};
1140
- }
1141
- summary.details.includesProtocolTree =
1142
- this.options.summarizeProtocolTree === true;
1143
- break;
1144
- }
1135
+ return this.submitMessage(type, JSON.stringify(contents), batch, metadata);
1136
+ case protocol_definitions_1.MessageType.Summarize:
1137
+ return this.submitSummaryMessage(contents);
1145
1138
  default:
1146
1139
  this.close(new container_utils_1.GenericError("invalidContainerSubmitOpType", undefined /* error */, { messageType: type }));
1147
1140
  return -1;
1148
1141
  }
1149
- return this.submitMessage(type, contents, batch, metadata);
1142
+ }
1143
+ /** @returns clientSequenceNumber of last message in a batch */
1144
+ submitBatch(batch) {
1145
+ let clientSequenceNumber = -1;
1146
+ for (const message of batch) {
1147
+ clientSequenceNumber = this.submitMessage(protocol_definitions_1.MessageType.Operation, message.contents, true, // batch
1148
+ message.metadata);
1149
+ }
1150
+ this._deltaManager.flush();
1151
+ return clientSequenceNumber;
1152
+ }
1153
+ submitSummaryMessage(summary) {
1154
+ // github #6451: this is only needed for staging so the server
1155
+ // know when the protocol tree is included
1156
+ // this can be removed once all clients send
1157
+ // protocol tree by default
1158
+ if (summary.details === undefined) {
1159
+ summary.details = {};
1160
+ }
1161
+ summary.details.includesProtocolTree =
1162
+ this.options.summarizeProtocolTree === true;
1163
+ return this.submitMessage(protocol_definitions_1.MessageType.Summarize, JSON.stringify(summary), false /* batch */);
1150
1164
  }
1151
1165
  submitMessage(type, contents, batch, metadata) {
1152
1166
  var _a;
@@ -1161,22 +1175,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1161
1175
  processRemoteMessage(message) {
1162
1176
  const local = this.clientId === message.clientId;
1163
1177
  // Allow the protocol handler to process the message
1164
- let result = { immediateNoOp: false };
1165
- try {
1166
- result = this.protocolHandler.processMessage(message, local);
1167
- }
1168
- catch (error) {
1169
- this.close((0, telemetry_utils_1.wrapError)(error, (errorMessage) => new container_utils_1.DataCorruptionError(errorMessage, (0, container_utils_1.extractSafePropertiesFromMessage)(message))));
1170
- }
1171
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
1172
- if ((0, driver_utils_1.isUnpackedRuntimeMessage)(message) && !(0, driver_utils_1.isRuntimeMessage)(message)) {
1173
- this.mc.logger.sendTelemetryEvent({ eventName: "UnpackedRuntimeMessage", type: message.type });
1174
- }
1175
- // Forward non system messages to the loaded runtime for processing
1176
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
1177
- if ((0, driver_utils_1.isRuntimeMessage)(message) || (0, driver_utils_1.isUnpackedRuntimeMessage)(message)) {
1178
- this.context.process(message, local, undefined);
1179
- }
1178
+ const result = this.protocolHandler.processMessage(message, local);
1179
+ // Forward messages to the loaded runtime for processing
1180
+ this.context.process(message, local, undefined);
1180
1181
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
1181
1182
  if (this.activeConnection()) {
1182
1183
  if (this.collabWindowTracker === undefined) {
@@ -1185,15 +1186,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1185
1186
  // clients.
1186
1187
  // All existing will continue to use settings they got earlier.
1187
1188
  (0, common_utils_1.assert)(this.serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1188
- this.collabWindowTracker = new collabWindowTracker_1.CollabWindowTracker((type, contents) => {
1189
+ this.collabWindowTracker = new collabWindowTracker_1.CollabWindowTracker((type) => {
1189
1190
  (0, common_utils_1.assert)(this.activeConnection(), 0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
1190
- this.submitMessage(type, contents);
1191
+ this.submitMessage(type);
1191
1192
  }, this.serviceConfiguration.noopTimeFrequency, this.serviceConfiguration.noopCountFrequency);
1192
1193
  }
1193
1194
  this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
1194
1195
  }
1195
1196
  this.emit("op", message);
1196
- return result;
1197
1197
  }
1198
1198
  submitSignal(message) {
1199
1199
  this._deltaManager.submitSignal(JSON.stringify(message));
@@ -1201,15 +1201,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1201
1201
  processSignal(message) {
1202
1202
  // No clientId indicates a system signal message.
1203
1203
  if (message.clientId === null) {
1204
- const innerContent = message.content;
1205
- if (innerContent.type === protocol_definitions_1.MessageType.ClientJoin) {
1206
- const newClient = innerContent.content;
1207
- this._audience.addMember(newClient.clientId, newClient.client);
1208
- }
1209
- else if (innerContent.type === protocol_definitions_1.MessageType.ClientLeave) {
1210
- const leftClientId = innerContent.content;
1211
- this._audience.removeMember(leftClientId);
1212
- }
1204
+ this.protocolHandler.processSignal(message);
1213
1205
  }
1214
1206
  else {
1215
1207
  const local = this.clientId === message.clientId;
@@ -1248,7 +1240,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1248
1240
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1249
1241
  // are set. Global requests will still go directly to the loader
1250
1242
  const loader = new loader_1.RelativeLoader(this, this.loader);
1251
- 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);
1243
+ 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);
1252
1244
  this.emit("contextChanged", codeDetails);
1253
1245
  }
1254
1246
  updateDirtyContainerState(dirty) {
@@ -1261,6 +1253,24 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1261
1253
  logContainerError(warning) {
1262
1254
  this.mc.logger.sendErrorEvent({ eventName: "ContainerWarning" }, warning);
1263
1255
  }
1256
+ /**
1257
+ * Set the connected state of the ContainerContext
1258
+ * This controls the "connected" state of the ContainerRuntime as well
1259
+ * @param state - Is the container currently connected?
1260
+ * @param readonly - Is the container in readonly mode?
1261
+ */
1262
+ setContextConnectedState(state, readonly) {
1263
+ var _a;
1264
+ if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1265
+ /**
1266
+ * We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
1267
+ * ops getting through to the DeltaManager.
1268
+ * The ContainerRuntime's "connected" state simply means it is ok to send ops
1269
+ * See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
1270
+ */
1271
+ this.context.setConnectionState(state && !readonly, this.clientId);
1272
+ }
1273
+ }
1264
1274
  }
1265
1275
  exports.Container = Container;
1266
1276
  Container.version = "^0.1.0";