@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/lib/container.js CHANGED
@@ -7,9 +7,8 @@ import merge from "lodash/merge";
7
7
  import { v4 as uuid } from "uuid";
8
8
  import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
9
9
  import { AttachState, isFluidCodeDetails, } from "@fluidframework/container-definitions";
10
- import { DataCorruptionError, extractSafePropertiesFromMessage, GenericError, UsageError, } from "@fluidframework/container-utils";
11
- import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl, isRuntimeMessage, isUnpackedRuntimeMessage, } from "@fluidframework/driver-utils";
12
- import { ProtocolOpHandlerWithClientValidation, } from "@fluidframework/protocol-base";
10
+ import { GenericError, UsageError, } from "@fluidframework/container-utils";
11
+ import { readAndParse, OnlineStatus, isOnline, ensureFluidResolvedUrl, combineAppAndProtocolSummary, runWithRetry, isFluidResolvedUrl, } from "@fluidframework/driver-utils";
13
12
  import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
14
13
  import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName, disconnectedEventName, normalizeError, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
15
14
  import { Audience } from "./audience";
@@ -19,27 +18,31 @@ import { DeltaManager } from "./deltaManager";
19
18
  import { DeltaManagerProxy } from "./deltaManagerProxy";
20
19
  import { RelativeLoader } from "./loader";
21
20
  import { pkgVersion } from "./packageVersion";
22
- import { ConnectionStateHandler } from "./connectionStateHandler";
23
- import { RetriableDocumentStorageService } from "./retriableDocumentStorageService";
24
- import { ProtocolTreeStorageService } from "./protocolTreeDocumentStorageService";
25
- import { BlobOnlyStorage, ContainerStorageAdapter } from "./containerStorageAdapter";
21
+ import { ContainerStorageAdapter } from "./containerStorageAdapter";
22
+ import { createConnectionStateHandler, } from "./connectionStateHandler";
26
23
  import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
27
24
  import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy } from "./quorum";
28
25
  import { CollabWindowTracker } from "./collabWindowTracker";
29
26
  import { ConnectionManager } from "./connectionManager";
30
27
  import { ConnectionState } from "./connectionState";
28
+ import { ProtocolHandler, } from "./protocol";
31
29
  const detachedContainerRefSeqNumber = 0;
32
30
  const dirtyContainerEvent = "dirty";
33
31
  const savedContainerEvent = "saved";
34
32
  /**
35
- * Waits until container connects to delta storage and gets up-to-date
33
+ * Waits until container connects to delta storage and gets up-to-date.
34
+ *
36
35
  * Useful when resolving URIs and hitting 404, due to container being loaded from (stale) snapshot and not being
37
36
  * up to date. Host may chose to wait in such case and retry resolving URI.
37
+ *
38
38
  * Warning: Will wait infinitely for connection to establish if there is no connection.
39
39
  * May result in deadlock if Container.disconnect() is called and never followed by a call to Container.connect().
40
- * @returns true: container is up to date, it processed all the ops that were know at the time of first connection
41
- * false: storage does not provide indication of how far the client is. Container processed
42
- * all the ops known to it, but it maybe still behind.
40
+ *
41
+ * @returns `true`: container is up to date, it processed all the ops that were know at the time of first connection.
42
+ *
43
+ * `false`: storage does not provide indication of how far the client is. Container processed all the ops known to it,
44
+ * but it maybe still behind.
45
+ *
43
46
  * @throws an error beginning with `"Container closed"` if the container is closed before it catches up.
44
47
  */
45
48
  export async function waitContainerToCatchUp(container) {
@@ -57,6 +60,10 @@ export async function waitContainerToCatchUp(container) {
57
60
  : new GenericError(baseMessage));
58
61
  };
59
62
  container.on("closed", closedCallback);
