@fluidframework/container-loader 1.2.7 → 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/src/container.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  import merge from "lodash/merge";
8
8
  import { v4 as uuid } from "uuid";
9
9
  import {
10
- IDisposable, ITelemetryProperties,
10
+ ITelemetryLogger, ITelemetryProperties,
11
11
  } from "@fluidframework/common-definitions";
12
12
  import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
13
13
  import {
@@ -29,10 +29,9 @@ import {
29
29
  IContainerLoadMode,
30
30
  IFluidCodeDetails,
31
31
  isFluidCodeDetails,
32
+ IBatchMessage,
32
33
  } from "@fluidframework/container-definitions";
33
34
  import {
34
- DataCorruptionError,
35
- extractSafePropertiesFromMessage,
36
35
  GenericError,
37
36
  UsageError,
38
37
  } from "@fluidframework/container-utils";
@@ -50,13 +49,8 @@ import {
50
49
  combineAppAndProtocolSummary,
51
50
  runWithRetry,
52
51
  isFluidResolvedUrl,
53
- isRuntimeMessage,
54
- isUnpackedRuntimeMessage,
55
52
  } from "@fluidframework/driver-utils";
56
- import {
57
- IProtocolHandler,
58
- ProtocolOpHandlerWithClientValidation,
59
- } from "@fluidframework/protocol-base";
53
+ import { IQuorumSnapshot } from "@fluidframework/protocol-base";
60
54
  import {
61
55
  IClient,
62
56
  IClientConfiguration,
@@ -64,14 +58,12 @@ import {
64
58
  ICommittedProposal,
65
59
  IDocumentAttributes,
66
60
  IDocumentMessage,
67
- IProcessMessageResult,
68
61
  IProtocolState,
69
62
  IQuorumClients,
70
63
  IQuorumProposals,
71
64
  ISequencedClient,
72
65
  ISequencedDocumentMessage,
73
66
  ISequencedProposal,
74
- ISignalClient,
75
67
  ISignalMessage,
76
68
  ISnapshotTree,
77
69
  ISummaryContent,
@@ -100,15 +92,21 @@ import { DeltaManager, IConnectionArgs } from "./deltaManager";
100
92
  import { DeltaManagerProxy } from "./deltaManagerProxy";
101
93
  import { ILoaderOptions, Loader, RelativeLoader } from "./loader";
102
94
  import { pkgVersion } from "./packageVersion";
103
- import { ConnectionStateHandler } from "./connectionStateHandler";
104
- import { RetriableDocumentStorageService } from "./retriableDocumentStorageService";
105
- import { ProtocolTreeStorageService } from "./protocolTreeDocumentStorageService";
106
- import { BlobOnlyStorage, ContainerStorageAdapter } from "./containerStorageAdapter";
95
+ import { ContainerStorageAdapter } from "./containerStorageAdapter";
96
+ import {
97
+ IConnectionStateHandler,
98
+ createConnectionStateHandler,
99
+ } from "./connectionStateHandler";
107
100
  import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
108
101
  import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy } from "./quorum";
109
102
  import { CollabWindowTracker } from "./collabWindowTracker";
110
103
  import { ConnectionManager } from "./connectionManager";
111
104
  import { ConnectionState } from "./connectionState";
105
+ import {
106
+ IProtocolHandler,
107
+ ProtocolHandler,
108
+ ProtocolHandlerBuilder,
109
+ } from "./protocol";
112
110
 
113
111
  const detachedContainerRefSeqNumber = 0;
114
112
 
@@ -149,14 +147,19 @@ export interface IContainerConfig {
149
147
  }
150
148
 
151
149
  /**
152
- * Waits until container connects to delta storage and gets up-to-date
150
+ * Waits until container connects to delta storage and gets up-to-date.
151
+ *
153
152
  * Useful when resolving URIs and hitting 404, due to container being loaded from (stale) snapshot and not being
154
153
  * up to date. Host may chose to wait in such case and retry resolving URI.
154
+ *
155
155
  * Warning: Will wait infinitely for connection to establish if there is no connection.
156
156
  * May result in deadlock if Container.disconnect() is called and never followed by a call to Container.connect().
157
- * @returns true: container is up to date, it processed all the ops that were know at the time of first connection
158
- * false: storage does not provide indication of how far the client is. Container processed
159
- * all the ops known to it, but it maybe still behind.
157
+ *
158
+ * @returns `true`: container is up to date, it processed all the ops that were know at the time of first connection.
159
+ *
160
+ * `false`: storage does not provide indication of how far the client is. Container processed all the ops known to it,
161
+ * but it maybe still behind.
162
+ *
160
163
  * @throws an error beginning with `"Container closed"` if the container is closed before it catches up.
161
164
  */
162
165
  export async function waitContainerToCatchUp(container: IContainer) {
@@ -179,6 +182,10 @@ export async function waitContainerToCatchUp(container: IContainer) {
179
182
  };
180
183
  container.on("closed", closedCallback);
181
184
 
185
+ // Depending on config, transition to "connected" state may include the guarantee
186
+ // that all known ops have been processed. If so, we may introduce additional wait here.
187
+ // Waiting for "connected" state in either case gets us at least to our own Join op
188
+ // which is a reasonable approximation of "caught up"
182
189
  const waitForOps = () => {
183
190
  assert(container.connectionState === ConnectionState.CatchingUp
184
191
  || container.connectionState === ConnectionState.Connected,
@@ -228,6 +235,24 @@ const getCodeProposal =
228
235
  // eslint-disable-next-line @typescript-eslint/no-unsafe-return
229
236
  (quorum: IQuorumProposals) => quorum.get("code") ?? quorum.get("code2");
230
237
 
238
+ /**
239
+ * Helper function to report to telemetry cases where operation takes longer than expected (1s)
240
+ * @param logger - logger to use
241
+ * @param eventName - event name
242
+ * @param action - functor to call and measure
243
+ */
244
+ async function ReportIfTooLong(
245
+ logger: ITelemetryLogger,
246
+ eventName: string,
247
+ action: () => Promise<ITelemetryProperties>,
248
+ ) {
249
+ const event = PerformanceEvent.start(logger, { eventName });
250
+ const props = await action();
251
+ if (event.duration > 1000) {
252
+ event.end(props);
253
+ }
254
+ }
255
+
231
256
  /**
232
257
  * State saved by a container at close time, to be used to load a new instance
233
258
  * of the container to the same state
@@ -252,6 +277,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
252
277
  loader: Loader,
253
278
  loadOptions: IContainerLoadOptions,
254
279
  pendingLocalState?: IPendingContainerState,
280
+ protocolHandlerBuilder?: ProtocolHandlerBuilder,
255
281
  ): Promise<Container> {
256
282
  const container = new Container(
257
283
  loader,
@@ -260,7 +286,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
260
286
  resolvedUrl: loadOptions.resolvedUrl,
261
287
  canReconnect: loadOptions.canReconnect,
262
288
  serializedContainerState: pendingLocalState,
263
- });
289
+ },
290
+ protocolHandlerBuilder);
264
291
 
265
292
  return PerformanceEvent.timedExecAsync(
266
293
  container.mc.logger,
@@ -308,10 +335,12 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
308
335
  public static async createDetached(
309
336
  loader: Loader,
310
337
  codeDetails: IFluidCodeDetails,
338
+ protocolHandlerBuilder?: ProtocolHandlerBuilder,
311
339
  ): Promise<Container> {
312
340
  const container = new Container(
313
341
  loader,
314
- {});
342
+ {},
343
+ protocolHandlerBuilder);
315
344
 
316
345
  return PerformanceEvent.timedExecAsync(
317
346
  container.mc.logger,
@@ -330,10 +359,13 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
330
359
  public static async rehydrateDetachedFromSnapshot(
331
360
  loader: Loader,
332
361
  snapshot: string,
362
+ protocolHandlerBuilder?: ProtocolHandlerBuilder,
333
363
  ): Promise<Container> {
334
364
  const container = new Container(
335
365
  loader,
336
- {});
366
+ {},
367
+ protocolHandlerBuilder);
368
+
337
369
  return PerformanceEvent.timedExecAsync(
338
370
  container.mc.logger,
339
371
  { eventName: "RehydrateDetachedFromSnapshot" },
@@ -360,7 +392,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
360
392
  // Only transition states if currently loading
361
393
  if (this._lifecycleState === "loading") {
362
394
  // Propagate current connection state through the system.
363
- this.propagateConnectionState();
395
+ this.propagateConnectionState(true /* initial transition */);
364
396
  this._lifecycleState = "loaded";
365
397
  }
366
398
  }
@@ -371,23 +403,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
371
403
 
372
404
  private _attachState = AttachState.Detached;
373
405
 
374
- private readonly _storage: ContainerStorageAdapter;
406
+ private readonly storageService: ContainerStorageAdapter;
375
407
  public get storage(): IDocumentStorageService {
376
- return this._storage;
377
- }
378
-
379
- private _storageService: IDocumentStorageService & IDisposable | undefined;
380
- private get storageService(): IDocumentStorageService {
381
- if (this._storageService === undefined) {
382
- throw new Error("Attempted to access storageService before it was defined");
383
- }
384
- return this._storageService;
408
+ return this.storageService;
385
409
  }
386
410
 
387
411
  private readonly clientDetailsOverride: IClientDetails | undefined;
388
412
  private readonly _deltaManager: DeltaManager<ConnectionManager>;
389
413
  private service: IDocumentService | undefined;
390
- private readonly _audience: Audience;
391
414
 
392
415
  private _context: ContainerContext | undefined;
393
416
  private get context() {
@@ -416,7 +439,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
416
439
 
417
440
  private lastVisible: number | undefined;
418
441
  private readonly visibilityEventHandler: (() => void) | undefined;
419
- private readonly connectionStateHandler: ConnectionStateHandler;
442
+ private readonly connectionStateHandler: IConnectionStateHandler;
420
443
 
421
444
  private setAutoReconnectTime = performance.now();
422
445
 
@@ -458,7 +481,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
458
481
  }
459
482
 
460
483
  public get connected(): boolean {
461
- return this.connectionStateHandler.connected;
484
+ return this.connectionStateHandler.connectionState === ConnectionState.Connected;
462
485
  }
463
486
 
464
487
  /**
@@ -469,12 +492,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
469
492
  return this._deltaManager.serviceConfiguration;
470
493
  }
471
494
 
495
+ private _clientId: string | undefined;
496
+
472
497
  /**
473
498
  * The server provided id of the client.
474
499
  * Set once this.connected is true, otherwise undefined
475
500
  */
476
501
  public get clientId(): string | undefined {
477
- return this.connectionStateHandler.clientId;
502
+ return this._clientId;
478
503
  }
479
504
 
480
505
  /**
@@ -510,13 +535,13 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
510
535
  * Retrieves the audience associated with the document
511
536
  */
512
537
  public get audience(): IAudience {
513
- return this._audience;
538
+ return this.protocolHandler.audience;
514
539
  }
515
540
 
516
541
  /**
517
542
  * Returns true if container is dirty.
518
543
  * Which means data loss if container is closed at that same moment
519
- * Most likely that happens when there is no network connection to ordering service
544
+ * Most likely that happens when there is no network connection to Relay Service
520
545
  */
521
546
  public get isDirty() {
522
547
  return this._dirtyContainer;
@@ -531,6 +556,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
531
556
  constructor(
532
557
  private readonly loader: Loader,
533
558
  config: IContainerConfig,
559
+ private readonly protocolHandlerBuilder?: ProtocolHandlerBuilder,
534
560
  ) {
535
561
  super((name, error) => {
536
562
  this.mc.logger.sendErrorEvent(
@@ -540,7 +566,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
540
566
  },
541
567
  error);
542
568
  });
543
- this._audience = new Audience();
544
569
 
545
570
  this.clientDetailsOverride = config.clientDetailsOverride;
546
571
  this._resolvedUrl = config.resolvedUrl;
@@ -566,6 +591,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
566
591
  containerAttachState: () => this._attachState,
567
592
  containerLifecycleState: () => this._lifecycleState,
568
593
  containerConnectionState: () => ConnectionState[this.connectionState],
594
+ serializedContainer: config.serializedContainerState !== undefined,
569
595
  },
570
596
  // we need to be judicious with our logging here to avoid generating too much data
571
597
  // all data logged here should be broadly applicable, and not specific to a
@@ -578,6 +604,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
578
604
  containerLoadedFromVersionId: () => this.loadedFromVersion?.id,
579
605
  containerLoadedFromVersionDate: () => this.loadedFromVersion?.date,
580
606
  // message information to associate errors with the specific execution state
607
+ // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
581
608
  dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
582
609
  dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
583
610
  dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId,
@@ -598,11 +625,21 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
598
625
  summarizeProtocolTree,
599
626
  };
600
627
 
601
- this.connectionStateHandler = new ConnectionStateHandler(
628
+ this._deltaManager = this.createDeltaManager();
629
+
630
+ this._clientId = config.serializedContainerState?.clientId;
631
+ this.connectionStateHandler = createConnectionStateHandler(
602
632
  {
603
- quorumClients: () => this._protocolHandler?.quorum,
604
- logConnectionStateChangeTelemetry: (value, oldState, reason) =>
605
- this.logConnectionStateChangeTelemetry(value, oldState, reason),
633
+ logger: this.mc.logger,
634
+ connectionStateChanged: (value, oldState, reason) => {
635
+ if (value === ConnectionState.Connected) {
636
+ this._clientId = this.connectionStateHandler.pendingClientId;
637
+ }
638
+ this.logConnectionStateChangeTelemetry(value, oldState, reason);
639
+ if (this._lifecycleState === "loaded") {
640
+ this.propagateConnectionState(false /* initial transition */);
641
+ }
642
+ },
606
643
  shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
607
644
  maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
608
645
  logConnectionIssue: (eventName: string, details?: ITelemetryProperties) => {
@@ -614,35 +651,21 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
614
651
  ...(details === undefined ? {} : { details: JSON.stringify(details) }),
615
652
  });
616
653
  },
617
- connectionStateChanged: () => {
618
- // Fire events only if container is fully loaded and not closed
619
- if (this._lifecycleState === "loaded") {
620
- this.propagateConnectionState();
621
- }
622
- },
623
654
  },
624
- this.mc.logger,
625
- config.serializedContainerState?.clientId,
655
+ this.deltaManager,
656
+ this._clientId,
626
657
  );
627
658
 
628
659
  this.on(savedContainerEvent, () => {
629
660
  this.connectionStateHandler.containerSaved();
630
661
  });
631
662
 
632
- this._deltaManager = this.createDeltaManager();
633
- this._storage = new ContainerStorageAdapter(
634
- () => {
635
- if (this.attachState !== AttachState.Attached) {
636
- if (this.loader.services.detachedBlobStorage !== undefined) {
637
- return new BlobOnlyStorage(this.loader.services.detachedBlobStorage, this.mc.logger);
638
- }
639
- this.mc.logger.sendErrorEvent({
640
- eventName: "NoRealStorageInDetachedContainer",
641
- });
642
- throw new Error("Real storage calls not allowed in Unattached container");
643
- }
644
- return this.storageService;
645
- },
663
+ this.storageService = new ContainerStorageAdapter(
664
+ this.loader.services.detachedBlobStorage,
665
+ this.mc.logger,
666
+ this.options.summarizeProtocolTree === true
667
+ ? () => this.captureProtocolSummary()
668
+ : undefined,
646
669
  );
647
670
 
648
671
  const isDomAvailable = typeof document === "object" &&
@@ -742,7 +765,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
742
765
 
743
766
  this._context?.dispose(error !== undefined ? new Error(error.message) : undefined);
744
767
 
745
- this._storageService?.dispose();
768
+ this.storageService.dispose();
746
769
 
747
770
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
748
771
  // about file, like file being overwritten in storage, but client having stale local cache.
@@ -767,13 +790,12 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
767
790
  // runtime matches pending ops to successful ones by clientId and client seq num, so we need to close the
768
791
  // container at the same time we get pending state, otherwise this container could reconnect and resubmit with
769
792
  // a new clientId and a future container using stale pending state without the new clientId would resubmit them
770
-
771
793
  assert(this.attachState === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
772
794
  assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid",
773
795
  0x0d2 /* "resolved url should be valid Fluid url" */);
774
796
  assert(!!this._protocolHandler, 0x2e3 /* "Must have a valid protocol handler instance" */);
775
797
  assert(this._protocolHandler.attributes.term !== undefined,
776
- 0x30b /* Must have a valid protocol handler instance */);
798
+ 0x37e /* Must have a valid protocol handler instance */);
777
799
  const pendingState: IPendingContainerState = {
778
800
  pendingRuntimeState: this.context.getPendingLocalState(),
779
801
  url: this.resolvedUrl.url,
@@ -782,6 +804,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
782
804
  clientId: this.clientId,
783
805
  };