63
+ // Depending on config, transition to "connected" state may include the guarantee
64
+ // that all known ops have been processed. If so, we may introduce additional wait here.
65
+ // Waiting for "connected" state in either case gets us at least to our own Join op
66
+ // which is a reasonable approximation of "caught up"
60
67
  const waitForOps = () => {
61
68
  assert(container.connectionState === ConnectionState.CatchingUp
62
69
  || container.connectionState === ConnectionState.Connected, 0x0cd /* "Container disconnected while waiting for ops!" */);
@@ -98,9 +105,22 @@ export async function waitContainerToCatchUp(container) {
98
105
  const getCodeProposal =
99
106
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
100
107
  (quorum) => { var _a; return (_a = quorum.get("code")) !== null && _a !== void 0 ? _a : quorum.get("code2"); };
108
+ /**
109
+ * Helper function to report to telemetry cases where operation takes longer than expected (1s)
110
+ * @param logger - logger to use
111
+ * @param eventName - event name
112
+ * @param action - functor to call and measure
113
+ */
114
+ async function ReportIfTooLong(logger, eventName, action) {
115
+ const event = PerformanceEvent.start(logger, { eventName });
116
+ const props = await action();
117
+ if (event.duration > 1000) {
118
+ event.end(props);
119
+ }
120
+ }
101
121
  const summarizerClientType = "summarizer";
102
122
  export class Container extends EventEmitterWithErrorHandling {
103
- constructor(loader, config) {
123
+ constructor(loader, config, protocolHandlerBuilder) {
104
124
  var _a, _b;
105
125
  super((name, error) => {
106
126
  this.mc.logger.sendErrorEvent({
@@ -109,6 +129,7 @@ export class Container extends EventEmitterWithErrorHandling {
109
129
  }, error);
110
130
  });
111
131
  this.loader = loader;
132
+ this.protocolHandlerBuilder = protocolHandlerBuilder;
112
133
  // Tells if container can reconnect on losing fist connection
113
134
  // If false, container gets closed on loss of connection.
114
135
  this._canReconnect = true;
@@ -122,7 +143,6 @@ export class Container extends EventEmitterWithErrorHandling {
122
143
  this.attachStarted = false;
123
144
  this._dirtyContainer = false;
124
145
  this.setAutoReconnectTime = performance.now();
125
- this._audience = new Audience();
126
146
  this.clientDetailsOverride = config.clientDetailsOverride;
127
147
  this._resolvedUrl = config.resolvedUrl;
128
148
  if (config.canReconnect !== undefined) {
@@ -142,6 +162,7 @@ export class Container extends EventEmitterWithErrorHandling {
142
162
  containerAttachState: () => this._attachState,
143
163
  containerLifecycleState: () => this._lifecycleState,
144
164
  containerConnectionState: () => ConnectionState[this.connectionState],
165
+ serializedContainer: config.serializedContainerState !== undefined,
145
166
  },
146
167
  // we need to be judicious with our logging here to avoid generating too much data
147
168
  // all data logged here should be broadly applicable, and not specific to a
@@ -154,6 +175,7 @@ export class Container extends EventEmitterWithErrorHandling {
154
175
  containerLoadedFromVersionId: () => { var _a; return (_a = this.loadedFromVersion) === null || _a === void 0 ? void 0 : _a.id; },
155
176
  containerLoadedFromVersionDate: () => { var _a; return (_a = this.loadedFromVersion) === null || _a === void 0 ? void 0 : _a.date; },
156
177
  // message information to associate errors with the specific execution state
178
+ // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
157
179
  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; },
158
180
  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; },
159
181
  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; },
@@ -164,9 +186,19 @@ export class Container extends EventEmitterWithErrorHandling {
164
186
  this.mc = loggerToMonitoringContext(ChildLogger.create(this.subLogger, "Container"));
165
187
  const summarizeProtocolTree = (_a = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree")) !== null && _a !== void 0 ? _a : this.loader.services.options.summarizeProtocolTree;
166
188
  this.options = Object.assign(Object.assign({}, this.loader.services.options), { summarizeProtocolTree });
167
- this.connectionStateHandler = new ConnectionStateHandler({
168
- quorumClients: () => { var _a; return (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.quorum; },
169
- logConnectionStateChangeTelemetry: (value, oldState, reason) => this.logConnectionStateChangeTelemetry(value, oldState, reason),
189
+ this._deltaManager = this.createDeltaManager();
190
+ this._clientId = (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId;
191
+ this.connectionStateHandler = createConnectionStateHandler({
192
+ logger: this.mc.logger,
193
+ connectionStateChanged: (value, oldState, reason) => {
194
+ if (value === ConnectionState.Connected) {
195
+ this._clientId = this.connectionStateHandler.pendingClientId;
196
+ }
197
+ this.logConnectionStateChangeTelemetry(value, oldState, reason);
198
+ if (this._lifecycleState === "loaded") {
199
+ this.propagateConnectionState(false /* initial transition */);
200
+ }
201
+ },
170
202
  shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
171
203
  maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
172
204
  logConnectionIssue: (eventName, details) => {
@@ -174,29 +206,13 @@ export class Container extends EventEmitterWithErrorHandling {
174
206
  // its own join op. Attempt recovery option.
175
207
  this._deltaManager.logConnectionIssue(Object.assign({ eventName, duration: performance.now() - this.connectionTransitionTimes[ConnectionState.CatchingUp] }, (details === undefined ? {} : { details: JSON.stringify(details) })));
176
208
  },
177
- connectionStateChanged: () => {
178
- // Fire events only if container is fully loaded and not closed
179
- if (this._lifecycleState === "loaded") {
180
- this.propagateConnectionState();
181
- }
182
- },
183
- }, this.mc.logger, (_b = config.serializedContainerState) === null || _b === void 0 ? void 0 : _b.clientId);
209
+ }, this.deltaManager, this._clientId);
184
210
  this.on(savedContainerEvent, () => {
185
211
  this.connectionStateHandler.containerSaved();
186
212
  });
187
- this._deltaManager = this.createDeltaManager();
188
- this._storage = new ContainerStorageAdapter(() => {
189
- if (this.attachState !== AttachState.Attached) {
190
- if (this.loader.services.detachedBlobStorage !== undefined) {
191
- return new BlobOnlyStorage(this.loader.services.detachedBlobStorage, this.mc.logger);
192
- }
193
- this.mc.logger.sendErrorEvent({
194
- eventName: "NoRealStorageInDetachedContainer",
195
- });
196
- throw new Error("Real storage calls not allowed in Unattached container");
197
- }
198
- return this.storageService;
199
- });
213
+ this.storageService = new ContainerStorageAdapter(this.loader.services.detachedBlobStorage, this.mc.logger, this.options.summarizeProtocolTree === true
214
+ ? () => this.captureProtocolSummary()
215
+ : undefined);
200
216
  const isDomAvailable = typeof document === "object" &&
201
217
  document !== null &&
202
218
  typeof document.addEventListener === "function" &&
@@ -253,13 +269,13 @@ export class Container extends EventEmitterWithErrorHandling {
253
269
  /**
254
270
  * Load an existing container.
255
271
  */
256
- static async load(loader, loadOptions, pendingLocalState) {
272
+ static async load(loader, loadOptions, pendingLocalState, protocolHandlerBuilder) {
257
273
  const container = new Container(loader, {
258
274
  clientDetailsOverride: loadOptions.clientDetailsOverride,
259
275
  resolvedUrl: loadOptions.resolvedUrl,
260
276
  canReconnect: loadOptions.canReconnect,
261
277
  serializedContainerState: pendingLocalState,
262
- });
278
+ }, protocolHandlerBuilder);
263
279
  return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
264
280
  var _a, _b;
265
281
  const version = loadOptions.version;
@@ -293,8 +309,8 @@ export class Container extends EventEmitterWithErrorHandling {
293
309
  /**
294
310
  * Create a new container in a detached state.
295
311
  */
296
- static async createDetached(loader, codeDetails) {
297
- const container = new Container(loader, {});
312
+ static async createDetached(loader, codeDetails, protocolHandlerBuilder) {
313
+ const container = new Container(loader, {}, protocolHandlerBuilder);
298
314
  return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "CreateDetached" }, async (_event) => {
299
315
  await container.createDetached(codeDetails);
300
316
  return container;
@@ -304,8 +320,8 @@ export class Container extends EventEmitterWithErrorHandling {
304
320
  * Create a new container in a detached state that is initialized with a
305
321
  * snapshot from a previous detached container.
306
322
  */
307
- static async rehydrateDetachedFromSnapshot(loader, snapshot) {
308
- const container = new Container(loader, {});
323
+ static async rehydrateDetachedFromSnapshot(loader, snapshot, protocolHandlerBuilder) {
324
+ const container = new Container(loader, {}, protocolHandlerBuilder);
309
325
  return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "RehydrateDetachedFromSnapshot" }, async (_event) => {
310
326
  const deserializedSummary = JSON.parse(snapshot);
311
327
  await container.rehydrateDetachedFromSnapshot(deserializedSummary);
@@ -317,7 +333,7 @@ export class Container extends EventEmitterWithErrorHandling {
317
333
  // Only transition states if currently loading
318
334
  if (this._lifecycleState === "loading") {
319
335
  // Propagate current connection state through the system.
320
- this.propagateConnectionState();
336
+ this.propagateConnectionState(true /* initial transition */);
321
337
  this._lifecycleState = "loaded";
322
338
  }
323
339
  }
@@ -325,13 +341,7 @@ export class Container extends EventEmitterWithErrorHandling {
325
341
  return (this._lifecycleState === "closing" || this._lifecycleState === "closed");
326
342
  }
327
343
  get storage() {
328
- return this._storage;
329
- }
330
- get storageService() {
331
- if (this._storageService === undefined) {
332
- throw new Error("Attempted to access storageService before it was defined");
333
- }
334
- return this._storageService;
344
+ return this.storageService;
335
345
  }
336
346
  get context() {
337
347
  if (this._context === undefined) {
@@ -372,7 +382,7 @@ export class Container extends EventEmitterWithErrorHandling {
372
382
  return this.connectionStateHandler.connectionState;
373
383
  }
374
384
  get connected() {
375
- return this.connectionStateHandler.connected;
385
+ return this.connectionStateHandler.connectionState === ConnectionState.Connected;
376
386
  }
377
387
  /**
378
388
  * Service configuration details. If running in offline mode will be undefined otherwise will contain service
@@ -386,7 +396,7 @@ export class Container extends EventEmitterWithErrorHandling {
386
396
  * Set once this.connected is true, otherwise undefined
387
397
  */
388
398
  get clientId() {
389
- return this.connectionStateHandler.clientId;
399
+ return this._clientId;
390
400
  }
391
401
  /**
392
402
  * The server provided claims of the client.
@@ -418,12 +428,12 @@ export class Container extends EventEmitterWithErrorHandling {
418
428
  * Retrieves the audience associated with the document
419
429
  */
420
430
  get audience() {
421
- return this._audience;
431
+ return this.protocolHandler.audience;
422
432
  }
423
433
  /**
424
434
  * Returns true if container is dirty.
425
435
  * Which means data loss if container is closed at that same moment
426
- * Most likely that happens when there is no network connection to ordering service
436
+ * Most likely that happens when there is no network connection to Relay Service
427
437
  */
428
438
  get isDirty() {
429
439
  return this._dirtyContainer;
@@ -448,7 +458,7 @@ export class Container extends EventEmitterWithErrorHandling {
448
458
  assert(this._lifecycleState === "closed", 0x314 /* Container properly closed */);
449
459
  }
450
460
  closeCore(error) {
451
- var _a, _b, _c, _d;
461
+ var _a, _b, _c;
452
462
  assert(!this.closed, 0x315 /* re-entrancy */);
453
463
  try {
454
464
  // Ensure that we raise all key events even if one of these throws
@@ -463,11 +473,11 @@ export class Container extends EventEmitterWithErrorHandling {
463
473
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
464
474
  this.connectionStateHandler.dispose();
465
475
  (_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
466
- (_c = this._storageService) === null || _c === void 0 ? void 0 : _c.dispose();
476
+ this.storageService.dispose();
467
477
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
468
478
  // about file, like file being overwritten in storage, but client having stale local cache.
469
479
  // Driver need to ensure all caches are cleared on critical errors
470
- (_d = this.service) === null || _d === void 0 ? void 0 : _d.dispose(error);
480
+ (_c = this.service) === null || _c === void 0 ? void 0 : _c.dispose(error);
471
481
  }
472
482
  catch (exception) {
473
483
  this.mc.logger.sendErrorEvent({ eventName: "ContainerCloseException" }, exception);
@@ -489,7 +499,7 @@ export class Container extends EventEmitterWithErrorHandling {
489
499
  assert(this.attachState === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
490
500
  assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
491
501
  assert(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
492
- assert(this._protocolHandler.attributes.term !== undefined, 0x30b /* Must have a valid protocol handler instance */);
502
+ assert(this._protocolHandler.attributes.term !== undefined, 0x37e /* Must have a valid protocol handler instance */);
493
503
  const pendingState = {
494
504
  pendingRuntimeState: this.context.getPendingLocalState(),
495
505
  url: this.resolvedUrl.url,
@@ -497,6 +507,7 @@ export class Container extends EventEmitterWithErrorHandling {
497
507
  term: this._protocolHandler.attributes.term,
498
508
  clientId: this.clientId,
499
509
  };
510
+ this.mc.logger.sendTelemetryEvent({ eventName: "CloseAndGetPendingLocalState" });
500
511
  this.close();
501
512
  return JSON.stringify(pendingState);
502
513
  }
@@ -553,7 +564,7 @@ export class Container extends EventEmitterWithErrorHandling {
553
564
  const resolvedUrl = this.service.resolvedUrl;
554
565
  ensureFluidResolvedUrl(resolvedUrl);
555
566
  this._resolvedUrl = resolvedUrl;
556
- await this.connectStorageService();
567
+ await this.storageService.connectToService(this.service);
557
568
  if (hasAttachmentBlobs) {
558
569
  // upload blobs to storage
559
570
  assert(!!this.loader.services.detachedBlobStorage, 0x24e /* "assertion for type narrowing" */);
@@ -583,8 +594,6 @@ export class Container extends EventEmitterWithErrorHandling {
583
594
  }
584
595
  this._attachState = AttachState.Attached;
585
596
  this.emit("attached");
586
- // Propagate current connection state through the system.
587
- this.propagateConnectionState();
588
597
  if (!this.closed) {
589
598
  this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
590
599
  }
@@ -723,9 +732,7 @@ export class Container extends EventEmitterWithErrorHandling {
723
732
  /**
724
733
  * Load container.
725
734
  *
726
- * @param specifiedVersion - one of the following
727
- * - undefined - fetch latest snapshot
728
- * - otherwise, version sha to load snapshot
735
+ * @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
729
736
  */
730
737
  async load(specifiedVersion, loadMode, pendingLocalState) {
731
738
  if (this._resolvedUrl === undefined) {
@@ -748,11 +755,11 @@ export class Container extends EventEmitterWithErrorHandling {
748
755
  this.connectToDeltaStream(connectionArgs);
749
756
  }
750
757
  if (!pendingLocalState) {
751
- await this.connectStorageService();
758
+ await this.storageService.connectToService(this.service);
752
759
  }
753
760
  else {
754
761
  // if we have pendingLocalState we can load without storage; don't wait for connection
755
- this.connectStorageService().catch((error) => this.close(error));
762
+ this.storageService.connectToService(this.service).catch((error) => this.close(error));
756
763
  }
757
764
  this._attachState = AttachState.Attached;
758
765
  // Fetch specified snapshot.
@@ -787,21 +794,26 @@ export class Container extends EventEmitterWithErrorHandling {
787
794
  }
788
795
  // ...load in the existing quorum
789
796
  // Initialize the protocol handler
790
- this._protocolHandler = pendingLocalState === undefined
791
- ? await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot)
792
- : await this.initializeProtocolState(attributes, pendingLocalState.protocol.members, pendingLocalState.protocol.proposals, pendingLocalState.protocol.values);
797
+ if (pendingLocalState === undefined) {
798
+ await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot);
799
+ }
800
+ else {
801
+ this.initializeProtocolState(attributes, {
802
+ members: pendingLocalState.protocol.members,
803
+ proposals: pendingLocalState.protocol.proposals,
804
+ values: pendingLocalState.protocol.values,
805
+ });
806
+ }
793
807
  const codeDetails = this.getCodeDetailsFromQuorum();
794
808
  await this.instantiateContext(true, // existing
795
809
  codeDetails, snapshot, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.pendingRuntimeState);
796
- // Internal context is fully loaded at this point
797
- this.setLoaded();
798
810
  // We might have hit some failure that did not manifest itself in exception in this flow,
799
811
  // do not start op processing in such case - static version of Container.load() will handle it correctly.
800
812
  if (!this.closed) {
801
813
  if (opsBeforeReturnP !== undefined) {
802
814
  this._deltaManager.inbound.resume();
803
- await opsBeforeReturnP;
804
- await this._deltaManager.inbound.waitTillProcessingDone();
815
+ await ReportIfTooLong(this.mc.logger, "WaitOps", async () => { await opsBeforeReturnP; return {}; });
816
+ await ReportIfTooLong(this.mc.logger, "WaitOpProcessing", async () => this._deltaManager.inbound.waitTillProcessingDone());
805
817
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
806
818
  this._deltaManager.inbound.pause();
807
819
  }
@@ -827,9 +839,13 @@ export class Container extends EventEmitterWithErrorHandling {
827
839
  if (this.closed) {
828
840
  throw new Error("Container was closed while load()");
829
841
  }
842
+ // Internal context is fully loaded at this point
843
+ this.setLoaded();
830
844
  return {
831
845
  sequenceNumber: attributes.sequenceNumber,
832
846
  version: versionId,
847
+ dmLastProcessedSeqNumber: this._deltaManager.lastSequenceNumber,
848
+ dmLastKnownSeqNumber: this._deltaManager.lastKnownSeqNumber,
833
849
  };
834
850
  }
835
851
  async createDetached(source) {
@@ -841,9 +857,11 @@ export class Container extends EventEmitterWithErrorHandling {
841
857
  await this.attachDeltaManagerOpHandler(attributes);
842
858
  // Need to just seed the source data in the code quorum. Quorum itself is empty
843
859
  const qValues = initQuorumValuesFromCodeDetails(source);
844
- this._protocolHandler = await this.initializeProtocolState(attributes, [], // members
845
- [], // proposals
846
- qValues);
860
+ this.initializeProtocolState(attributes, {
861
+ members: [],
862
+ proposals: [],
863
+ values: qValues,
864
+ });
847
865
  // The load context - given we seeded the quorum - will be great
848
866
  await this.instantiateContextDetached(false);
849
867
  this.setLoaded();
@@ -854,38 +872,22 @@ export class Container extends EventEmitterWithErrorHandling {
854
872
  delete detachedContainerSnapshot.tree[".hasAttachmentBlobs"];
855
873
  }
856
874
  const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
857
- this._storage.loadSnapshotForRehydratingContainer(snapshotTree);
858
- const attributes = await this.getDocumentAttributes(this._storage, snapshotTree);
875
+ this.storageService.loadSnapshotForRehydratingContainer(snapshotTree);
876
+ const attributes = await this.getDocumentAttributes(this.storageService, snapshotTree);
859
877
  await this.attachDeltaManagerOpHandler(attributes);
860
878
  // Initialize the protocol handler
861
879
  const baseTree = getProtocolSnapshotTree(snapshotTree);
862
- const qValues = await readAndParse(this._storage, baseTree.blobs.quorumValues);
880
+ const qValues = await readAndParse(this.storageService, baseTree.blobs.quorumValues);
863
881
  const codeDetails = getCodeDetailsFromQuorumValues(qValues);
864
- this._protocolHandler =
865
- await this.initializeProtocolState(attributes, [], // members
866
- [], // proposals
867
- codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : []);
882
+ this.initializeProtocolState(attributes, {
883
+ members: [],
884
+ proposals: [],
885
+ values: codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
886
+ });
868
887
  await this.instantiateContextDetached(true, // existing
869
888
  snapshotTree);
870
889
  this.setLoaded();
871
890
  }
872
- async connectStorageService() {
873
- var _a, _b;
874
- if (this._storageService !== undefined) {
875
- return;
876
- }
877
- assert(this.service !== undefined, 0x1ef /* "services must be defined" */);
878
- const storageService = await this.service.connectToStorage();
879
- this._storageService =
880
- new RetriableDocumentStorageService(storageService, this.mc.logger);
881
- if (this.options.summarizeProtocolTree === true) {
882
- this.mc.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
883
- this._storageService =
884
- new ProtocolTreeStorageService(this._storageService, () => this.captureProtocolSummary());
885
- }
886
- // ensure we did not lose that policy in the process of wrapping
887
- 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" */);
888
- }
889
891
  async getDocumentAttributes(storage, tree) {
890
892
  if (tree === undefined) {
891
893
  return {
@@ -906,22 +908,25 @@ export class Container extends EventEmitterWithErrorHandling {
906
908
  return attributes;
907
909
  }
908
910
  async initializeProtocolStateFromSnapshot(attributes, storage, snapshot) {
909
- let members = [];
910
- let proposals = [];
911
- let values = [];
911
+ const quorumSnapshot = {
912
+ members: [],
913
+ proposals: [],
914
+ values: [],
915
+ };
912
916
  if (snapshot !== undefined) {
913
917
  const baseTree = getProtocolSnapshotTree(snapshot);
914
- [members, proposals, values] = await Promise.all([
918
+ [quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] = await Promise.all([
915
919
  readAndParse(storage, baseTree.blobs.quorumMembers),
916
920
  readAndParse(storage, baseTree.blobs.quorumProposals),
917
921
  readAndParse(storage, baseTree.blobs.quorumValues),
918
922
  ]);
919
923
  }
920
- const protocolHandler = await this.initializeProtocolState(attributes, members, proposals, values);
921
- return protocolHandler;
924
+ this.initializeProtocolState(attributes, quorumSnapshot);
922
925
  }
923
- async initializeProtocolState(attributes, members, proposals, values) {
924
- const protocol = new ProtocolOpHandlerWithClientValidation(attributes.minimumSequenceNumber, attributes.sequenceNumber, attributes.term, members, proposals, values, (key, value) => this.submitMessage(MessageType.Propose, { key, value }));
926
+ initializeProtocolState(attributes, quorumSnapshot) {
927
+ var _a;
928
+ const protocolHandlerBuilder = (_a = this.protocolHandlerBuilder) !== null && _a !== void 0 ? _a : ((...args) => new ProtocolHandler(...args, new Audience()));
929
+ const protocol = protocolHandlerBuilder(attributes, quorumSnapshot, (key, value) => this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })));
925
930
  const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
926
931
  protocol.quorum.on("error", (error) => {
927
932
  protocolLogger.sendErrorEvent(error);
@@ -946,7 +951,11 @@ export class Container extends EventEmitterWithErrorHandling {
946
951
  });
947
952
  }
948
953
  });
949
- return protocol;
954
+ // we need to make sure this member get set in a synchronous context,
955
+ // or other things can happen after the object that will be set is created, but not yet set
956
+ // this was breaking this._initialClients handling
957
+ //
958
+ this._protocolHandler = protocol;
950
959
  }
951
960
  captureProtocolSummary() {
952
961
  const quorumSnapshot = this.protocolHandler.snapshot();
@@ -1015,13 +1024,7 @@ export class Container extends EventEmitterWithErrorHandling {
1015
1024
  deltaManager.inbound.pause();
1016
1025
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1017
1026
  deltaManager.inboundSignal.pause();
1018
- deltaManager.on("connect", (details, opsBehind) => {
1019
- var _a;
1020
- // Back-compat for new client and old server.
1021
- this._audience.clear();
1022
- for (const priorClient of (_a = details.initialClients) !== null && _a !== void 0 ? _a : []) {
1023
- this._audience.addMember(priorClient.clientId, priorClient.client);
1024
- }
1027
+ deltaManager.on("connect", (details, _opsBehind) => {
1025
1028
  this.connectionStateHandler.receivedConnectEvent(this.connectionMode, details);
1026
1029
  });
1027
1030
  deltaManager.on("disconnect", (reason) => {
@@ -1039,6 +1042,7 @@ export class Container extends EventEmitterWithErrorHandling {
1039
1042
  this.emit("warning", warn);
1040
1043
  });
1041
1044
  deltaManager.on("readonly", (readonly) => {
1045
+ this.setContextConnectedState(this.connectionState === ConnectionState.Connected, readonly);
1042
1046
  this.emit("readonly", readonly);
1043
1047
  });
1044
1048
  deltaManager.on("closed", (error) => {
@@ -1081,12 +1085,7 @@ export class Container extends EventEmitterWithErrorHandling {
1081
1085
  opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
1082
1086
  }
1083
1087
  }
1084
- if (this.firstConnection) {
1085
- connectionInitiationReason = "InitialConnect";
1086
- }
1087
- else {
1088
- connectionInitiationReason = "AutoReconnect";
1089
- }
1088
+ connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
1090
1089
  }
1091
1090
  this.mc.logger.sendPerformanceEvent(Object.assign({ eventName: `ConnectionStateChange_${ConnectionState[value]}`, from: ConnectionState[oldState], duration,
1092
1091
  durationFromDisconnected,
@@ -1097,49 +1096,64 @@ export class Container extends EventEmitterWithErrorHandling {
1097
1096
  this.firstConnection = false;
1098
1097
  }
1099
1098
  }
1100
- propagateConnectionState() {
1099
+ propagateConnectionState(initialTransition) {
1101
1100
  var _a;
1101
+ // When container loaded, we want to propagate initial connection state.
1102
+ // After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
1103
+ // This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
1104
+ if (!initialTransition &&
1105
+ this.connectionState !== ConnectionState.Connected &&
1106
+ this.connectionState !== ConnectionState.Disconnected) {
1107
+ return;
1108
+ }
1109
+ const state = this.connectionState === ConnectionState.Connected;
1102
1110
  const logOpsOnReconnect = this.connectionState === ConnectionState.Connected &&
1103
1111
  !this.firstConnection &&
1104
1112
  this.connectionMode === "write";
1105
1113
  if (logOpsOnReconnect) {
1106
1114
  this.messageCountAfterDisconnection = 0;
1107
1115
  }
1108
- const state = this.connectionState === ConnectionState.Connected;
1109
1116
  // Both protocol and context should not be undefined if we got so far.
1110
- if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1111
- this.context.setConnectionState(state, this.clientId);
1112
- }
1117
+ this.setContextConnectedState(state, (_a = this._deltaManager.connectionManager.readOnlyInfo.readonly) !== null && _a !== void 0 ? _a : false);
1113
1118
  this.protocolHandler.setConnectionState(state, this.clientId);
1114
1119
  raiseConnectedEvent(this.mc.logger, this, state, this.clientId);
1115
1120
  if (logOpsOnReconnect) {
1116
1121
  this.mc.logger.sendTelemetryEvent({ eventName: "OpsSentOnReconnect", count: this.messageCountAfterDisconnection });
1117
1122
  }
1118
1123
  }
1124
+ // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
1119
1125
  submitContainerMessage(type, contents, batch, metadata) {
1120
- const outboundMessageType = type;
1121
- switch (outboundMessageType) {
1126
+ switch (type) {
1122
1127
  case MessageType.Operation:
1123
- case MessageType.RemoteHelp:
1124
- break;
1125
- case MessageType.Summarize: {
1126
- // github #6451: this is only needed for staging so the server
1127
- // know when the protocol tree is included
1128
- // this can be removed once all clients send
1129
- // protocol tree by default
1130
- const summary = contents;
1131
- if (summary.details === undefined) {
1132
- summary.details = {};
1133
- }
1134
- summary.details.includesProtocolTree =
1135
- this.options.summarizeProtocolTree === true;
1136
- break;
1137
- }
1128
+ return this.submitMessage(type, JSON.stringify(contents), batch, metadata);
1129
+ case MessageType.Summarize:
1130
+ return this.submitSummaryMessage(contents);
1138
1131
  default:
1139
1132
  this.close(new GenericError("invalidContainerSubmitOpType", undefined /* error */, { messageType: type }));
1140
1133
  return -1;
1141
1134
  }
1142
- return this.submitMessage(type, contents, batch, metadata);
1135
+ }
1136
+ /** @returns clientSequenceNumber of last message in a batch */
1137
+ submitBatch(batch) {
1138
+ let clientSequenceNumber = -1;
1139
+ for (const message of batch) {
1140
+ clientSequenceNumber = this.submitMessage(MessageType.Operation, message.contents, true, // batch
1141
+ message.metadata);
1142
+ }
1143
+ this._deltaManager.flush();
1144
+ return clientSequenceNumber;
1145
+ }
1146
+ submitSummaryMessage(summary) {
1147
+ // github #6451: this is only needed for staging so the server
1148
+ // know when the protocol tree is included
1149
+ // this can be removed once all clients send
1150
+ // protocol tree by default
1151
+ if (summary.details === undefined) {
1152
+ summary.details = {};
1153
+ }
1154
+ summary.details.includesProtocolTree =
1155
+ this.options.summarizeProtocolTree === true;
1156
+ return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */);
1143
1157
  }
1144
1158
  submitMessage(type, contents, batch, metadata) {
1145
1159
  var _a;
@@ -1154,22 +1168,9 @@ export class Container extends EventEmitterWithErrorHandling {
1154
1168
  processRemoteMessage(message) {
1155
1169
  const local = this.clientId === message.clientId;
1156
1170
  // Allow the protocol handler to process the message
1157
- let result = { immediateNoOp: false };
1158
- try {
1159
- result = this.protocolHandler.processMessage(message, local);
1160
- }
1161
- catch (error) {
1162
- this.close(wrapError(error, (errorMessage) => new DataCorruptionError(errorMessage, extractSafePropertiesFromMessage(message))));
1163
- }
1164
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
1165
- if (isUnpackedRuntimeMessage(message) && !isRuntimeMessage(message)) {
1166
- this.mc.logger.sendTelemetryEvent({ eventName: "UnpackedRuntimeMessage", type: message.type });
1167
- }
1168
- // Forward non system messages to the loaded runtime for processing
1169
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
1170
- if (isRuntimeMessage(message) || isUnpackedRuntimeMessage(message)) {
1171
- this.context.process(message, local, undefined);
1172
- }
1171
+ const result = this.protocolHandler.processMessage(message, local);
1172
+ // Forward messages to the loaded runtime for processing
1173
+ this.context.process(message, local, undefined);
1173
1174
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
1174
1175
  if (this.activeConnection()) {
1175
1176
  if (this.collabWindowTracker === undefined) {
@@ -1178,15 +1179,14 @@ export class Container extends EventEmitterWithErrorHandling {
1178
1179
  // clients.
1179
1180
  // All existing will continue to use settings they got earlier.
1180
1181
  assert(this.serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1181
- this.collabWindowTracker = new CollabWindowTracker((type, contents) => {
1182
+ this.collabWindowTracker = new CollabWindowTracker((type) => {
1182
1183
  assert(this.activeConnection(), 0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
1183
- this.submitMessage(type, contents);
1184
+ this.submitMessage(type);
1184
1185
  }, this.serviceConfiguration.noopTimeFrequency, this.serviceConfiguration.noopCountFrequency);
1185
1186
  }
1186
1187
  this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
1187
1188
  }
1188
1189
  this.emit("op", message);
1189
- return result;
1190
1190
  }
1191
1191
  submitSignal(message) {
1192
1192
  this._deltaManager.submitSignal(JSON.stringify(message));
@@ -1194,15 +1194,7 @@ export class Container extends EventEmitterWithErrorHandling {
1194
1194
  processSignal(message) {
1195
1195
  // No clientId indicates a system signal message.
1196
1196
  if (message.clientId === null) {
1197
- const innerContent = message.content;
1198
- if (innerContent.type === MessageType.ClientJoin) {
1199
- const newClient = innerContent.content;
1200
- this._audience.addMember(newClient.clientId, newClient.client);
1201
- }
1202
- else if (innerContent.type === MessageType.ClientLeave) {
1203
- const leftClientId = innerContent.content;
1204
- this._audience.removeMember(leftClientId);
1205
- }
1197
+ this.protocolHandler.processSignal(message);
1206
1198
  }
1207
1199
  else {
1208
1200
  const local = this.clientId === message.clientId;
@@ -1241,7 +1233,7 @@ export class Container extends EventEmitterWithErrorHandling {
1241
1233
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1242
1234
  // are set. Global requests will still go directly to the loader
1243
1235
  const loader = new RelativeLoader(this, this.loader);
1244
- this._context = await ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new DeltaManagerProxy(this._deltaManager), new QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (message) => this.submitSignal(message), (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
1236
+ this._context = await ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new DeltaManagerProxy(this._deltaManager), new QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp) => this.submitSummaryMessage(summaryOp), (batch) => this.submitBatch(batch), (message) => this.submitSignal(message), (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
1245
1237
  this.emit("contextChanged", codeDetails);
1246
1238
  }
1247
1239
  updateDirtyContainerState(dirty) {
@@ -1254,6 +1246,24 @@ export class Container extends EventEmitterWithErrorHandling {
1254
1246
  logContainerError(warning) {
1255
1247
  this.mc.logger.sendErrorEvent({ eventName: "ContainerWarning" }, warning);
1256
1248
  }
1249
+ /**
1250
+ * Set the connected state of the ContainerContext
1251
+ * This controls the "connected" state of the ContainerRuntime as well
1252
+ * @param state - Is the container currently connected?
1253
+ * @param readonly - Is the container in readonly mode?
1254
+ */
1255
+ setContextConnectedState(state, readonly) {
1256
+ var _a;
1257
+ if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1258
+ /**
1259
+ * We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
1260
+ * ops getting through to the DeltaManager.
1261
+ * The ContainerRuntime's "connected" state simply means it is ok to send ops
1262
+ * See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
1263
+ */
1264
+ this.context.setConnectionState(state && !readonly, this.clientId);
1265
+ }
1266
+ }
1257
1267
  }
1258
1268
  Container.version = "^0.1.0";
1259
1269
  //# sourceMappingURL=container.js.map