784
806
 
807
+ this.mc.logger.sendTelemetryEvent({ eventName: "CloseAndGetPendingLocalState" });
808
+
785
809
  this.close();
786
810
 
787
811
  return JSON.stringify(pendingState);
@@ -863,7 +887,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
863
887
  const resolvedUrl = this.service.resolvedUrl;
864
888
  ensureFluidResolvedUrl(resolvedUrl);
865
889
  this._resolvedUrl = resolvedUrl;
866
- await this.connectStorageService();
890
+ await this.storageService.connectToService(this.service);
867
891
 
868
892
  if (hasAttachmentBlobs) {
869
893
  // upload blobs to storage
@@ -901,8 +925,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
901
925
  this._attachState = AttachState.Attached;
902
926
  this.emit("attached");
903
927
 
904
- // Propagate current connection state through the system.
905
- this.propagateConnectionState();
906
928
  if (!this.closed) {
907
929
  this.resumeInternal({ fetchOpsFromStorage: false, reason: "createDetached" });
908
930
  }
@@ -1078,9 +1100,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1078
1100
  /**
1079
1101
  * Load container.
1080
1102
  *
1081
- * @param specifiedVersion - one of the following
1082
- * - undefined - fetch latest snapshot
1083
- * - otherwise, version sha to load snapshot
1103
+ * @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
1084
1104
  */
1085
1105
  private async load(
1086
1106
  specifiedVersion: string | undefined,
@@ -1114,10 +1134,10 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1114
1134
  }
1115
1135
 
1116
1136
  if (!pendingLocalState) {
1117
- await this.connectStorageService();
1137
+ await this.storageService.connectToService(this.service);
1118
1138
  } else {
1119
1139
  // if we have pendingLocalState we can load without storage; don't wait for connection
1120
- this.connectStorageService().catch((error) => this.close(error));
1140
+ this.storageService.connectToService(this.service).catch((error) => this.close(error));
1121
1141
  }
1122
1142
 
1123
1143
  this._attachState = AttachState.Attached;
@@ -1158,14 +1178,21 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1158
1178
 
1159
1179
  // ...load in the existing quorum
1160
1180
  // Initialize the protocol handler
1161
- this._protocolHandler = pendingLocalState === undefined
1162
- ? await this.initializeProtocolStateFromSnapshot(attributes, this.storageService, snapshot)
1163
- : await this.initializeProtocolState(
1181
+ if (pendingLocalState === undefined) {
1182
+ await this.initializeProtocolStateFromSnapshot(
1183
+ attributes,
1184
+ this.storageService,
1185
+ snapshot);
1186
+ } else {
1187
+ this.initializeProtocolState(
1164
1188
  attributes,
1165
- pendingLocalState.protocol.members,
1166
- pendingLocalState.protocol.proposals,
1167
- pendingLocalState.protocol.values,
1189
+ {
1190
+ members: pendingLocalState.protocol.members,
1191
+ proposals: pendingLocalState.protocol.proposals,
1192
+ values: pendingLocalState.protocol.values,
1193
+ }, // pending IQuorumSnapshot
1168
1194
  );
1195
+ }
1169
1196
 
1170
1197
  const codeDetails = this.getCodeDetailsFromQuorum();
1171
1198
  await this.instantiateContext(
@@ -1175,17 +1202,20 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1175
1202
  pendingLocalState?.pendingRuntimeState,
1176
1203
  );
1177
1204
 
1178
- // Internal context is fully loaded at this point
1179
- this.setLoaded();
1180
-
1181
1205
  // We might have hit some failure that did not manifest itself in exception in this flow,
1182
1206
  // do not start op processing in such case - static version of Container.load() will handle it correctly.
1183
1207
  if (!this.closed) {
1184
1208
  if (opsBeforeReturnP !== undefined) {
1185
1209
  this._deltaManager.inbound.resume();
1186
1210
 
1187
- await opsBeforeReturnP;
1188
- await this._deltaManager.inbound.waitTillProcessingDone();
1211
+ await ReportIfTooLong(
1212
+ this.mc.logger,
1213
+ "WaitOps",
1214
+ async () => { await opsBeforeReturnP; return {}; });
1215
+ await ReportIfTooLong(
1216
+ this.mc.logger,
1217
+ "WaitOpProcessing",
1218
+ async () => this._deltaManager.inbound.waitTillProcessingDone());
1189
1219
 
1190
1220
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1191
1221
  this._deltaManager.inbound.pause();
@@ -1215,9 +1245,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1215
1245
  throw new Error("Container was closed while load()");
1216
1246
  }
1217
1247
 
1248
+ // Internal context is fully loaded at this point
1249
+ this.setLoaded();
1250
+
1218
1251
  return {
1219
1252
  sequenceNumber: attributes.sequenceNumber,
1220
1253
  version: versionId,
1254
+ dmLastProcessedSeqNumber: this._deltaManager.lastSequenceNumber,
1255
+ dmLastKnownSeqNumber: this._deltaManager.lastKnownSeqNumber,
1221
1256
  };
1222
1257
  }
1223
1258
 
@@ -1232,11 +1267,13 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1232
1267
 
1233
1268
  // Need to just seed the source data in the code quorum. Quorum itself is empty
1234
1269
  const qValues = initQuorumValuesFromCodeDetails(source);
1235
- this._protocolHandler = await this.initializeProtocolState(
1270
+ this.initializeProtocolState(
1236
1271
  attributes,
1237
- [], // members
1238
- [], // proposals
1239
- qValues,
1272
+ {
1273
+ members: [],
1274
+ proposals: [],
1275
+ values: qValues,
1276
+ }, // IQuorumSnapShot
1240
1277
  );
1241
1278
 
1242
1279
  // The load context - given we seeded the quorum - will be great
@@ -1255,24 +1292,26 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1255
1292
  }
1256
1293
 
1257
1294
  const snapshotTree = getSnapshotTreeFromSerializedContainer(detachedContainerSnapshot);
1258
- this._storage.loadSnapshotForRehydratingContainer(snapshotTree);
1259
- const attributes = await this.getDocumentAttributes(this._storage, snapshotTree);
1295
+ this.storageService.loadSnapshotForRehydratingContainer(snapshotTree);
1296
+ const attributes = await this.getDocumentAttributes(this.storageService, snapshotTree);
1260
1297
 
1261
1298
  await this.attachDeltaManagerOpHandler(attributes);
1262
1299
 
1263
1300
  // Initialize the protocol handler
1264
1301
  const baseTree = getProtocolSnapshotTree(snapshotTree);
1265
1302
  const qValues = await readAndParse<[string, ICommittedProposal][]>(
1266
- this._storage,
1303
+ this.storageService,
1267
1304
  baseTree.blobs.quorumValues,
1268
1305
  );
1269
1306
  const codeDetails = getCodeDetailsFromQuorumValues(qValues);
1270
- this._protocolHandler =
1271
- await this.initializeProtocolState(
1272
- attributes,
1273
- [], // members
1274
- [], // proposals
1275
- codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : []);
1307
+ this.initializeProtocolState(
1308
+ attributes,
1309
+ {
1310
+ members: [],
1311
+ proposals: [],
1312
+ values: codeDetails !== undefined ? initQuorumValuesFromCodeDetails(codeDetails) : [],
1313
+ }, // IQuorumSnapShot
1314
+ );
1276
1315
 
1277
1316
  await this.instantiateContextDetached(
1278
1317
  true, // existing
@@ -1282,28 +1321,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1282
1321
  this.setLoaded();
1283
1322
  }
1284
1323
 
1285
- private async connectStorageService(): Promise<void> {
1286
- if (this._storageService !== undefined) {
1287
- return;
1288
- }
1289
-
1290
- assert(this.service !== undefined, 0x1ef /* "services must be defined" */);
1291
- const storageService = await this.service.connectToStorage();
1292
-
1293
- this._storageService =
1294
- new RetriableDocumentStorageService(storageService, this.mc.logger);
1295
-
1296
- if (this.options.summarizeProtocolTree === true) {
1297
- this.mc.logger.sendTelemetryEvent({ eventName: "summarizeProtocolTreeEnabled" });
1298
- this._storageService =
1299
- new ProtocolTreeStorageService(this._storageService, () => this.captureProtocolSummary());
1300
- }
1301
-
1302
- // ensure we did not lose that policy in the process of wrapping
1303
- assert(storageService.policies?.minBlobSize === this.storageService.policies?.minBlobSize,
1304
- 0x0e0 /* "lost minBlobSize policy" */);
1305
- }
1306
-
1307
1324
  private async getDocumentAttributes(
1308
1325
  storage: IDocumentStorageService,
1309
1326
  tree: ISnapshotTree | undefined,
@@ -1335,43 +1352,35 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1335
1352
  attributes: IDocumentAttributes,
1336
1353
  storage: IDocumentStorageService,
1337
1354
  snapshot: ISnapshotTree | undefined,
1338
- ): Promise<IProtocolHandler> {
1339
- let members: [string, ISequencedClient][] = [];
1340
- let proposals: [number, ISequencedProposal, string[]][] = [];
1341
- let values: [string, any][] = [];
1355
+ ): Promise<void> {
1356
+ const quorumSnapshot: IQuorumSnapshot = {
1357
+ members: [],
1358
+ proposals: [],
1359
+ values: [],
1360
+ };
1342
1361
 
1343
1362
  if (snapshot !== undefined) {
1344
1363
  const baseTree = getProtocolSnapshotTree(snapshot);
1345
- [members, proposals, values] = await Promise.all([
1364
+ [quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values] = await Promise.all([
1346
1365
  readAndParse<[string, ISequencedClient][]>(storage, baseTree.blobs.quorumMembers),
1347
1366
  readAndParse<[number, ISequencedProposal, string[]][]>(storage, baseTree.blobs.quorumProposals),
1348
1367
  readAndParse<[string, ICommittedProposal][]>(storage, baseTree.blobs.quorumValues),
1349
1368
  ]);
1350
1369
  }
1351
1370
 
1352
- const protocolHandler = await this.initializeProtocolState(
1353
- attributes,
1354
- members,
1355
- proposals,
1356
- values);
1357
-
1358
- return protocolHandler;
1371
+ this.initializeProtocolState(attributes, quorumSnapshot);
1359
1372
  }
1360
1373
 
1361
- private async initializeProtocolState(
1374
+ private initializeProtocolState(
1362
1375
  attributes: IDocumentAttributes,
1363
- members: [string, ISequencedClient][],
1364
- proposals: [number, ISequencedProposal, string[]][],
1365
- values: [string, any][],
1366
- ): Promise<IProtocolHandler> {
1367
- const protocol = new ProtocolOpHandlerWithClientValidation(
1368
- attributes.minimumSequenceNumber,
1369
- attributes.sequenceNumber,
1370
- attributes.term,
1371
- members,
1372
- proposals,
1373
- values,
1374
- (key, value) => this.submitMessage(MessageType.Propose, { key, value }),
1376
+ quorumSnapshot: IQuorumSnapshot,
1377
+ ): void {
1378
+ const protocolHandlerBuilder =
1379
+ this.protocolHandlerBuilder ?? ((...args) => new ProtocolHandler(...args, new Audience()));
1380
+ const protocol = protocolHandlerBuilder(
1381
+ attributes,
1382
+ quorumSnapshot,
1383
+ (key, value) => this.submitMessage(MessageType.Propose, JSON.stringify({ key, value })),
1375
1384
  );
1376
1385
 
1377
1386
  const protocolLogger = ChildLogger.create(this.subLogger, "ProtocolHandler");
@@ -1404,8 +1413,11 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1404
1413
  });
1405
1414
  }
1406
1415
  });
1407
-
1408
- return protocol;
1416
+ // we need to make sure this member get set in a synchronous context,
1417
+ // or other things can happen after the object that will be set is created, but not yet set
1418
+ // this was breaking this._initialClients handling
1419
+ //
1420
+ this._protocolHandler = protocol;
1409
1421
  }
1410
1422
 
1411
1423
  private captureProtocolSummary(): ISummaryTree {
@@ -1494,14 +1506,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1494
1506
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1495
1507
  deltaManager.inboundSignal.pause();
1496
1508
 
1497
- deltaManager.on("connect", (details: IConnectionDetails, opsBehind?: number) => {
1498
- // Back-compat for new client and old server.
1499
- this._audience.clear();
1500
-
1501
- for (const priorClient of details.initialClients ?? []) {
1502
- this._audience.addMember(priorClient.clientId, priorClient.client);
1503
- }
1504
-
1509
+ deltaManager.on("connect", (details: IConnectionDetails, _opsBehind?: number) => {
1505
1510
  this.connectionStateHandler.receivedConnectEvent(
1506
1511
  this.connectionMode,
1507
1512
  details,
@@ -1524,6 +1529,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1524
1529
  });
1525
1530
 
1526
1531
  deltaManager.on("readonly", (readonly) => {
1532
+ this.setContextConnectedState(this.connectionState === ConnectionState.Connected, readonly);
1527
1533
  this.emit("readonly", readonly);
1528
1534
  });
1529
1535
 
@@ -1578,11 +1584,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1578
1584
  opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
1579
1585
  }
1580
1586
  }
1581
- if (this.firstConnection) {
1582
- connectionInitiationReason = "InitialConnect";
1583
- } else {
1584
- connectionInitiationReason = "AutoReconnect";
1585
- }
1587
+ connectionInitiationReason = this.firstConnection ? "InitialConnect" : "AutoReconnect";
1586
1588
  }
1587
1589
 
1588
1590
  this.mc.logger.sendPerformanceEvent({
@@ -1608,7 +1610,17 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1608
1610
  }
1609
1611
  }
1610
1612
 
1611
- private propagateConnectionState() {
1613
+ private propagateConnectionState(initialTransition: boolean) {
1614
+ // When container loaded, we want to propagate initial connection state.
1615
+ // After that, we communicate only transitions to Connected & Disconnected states, skipping all other states.
1616
+ // This can be changed in the future, for example we likely should add "CatchingUp" event on Container.
1617
+ if (!initialTransition &&
1618
+ this.connectionState !== ConnectionState.Connected &&
1619
+ this.connectionState !== ConnectionState.Disconnected) {
1620
+ return;
1621
+ }
1622
+ const state = this.connectionState === ConnectionState.Connected;
1623
+
1612
1624
  const logOpsOnReconnect: boolean =
1613
1625
  this.connectionState === ConnectionState.Connected &&
1614
1626
  !this.firstConnection &&
@@ -1617,13 +1629,9 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1617
1629
  this.messageCountAfterDisconnection = 0;
1618
1630
  }
1619
1631
 
1620
- const state = this.connectionState === ConnectionState.Connected;
1621
-
1622
1632
  // Both protocol and context should not be undefined if we got so far.
1623
1633
 
1624
- if (this._context?.disposed === false) {
1625
- this.context.setConnectionState(state, this.clientId);
1626
- }
1634
+ this.setContextConnectedState(state, this._deltaManager.connectionManager.readOnlyInfo.readonly ?? false);
1627
1635
  this.protocolHandler.setConnectionState(state, this.clientId);
1628
1636
  raiseConnectedEvent(this.mc.logger, this, state, this.clientId);
1629
1637
 
@@ -1633,35 +1641,53 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1633
1641
  }
1634
1642
  }
1635
1643
 
1644
+ // back-compat: ADO #1385: Remove in the future, summary op should come through submitSummaryMessage()
1636
1645
  private submitContainerMessage(type: MessageType, contents: any, batch?: boolean, metadata?: any): number {
1637
- const outboundMessageType: string = type;
1638
- switch (outboundMessageType) {
1646
+ switch (type) {
1639
1647
  case MessageType.Operation:
1640
- case MessageType.RemoteHelp:
1641
- break;
1642
- case MessageType.Summarize: {
1643
- // github #6451: this is only needed for staging so the server
1644
- // know when the protocol tree is included
1645
- // this can be removed once all clients send
1646
- // protocol tree by default
1647
- const summary = contents as ISummaryContent;
1648
- if (summary.details === undefined) {
1649
- summary.details = {};
1650
- }
1651
- summary.details.includesProtocolTree =
1652
- this.options.summarizeProtocolTree === true;
1653
- break;
1654
- }
1648
+ return this.submitMessage(
1649
+ type,
1650
+ JSON.stringify(contents),
1651
+ batch,
1652
+ metadata);
1653
+ case MessageType.Summarize:
1654
+ return this.submitSummaryMessage(contents as unknown as ISummaryContent);
1655
1655
  default:
1656
1656
  this.close(new GenericError("invalidContainerSubmitOpType",
1657
1657
  undefined /* error */,
1658
1658
  { messageType: type }));
1659
1659
  return -1;
1660
1660
  }
1661
- return this.submitMessage(type, contents, batch, metadata);
1662
1661
  }
1663
1662
 
1664
- private submitMessage(type: MessageType, contents: any, batch?: boolean, metadata?: any): number {
1663
+ /** @returns clientSequenceNumber of last message in a batch */
1664
+ private submitBatch(batch: IBatchMessage[]): number {
1665
+ let clientSequenceNumber = -1;
1666
+ for (const message of batch) {
1667
+ clientSequenceNumber = this.submitMessage(
1668
+ MessageType.Operation,
1669
+ message.contents,
1670
+ true, // batch
1671
+ message.metadata);
1672
+ }
1673
+ this._deltaManager.flush();
1674
+ return clientSequenceNumber;
1675
+ }
1676
+
1677
+ private submitSummaryMessage(summary: ISummaryContent) {
1678
+ // github #6451: this is only needed for staging so the server
1679
+ // know when the protocol tree is included
1680
+ // this can be removed once all clients send
1681
+ // protocol tree by default
1682
+ if (summary.details === undefined) {
1683
+ summary.details = {};
1684
+ }
1685
+ summary.details.includesProtocolTree =
1686
+ this.options.summarizeProtocolTree === true;
1687
+ return this.submitMessage(MessageType.Summarize, JSON.stringify(summary), false /* batch */);
1688
+ }
1689
+
1690
+ private submitMessage(type: MessageType, contents?: string, batch?: boolean, metadata?: any): number {
1665
1691
  if (this.connectionState !== ConnectionState.Connected) {
1666
1692
  this.mc.logger.sendErrorEvent({ eventName: "SubmitMessageWithNoConnection", type });
1667
1693
  return -1;
@@ -1672,28 +1698,14 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1672
1698
  return this._deltaManager.submit(type, contents, batch, metadata);
1673
1699
  }
1674
1700
 
1675
- private processRemoteMessage(message: ISequencedDocumentMessage): IProcessMessageResult {
1701
+ private processRemoteMessage(message: ISequencedDocumentMessage) {
1676
1702
  const local = this.clientId === message.clientId;
1677
1703
 
1678
1704
  // Allow the protocol handler to process the message
1679
- let result: IProcessMessageResult = { immediateNoOp: false };
1680
- try {
1681
- result = this.protocolHandler.processMessage(message, local);
1682
- } catch (error) {
1683
- this.close(wrapError(error, (errorMessage) =>
1684
- new DataCorruptionError(errorMessage, extractSafePropertiesFromMessage(message))));
1685
- }
1705
+ const result = this.protocolHandler.processMessage(message, local);
1686
1706
 
1687
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
1688
- if (isUnpackedRuntimeMessage(message) && !isRuntimeMessage(message)) {
1689
- this.mc.logger.sendTelemetryEvent(
1690
- { eventName: "UnpackedRuntimeMessage", type: message.type });
1691
- }
1692
- // Forward non system messages to the loaded runtime for processing
1693
- // eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
1694
- if (isRuntimeMessage(message) || isUnpackedRuntimeMessage(message)) {
1695
- this.context.process(message, local, undefined);
1696
- }
1707
+ // Forward messages to the loaded runtime for processing
1708
+ this.context.process(message, local, undefined);
1697
1709
 
1698
1710
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
1699
1711
  if (this.activeConnection()) {
@@ -1706,10 +1718,10 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1706
1718
  this.serviceConfiguration !== undefined,
1707
1719
  0x2e4 /* "there should be service config for active connection" */);
1708
1720
  this.collabWindowTracker = new CollabWindowTracker(
1709
- (type, contents) => {
1721
+ (type) => {
1710
1722
  assert(this.activeConnection(),
1711
1723
  0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
1712
- this.submitMessage(type, contents);
1724
+ this.submitMessage(type);
1713
1725
  },
1714
1726
  this.serviceConfiguration.noopTimeFrequency,
1715
1727
  this.serviceConfiguration.noopCountFrequency,
@@ -1719,8 +1731,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1719
1731
  }
1720
1732
 
1721
1733
  this.emit("op", message);
1722
-
1723
- return result;
1724
1734
  }
1725
1735
 
1726
1736
  private submitSignal(message: any) {
@@ -1730,14 +1740,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1730
1740
  private processSignal(message: ISignalMessage) {
1731
1741
  // No clientId indicates a system signal message.
1732
1742
  if (message.clientId === null) {
1733
- const innerContent = message.content as { content: any; type: string; };
1734
- if (innerContent.type === MessageType.ClientJoin) {
1735
- const newClient = innerContent.content as ISignalClient;
1736
- this._audience.addMember(newClient.clientId, newClient.client);
1737
- } else if (innerContent.type === MessageType.ClientLeave) {
1738
- const leftClientId = innerContent.content as string;
1739
- this._audience.removeMember(leftClientId);
1740
- }
1743
+ this.protocolHandler.processSignal(message);
1741
1744
  } else {
1742
1745
  const local = this.clientId === message.clientId;
1743
1746
  this.context.processSignal(message, local);
@@ -1803,6 +1806,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1803
1806
  new QuorumProxy(this.protocolHandler.quorum),
1804
1807
  loader,
1805
1808
  (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata),
1809
+ (summaryOp: ISummaryContent) => this.submitSummaryMessage(summaryOp),
1810
+ (batch: IBatchMessage[]) => this.submitBatch(batch),
1806
1811
  (message) => this.submitSignal(message),
1807
1812
  (error?: ICriticalContainerError) => this.close(error),
1808
1813
  Container.version,
@@ -1825,4 +1830,22 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1825
1830
  private logContainerError(warning: ContainerWarning) {
1826
1831
  this.mc.logger.sendErrorEvent({ eventName: "ContainerWarning" }, warning);
1827
1832
  }
1833
+
1834
+ /**
1835
+ * Set the connected state of the ContainerContext
1836
+ * This controls the "connected" state of the ContainerRuntime as well
1837
+ * @param state - Is the container currently connected?
1838
+ * @param readonly - Is the container in readonly mode?
1839
+ */
1840
+ private setContextConnectedState(state: boolean, readonly: boolean): void {
1841
+ if (this._context?.disposed === false) {
1842
+ /**
1843
+ * We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
1844
+ * ops getting through to the DeltaManager.
1845
+ * The ContainerRuntime's "connected" state simply means it is ok to send ops
1846
+ * See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
1847
+ */
1848
+ this.context.setConnectionState(state && !readonly, this.clientId);
1849
+ }
1850
+ }
1828
1851
  }