@fluidframework/container-loader 2.0.0-internal.5.1.1 → 2.0.0-internal.5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/catchUpMonitor.d.ts +1 -1
  3. package/dist/catchUpMonitor.d.ts.map +1 -1
  4. package/dist/catchUpMonitor.js.map +1 -1
  5. package/dist/connectionManager.d.ts.map +1 -1
  6. package/dist/connectionManager.js.map +1 -1
  7. package/dist/connectionStateHandler.d.ts +3 -0
  8. package/dist/connectionStateHandler.d.ts.map +1 -1
  9. package/dist/connectionStateHandler.js +10 -8
  10. package/dist/connectionStateHandler.js.map +1 -1
  11. package/dist/container.d.ts +20 -27
  12. package/dist/container.d.ts.map +1 -1
  13. package/dist/container.js +164 -104
  14. package/dist/container.js.map +1 -1
  15. package/dist/containerContext.d.ts +22 -58
  16. package/dist/containerContext.d.ts.map +1 -1
  17. package/dist/containerContext.js +27 -200
  18. package/dist/containerContext.js.map +1 -1
  19. package/dist/containerStorageAdapter.d.ts +1 -1
  20. package/dist/containerStorageAdapter.d.ts.map +1 -1
  21. package/dist/containerStorageAdapter.js.map +1 -1
  22. package/dist/noopHeuristic.d.ts +23 -0
  23. package/dist/noopHeuristic.d.ts.map +1 -0
  24. package/dist/{collabWindowTracker.js → noopHeuristic.js} +30 -42
  25. package/dist/noopHeuristic.js.map +1 -0
  26. package/dist/packageVersion.d.ts +1 -1
  27. package/dist/packageVersion.js +1 -1
  28. package/dist/packageVersion.js.map +1 -1
  29. package/dist/protocol.d.ts +1 -12
  30. package/dist/protocol.d.ts.map +1 -1
  31. package/dist/protocol.js +0 -18
  32. package/dist/protocol.js.map +1 -1
  33. package/dist/protocolTreeDocumentStorageService.d.ts +1 -1
  34. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  35. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  36. package/dist/retriableDocumentStorageService.d.ts +1 -1
  37. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  38. package/dist/retriableDocumentStorageService.js.map +1 -1
  39. package/lib/catchUpMonitor.d.ts +1 -1
  40. package/lib/catchUpMonitor.d.ts.map +1 -1
  41. package/lib/catchUpMonitor.js.map +1 -1
  42. package/lib/connectionManager.d.ts.map +1 -1
  43. package/lib/connectionManager.js.map +1 -1
  44. package/lib/connectionStateHandler.d.ts +3 -0
  45. package/lib/connectionStateHandler.d.ts.map +1 -1
  46. package/lib/connectionStateHandler.js +10 -8
  47. package/lib/connectionStateHandler.js.map +1 -1
  48. package/lib/container.d.ts +20 -27
  49. package/lib/container.d.ts.map +1 -1
  50. package/lib/container.js +166 -106
  51. package/lib/container.js.map +1 -1
  52. package/lib/containerContext.d.ts +22 -58
  53. package/lib/containerContext.d.ts.map +1 -1
  54. package/lib/containerContext.js +27 -200
  55. package/lib/containerContext.js.map +1 -1
  56. package/lib/containerStorageAdapter.d.ts +1 -1
  57. package/lib/containerStorageAdapter.d.ts.map +1 -1
  58. package/lib/containerStorageAdapter.js.map +1 -1
  59. package/lib/noopHeuristic.d.ts +23 -0
  60. package/lib/noopHeuristic.d.ts.map +1 -0
  61. package/lib/{collabWindowTracker.js → noopHeuristic.js} +30 -42
  62. package/lib/noopHeuristic.js.map +1 -0
  63. package/lib/packageVersion.d.ts +1 -1
  64. package/lib/packageVersion.js +1 -1
  65. package/lib/packageVersion.js.map +1 -1
  66. package/lib/protocol.d.ts +1 -12
  67. package/lib/protocol.d.ts.map +1 -1
  68. package/lib/protocol.js +0 -18
  69. package/lib/protocol.js.map +1 -1
  70. package/lib/protocolTreeDocumentStorageService.d.ts +1 -1
  71. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  72. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  73. package/lib/retriableDocumentStorageService.d.ts +1 -1
  74. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  75. package/lib/retriableDocumentStorageService.js.map +1 -1
  76. package/package.json +11 -11
  77. package/src/catchUpMonitor.ts +1 -1
  78. package/src/connectionManager.ts +2 -1
  79. package/src/connectionStateHandler.ts +14 -9
  80. package/src/container.ts +247 -126
  81. package/src/containerContext.ts +32 -318
  82. package/src/containerStorageAdapter.ts +1 -1
  83. package/src/{collabWindowTracker.ts → noopHeuristic.ts} +37 -47
  84. package/src/packageVersion.ts +1 -1
  85. package/src/protocol.ts +0 -39
  86. package/src/protocolTreeDocumentStorageService.ts +1 -1
  87. package/src/retriableDocumentStorageService.ts +2 -1
  88. package/dist/collabWindowTracker.d.ts +0 -19
  89. package/dist/collabWindowTracker.d.ts.map +0 -1
  90. package/dist/collabWindowTracker.js.map +0 -1
  91. package/lib/collabWindowTracker.d.ts +0 -19
  92. package/lib/collabWindowTracker.d.ts.map +0 -1
  93. package/lib/collabWindowTracker.js.map +0 -1
package/lib/container.js CHANGED
@@ -5,10 +5,10 @@
5
5
  // eslint-disable-next-line import/no-internal-modules
6
6
  import merge from "lodash/merge";
7
7
  import { v4 as uuid } from "uuid";
8
- import { assert, performance, unreachableCase } from "@fluidframework/common-utils";
8
+ import { TypedEventEmitter, assert, performance, unreachableCase, } from "@fluidframework/common-utils";
9
9
  import { AttachState, isFluidCodeDetails, } from "@fluidframework/container-definitions";
10
10
  import { GenericError, UsageError } from "@fluidframework/container-utils";
11
- import { readAndParse, OnlineStatus, isOnline, combineAppAndProtocolSummary, runWithRetry, isCombinedAppAndProtocolSummary, } from "@fluidframework/driver-utils";
11
+ import { readAndParse, OnlineStatus, isOnline, combineAppAndProtocolSummary, runWithRetry, isCombinedAppAndProtocolSummary, MessageType2, canBeCoalescedByService, } from "@fluidframework/driver-utils";
12
12
  import { MessageType, SummaryType, } from "@fluidframework/protocol-definitions";
13
13
  import { ChildLogger, EventEmitterWithErrorHandling, PerformanceEvent, raiseConnectedEvent, TelemetryLogger, connectedEventName, normalizeError, loggerToMonitoringContext, wrapError, } from "@fluidframework/telemetry-utils";
14
14
  import { Audience } from "./audience";
@@ -22,13 +22,14 @@ import { ContainerStorageAdapter, getBlobContentsFromTree, getBlobContentsFromTr
22
22
  import { createConnectionStateHandler } from "./connectionStateHandler";
23
23
  import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
24
24
  import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy, } from "./quorum";
25
- import { CollabWindowTracker } from "./collabWindowTracker";
25
+ import { NoopHeuristic } from "./noopHeuristic";
26
26
  import { ConnectionManager } from "./connectionManager";
27
27
  import { ConnectionState } from "./connectionState";
28
28
  import { OnlyValidTermValue, ProtocolHandler, } from "./protocol";
29
29
  const detachedContainerRefSeqNumber = 0;
30
30
  const dirtyContainerEvent = "dirty";
31
31
  const savedContainerEvent = "saved";
32
+ const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
32
33
  /**
33
34
  * Waits until container connects to delta storage and gets up-to-date.
34
35
  *
@@ -156,8 +157,23 @@ export class Container extends EventEmitterWithErrorHandling {
156
157
  this.attachStarted = false;
157
158
  this._dirtyContainer = false;
158
159
  this.savedOps = [];
160
+ this.clientsWhoShouldHaveLeft = new Set();
159
161
  this.setAutoReconnectTime = performance.now();
162
+ this._lifecycleEvents = new TypedEventEmitter();
160
163
  this._disposed = false;
164
+ this.getAbsoluteUrl = async (relativeUrl) => {
165
+ if (this.resolvedUrl === undefined) {
166
+ return undefined;
167
+ }
168
+ return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, getPackageName(this._loadedCodeDetails));
169
+ };
170
+ this.updateDirtyContainerState = (dirty) => {
171
+ if (this._dirtyContainer === dirty) {
172
+ return;
173
+ }
174
+ this._dirtyContainer = dirty;
175
+ this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
176
+ };
161
177
  const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
162
178
  this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
163
179
  const pendingLocalState = loadProps === null || loadProps === void 0 ? void 0 : loadProps.pendingLocalState;
@@ -202,8 +218,8 @@ export class Container extends EventEmitterWithErrorHandling {
202
218
  dmInitialSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.initialSequenceNumber; },
203
219
  dmLastProcessedSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.lastSequenceNumber; },
204
220
  dmLastKnownSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.lastKnownSeqNumber; },
205
- containerLoadedFromVersionId: () => { var _a; return (_a = this.loadedFromVersion) === null || _a === void 0 ? void 0 : _a.id; },
206
- containerLoadedFromVersionDate: () => { var _a; return (_a = this.loadedFromVersion) === null || _a === void 0 ? void 0 : _a.date; },
221
+ containerLoadedFromVersionId: () => { var _a; return (_a = this._loadedFromVersion) === null || _a === void 0 ? void 0 : _a.id; },
222
+ containerLoadedFromVersionDate: () => { var _a; return (_a = this._loadedFromVersion) === null || _a === void 0 ? void 0 : _a.date; },
207
223
  // message information to associate errors with the specific execution state
208
224
  // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
209
225
  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; },
@@ -254,6 +270,9 @@ export class Container extends EventEmitterWithErrorHandling {
254
270
  this.connect();
255
271
  }
256
272
  },
273
+ clientShouldHaveLeft: (clientId) => {
274
+ this.clientsWhoShouldHaveLeft.add(clientId);
275
+ },
257
276
  }, this.deltaManager, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId);
258
277
  this.on(savedContainerEvent, () => {
259
278
  this.connectionStateHandler.containerSaved();
@@ -296,6 +315,7 @@ export class Container extends EventEmitterWithErrorHandling {
296
315
  static async load(loadProps, createProps) {
297
316
  const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
298
317
  const container = new Container(createProps, loadProps);
318
+ const disableRecordHeapSize = container.mc.config.getBoolean("Fluid.Loader.DisableRecordHeapSize");
299
319
  return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
300
320
  const defaultMode = { opsBeforeReturn: "cached" };
301
321
  // if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
@@ -323,7 +343,7 @@ export class Container extends EventEmitterWithErrorHandling {
323
343
  container.close(err);
324
344
  onClosed(err);
325
345
  });
326
- }), { start: true, end: true, cancel: "generic" });
346
+ }), { start: true, end: true, cancel: "generic" }, disableRecordHeapSize !== true /* recordHeapSize */);
327
347
  }
328
348
  /**
329
349
  * Create a new container in a detached state.
@@ -362,14 +382,11 @@ export class Container extends EventEmitterWithErrorHandling {
362
382
  this._lifecycleState === "disposing" ||
363
383
  this._lifecycleState === "disposed");
364
384
  }
365
- get storage() {
366
- return this.storageAdapter;
367
- }
368
- get context() {
369
- if (this._context === undefined) {
370
- throw new GenericError("Attempted to access context before it was defined");
385
+ get runtime() {
386
+ if (this._runtime === undefined) {
387
+ throw new Error("Attempted to access runtime before it was defined");
371
388
  }
372
- return this._context;
389
+ return this._runtime;
373
390
  }
374
391
  get protocolHandler() {
375
392
  if (this._protocolHandler === undefined) {
@@ -398,15 +415,9 @@ export class Container extends EventEmitterWithErrorHandling {
398
415
  */
399
416
  return (_a = this.service) === null || _a === void 0 ? void 0 : _a.resolvedUrl;
400
417
  }
401
- get loadedFromVersion() {
402
- return this._loadedFromVersion;
403
- }
404
418
  get readOnlyInfo() {
405
419
  return this._deltaManager.readOnlyInfo;
406
420
  }
407
- get closeSignal() {
408
- return this._deltaManager.closeAbortController.signal;
409
- }
410
421
  /**
411
422
  * Tracks host requiring read-only mode.
412
423
  */
@@ -422,13 +433,6 @@ export class Container extends EventEmitterWithErrorHandling {
422
433
  get connected() {
423
434
  return this.connectionStateHandler.connectionState === ConnectionState.Connected;
424
435
  }
425
- /**
426
- * Service configuration details. If running in offline mode will be undefined otherwise will contain service
427
- * configuration details returned as part of the initial connection.
428
- */
429
- get serviceConfiguration() {
430
- return this._deltaManager.serviceConfiguration;
431
- }
432
436
  /**
433
437
  * The server provided id of the client.
434
438
  * Set once this.connected is true, otherwise undefined
@@ -436,21 +440,11 @@ export class Container extends EventEmitterWithErrorHandling {
436
440
  get clientId() {
437
441
  return this._clientId;
438
442
  }
439
- /**
440
- * The server provided claims of the client.
441
- * Set once this.connected is true, otherwise undefined
442
- */
443
- get scopes() {
444
- return this._deltaManager.connectionManager.scopes;
445
- }
446
- get clientDetails() {
447
- return this._deltaManager.clientDetails;
448
- }
449
443
  get offlineLoadEnabled() {
450
444
  var _a, _b;
451
445
  const enabled = (_a = this.mc.config.getBoolean("Fluid.Container.enableOfflineLoad")) !== null && _a !== void 0 ? _a : ((_b = this.options) === null || _b === void 0 ? void 0 : _b.enableOfflineLoad) === true;
452
446
  // summarizer will not have any pending state we want to save
453
- return enabled && this.clientDetails.capabilities.interactive;
447
+ return enabled && this.deltaManager.clientDetails.capabilities.interactive;
454
448
  }
455
449
  /**
456
450
  * Get the code details that are currently specified for the container.
@@ -465,8 +459,7 @@ export class Container extends EventEmitterWithErrorHandling {
465
459
  * loaded.
466
460
  */
467
461
  getLoadedCodeDetails() {
468
- var _a;
469
- return (_a = this._context) === null || _a === void 0 ? void 0 : _a.codeDetails;
462
+ return this._loadedCodeDetails;
470
463
  }
471
464
  /**
472
465
  * Retrieves the audience associated with the document
@@ -487,32 +480,26 @@ export class Container extends EventEmitterWithErrorHandling {
487
480
  */
488
481
  async getEntryPoint() {
489
482
  var _a, _b;
490
- // Only the disposing/disposed lifecycle states should prevent access to the entryPoint; closing/closed should still
491
- // allow it since they mean a kind of read-only state for the Container.
492
- // Note that all 4 are lifecycle states but only 'closed' and 'disposed' are emitted as events.
493
- if (this._lifecycleState === "disposing" || this._lifecycleState === "disposed") {
494
- throw new UsageError("The container is disposing or disposed");
483
+ if (this._disposed) {
484
+ throw new UsageError("The context is already disposed");
495
485
  }
496
- while (this._context === undefined) {
497
- await new Promise((resolve, reject) => {
498
- const contextChangedHandler = () => {
499
- resolve();
500
- this.off("disposed", disposedHandler);
501
- };
502
- const disposedHandler = (error) => {
503
- reject(error !== null && error !== void 0 ? error : "The Container is disposed");
504
- this.off("contextChanged", contextChangedHandler);
505
- };
506
- this.once("contextChanged", contextChangedHandler);
507
- this.once("disposed", disposedHandler);
508
- });
509
- // The Promise above should only resolve (vs reject) if the 'contextChanged' event was emitted and that
510
- // should have set this._context; making sure.
511
- assert(this._context !== undefined, 0x5a2 /* Context still not defined after contextChanged event */);
486
+ if (this._runtime !== undefined) {
487
+ return (_b = (_a = this._runtime).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a);
512
488
  }
513
- // Disable lint rule for the sake of more complete stack traces
514
- // eslint-disable-next-line no-return-await
515
- return await ((_b = (_a = this._context).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
489
+ return new Promise((resolve, reject) => {
490
+ const runtimeInstantiatedHandler = () => {
491
+ var _a, _b;
492
+ assert(this._runtime !== undefined, 0x5a3 /* runtimeInstantiated fired but runtime is still undefined */);
493
+ resolve((_b = (_a = this._runtime).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
494
+ this._lifecycleEvents.off("disposed", disposedHandler);
495
+ };
496
+ const disposedHandler = () => {
497
+ reject(new Error("ContainerContext was disposed"));
498
+ this._lifecycleEvents.off("runtimeInstantiated", runtimeInstantiatedHandler);
499
+ };
500
+ this._lifecycleEvents.once("runtimeInstantiated", runtimeInstantiatedHandler);
501
+ this._lifecycleEvents.once("disposed", disposedHandler);
502
+ });
516
503
  }
517
504
  /**
518
505
  * Retrieves the quorum associated with the document
@@ -579,7 +566,8 @@ export class Container extends EventEmitterWithErrorHandling {
579
566
  // This gives us a chance to know what errors happened on open vs. on fully loaded container.
580
567
  this.mc.logger.sendTelemetryEvent({
581
568
  eventName: "ContainerDispose",
582
- category: "generic",
569
+ // Only log error if container isn't closed
570
+ category: !this.closed && error !== undefined ? "error" : "generic",
583
571
  }, error);
584
572
  // ! Progressing from "closed" to "disposing" is not allowed
585
573
  if (this._lifecycleState !== "closed") {
@@ -587,7 +575,8 @@ export class Container extends EventEmitterWithErrorHandling {
587
575
  }
588
576
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
589
577
  this.connectionStateHandler.dispose();
590
- (_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
578
+ const maybeError = error !== undefined ? new Error(error.message) : undefined;
579
+ (_b = this._runtime) === null || _b === void 0 ? void 0 : _b.dispose(maybeError);
591
580
  this.storageAdapter.dispose();
592
581
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
593
582
  // about file, like file being overwritten in storage, but client having stale local cache.
@@ -605,6 +594,7 @@ export class Container extends EventEmitterWithErrorHandling {
605
594
  }
606
595
  finally {
607
596
  this._lifecycleState = "disposed";
597
+ this._lifecycleEvents.emit("disposed");
608
598
  }
609
599
  }
610
600
  closeAndGetPendingLocalState() {
@@ -624,7 +614,7 @@ export class Container extends EventEmitterWithErrorHandling {
624
614
  assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
625
615
  assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
626
616
  const pendingState = {
627
- pendingRuntimeState: this.context.getPendingLocalState(),
617
+ pendingRuntimeState: this.runtime.getPendingLocalState(),
628
618
  baseSnapshot: this.baseSnapshot,
629
619
  snapshotBlobs: this.baseSnapshotBlobs,
630
620
  savedOps: this.savedOps,
@@ -640,7 +630,7 @@ export class Container extends EventEmitterWithErrorHandling {
640
630
  }
641
631
  serialize() {
642
632
  assert(this.attachState === AttachState.Detached, 0x0d3 /* "Should only be called in detached container" */);
643
- const appSummary = this.context.createSummary();
633
+ const appSummary = this.runtime.createSummary();
644
634
  const protocolSummary = this.captureProtocolSummary();
645
635
  const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
646
636
  if (this.detachedBlobStorage && this.detachedBlobStorage.size > 0) {
@@ -669,7 +659,7 @@ export class Container extends EventEmitterWithErrorHandling {
669
659
  if (!hasAttachmentBlobs) {
670
660
  // Get the document state post attach - possibly can just call attach but we need to change the
671
661
  // semantics around what the attach means as far as async code goes.
672
- const appSummary = this.context.createSummary();
662
+ const appSummary = this.runtime.createSummary();
673
663
  const protocolSummary = this.captureProtocolSummary();
674
664
  summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
675
665
  // Set the state as attaching as we are starting the process of attaching container.
@@ -677,6 +667,7 @@ export class Container extends EventEmitterWithErrorHandling {
677
667
  // starting to attach the container to storage.
678
668
  // Also, this should only be fired in detached container.
679
669
  this._attachState = AttachState.Attaching;
670
+ this.runtime.setAttachState(AttachState.Attaching);
680
671
  this.emit("attaching");
681
672
  if (this.offlineLoadEnabled) {
682
673
  const snapshot = getSnapshotTreeFromSerializedContainer(summary);
@@ -691,7 +682,7 @@ export class Container extends EventEmitterWithErrorHandling {
691
682
  assert(this.client.details.type !== summarizerClientType &&
692
683
  createNewResolvedUrl !== undefined, 0x2c4 /* "client should not be summarizer before container is created" */);
693
684
  this.service = await runWithRetry(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger, false), "containerAttach", this.mc.logger, {
694
- cancel: this.closeSignal,
685
+ cancel: this._deltaManager.closeAbortController.signal,
695
686
  });
696
687
  }
697
688
  await this.storageAdapter.connectToService(this.service);
@@ -713,10 +704,11 @@ export class Container extends EventEmitterWithErrorHandling {
713
704
  }
714
705
  }
715
706
  // take summary and upload
716
- const appSummary = this.context.createSummary(redirectTable);
707
+ const appSummary = this.runtime.createSummary(redirectTable);
717
708
  const protocolSummary = this.captureProtocolSummary();
718
709
  summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
719
710
  this._attachState = AttachState.Attaching;
711
+ this.runtime.setAttachState(AttachState.Attaching);
720
712
  this.emit("attaching");
721
713
  if (this.offlineLoadEnabled) {
722
714
  const snapshot = getSnapshotTreeFromSerializedContainer(summary);
@@ -731,6 +723,7 @@ export class Container extends EventEmitterWithErrorHandling {
731
723
  });
732
724
  }
733
725
  this._attachState = AttachState.Attached;
726
+ this.runtime.setAttachState(AttachState.Attached);
734
727
  this.emit("attached");
735
728
  if (!this.closed) {
736
729
  this.resumeInternal({
@@ -749,7 +742,7 @@ export class Container extends EventEmitterWithErrorHandling {
749
742
  }, { start: true, end: true, cancel: "generic" });
750
743
  }
751
744
  async request(path) {
752
- return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.context.request(path), { end: true, cancel: "error" });
745
+ return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.runtime.request(path), { end: true, cancel: "error" });
753
746
  }
754
747
  setAutoReconnectInternal(mode) {
755
748
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
@@ -815,13 +808,6 @@ export class Container extends EventEmitterWithErrorHandling {
815
808
  // Ensure connection to web socket
816
809
  this.connectToDeltaStream(args);
817
810
  }
818
- async getAbsoluteUrl(relativeUrl) {
819
- var _a;
820
- if (this.resolvedUrl === undefined) {
821
- return undefined;
822
- }
823
- return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, getPackageName((_a = this._context) === null || _a === void 0 ? void 0 : _a.codeDetails));
824
- }
825
811
  async proposeCodeDetails(codeDetails) {
826
812
  if (!isFluidCodeDetails(codeDetails)) {
827
813
  throw new Error("Provided codeDetails are not IFluidCodeDetails");
@@ -843,7 +829,7 @@ export class Container extends EventEmitterWithErrorHandling {
843
829
  this.deltaManager.inbound.pause(),
844
830
  this.deltaManager.inboundSignal.pause(),
845
831
  ]);
846
- if ((await this.context.satisfies(codeDetails)) === true) {
832
+ if ((await this.satisfies(codeDetails)) === true) {
847
833
  this.deltaManager.inbound.resume();
848
834
  this.deltaManager.inboundSignal.resume();
849
835
  return;
@@ -852,6 +838,38 @@ export class Container extends EventEmitterWithErrorHandling {
852
838
  const error = new GenericError("Existing context does not satisfy incoming proposal");
853
839
  this.close(error);
854
840
  }
841
+ /**
842
+ * Determines if the currently loaded module satisfies the incoming constraint code details
843
+ */
844
+ async satisfies(constraintCodeDetails) {
845
+ var _a, _b;
846
+ // If we have no module, it can't satisfy anything.
847
+ if (this._loadedModule === undefined) {
848
+ return false;
849
+ }
850
+ const comparers = [];
851
+ const maybeCompareCodeLoader = this.codeLoader;
852
+ if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
853
+ comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
854
+ }
855
+ const maybeCompareExport = (_a = this._loadedModule) === null || _a === void 0 ? void 0 : _a.module.fluidExport;
856
+ if ((maybeCompareExport === null || maybeCompareExport === void 0 ? void 0 : maybeCompareExport.IFluidCodeDetailsComparer) !== undefined) {
857
+ comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
858
+ }
859
+ // If there are no comparers, then it's impossible to know if the currently loaded package satisfies
860
+ // the incoming constraint, so we return false. Assuming it does not satisfy is safer, to force a reload
861
+ // rather than potentially running with incompatible code.
862
+ if (comparers.length === 0) {
863
+ return false;
864
+ }
865
+ for (const comparer of comparers) {
866
+ const satisfies = await comparer.satisfies((_b = this._loadedModule) === null || _b === void 0 ? void 0 : _b.details, constraintCodeDetails);
867
+ if (satisfies === false) {
868
+ return false;
869
+ }
870
+ }
871
+ return true;
872
+ }
855
873
  async getVersion(version) {
856
874
  const versions = await this.storageAdapter.getVersions(version, 1);
857
875
  return versions[0];
@@ -869,7 +887,7 @@ export class Container extends EventEmitterWithErrorHandling {
869
887
  * @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
870
888
  */
871
889
  async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState) {
872
- var _a;
890
+ var _a, _b, _c;
873
891
  this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
874
892
  // Ideally we always connect as "read" by default.
875
893
  // Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
@@ -913,7 +931,7 @@ export class Container extends EventEmitterWithErrorHandling {
913
931
  if (this.offlineLoadEnabled) {
914
932
  this.baseSnapshot = snapshot;
915
933
  // Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
916
- this.baseSnapshotBlobs = await getBlobContentsFromTree(snapshot, this.storage);
934
+ this.baseSnapshotBlobs = await getBlobContentsFromTree(snapshot, this.storageAdapter);
917
935
  }
918
936
  }
919
937
  const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
@@ -949,7 +967,7 @@ export class Container extends EventEmitterWithErrorHandling {
949
967
  for (const message of pendingLocalState.savedOps) {
950
968
  this.processRemoteMessage(message);
951
969
  // allow runtime to apply stashed ops at this op's sequence number
952
- await this.context.notifyOpReplay(message);
970
+ await ((_c = (_b = this.runtime).notifyOpReplay) === null || _c === void 0 ? void 0 : _c.call(_b, message));
953
971
  }
954
972
  pendingLocalState.savedOps = [];
955
973
  // now set clientId to stashed clientId so live ops are correctly processed as local
@@ -1188,7 +1206,7 @@ export class Container extends EventEmitterWithErrorHandling {
1188
1206
  });
1189
1207
  deltaManager.on("disconnect", (reason, error) => {
1190
1208
  var _a;
1191
- (_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
1209
+ (_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyDisconnect();
1192
1210
  if (!this.closed) {
1193
1211
  this.connectionStateHandler.receivedDisconnectEvent(reason, error);
1194
1212
  }
@@ -1332,7 +1350,7 @@ export class Container extends EventEmitterWithErrorHandling {
1332
1350
  return -1;
1333
1351
  }
1334
1352
  this.messageCountAfterDisconnection += 1;
1335
- (_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
1353
+ (_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyMessageSent();
1336
1354
  return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
1337
1355
  }
1338
1356
  processRemoteMessage(message) {
@@ -1340,24 +1358,51 @@ export class Container extends EventEmitterWithErrorHandling {
1340
1358
  this.savedOps.push(message);
1341
1359
  }
1342
1360
  const local = this.clientId === message.clientId;
1361
+ // Check and report if we're getting messages from a clientId that we previously
1362
+ // flagged should have left, or from a client that's not in the quorum but should be
1363
+ if (message.clientId != null) {
1364
+ const client = this.protocolHandler.quorum.getMember(message.clientId);
1365
+ if (client === undefined && message.type !== MessageType.ClientJoin) {
1366
+ // pre-0.58 error message: messageClientIdMissingFromQuorum
1367
+ throw new Error("Remote message's clientId is missing from the quorum");
1368
+ }
1369
+ // Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
1370
+ // It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
1371
+ // document we don't need to blow up aggressively.
1372
+ if (this.clientsWhoShouldHaveLeft.has(message.clientId) &&
1373
+ !canBeCoalescedByService(message)) {
1374
+ // pre-0.58 error message: messageClientIdShouldHaveLeft
1375
+ throw new Error("Remote message's clientId already should have left");
1376
+ }
1377
+ }
1343
1378
  // Allow the protocol handler to process the message
1344
1379
  const result = this.protocolHandler.processMessage(message, local);
1345
1380
  // Forward messages to the loaded runtime for processing
1346
- this.context.process(message, local);
1381
+ this.runtime.process(message, local);
1347
1382
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
1348
1383
  if (this.activeConnection()) {
1349
- if (this.collabWindowTracker === undefined) {
1384
+ if (this.noopHeuristic === undefined) {
1385
+ const serviceConfiguration = this.deltaManager.serviceConfiguration;
1350
1386
  // Note that config from first connection will be used for this container's lifetime.
1351
1387
  // That means that if relay service changes settings, such changes will impact only newly booted
1352
1388
  // clients.
1353
1389
  // All existing will continue to use settings they got earlier.
1354
- assert(this.serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1355
- this.collabWindowTracker = new CollabWindowTracker((type) => {
1356
- assert(this.activeConnection(), 0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
1357
- this.submitMessage(type);
1358
- }, this.serviceConfiguration.noopTimeFrequency, this.serviceConfiguration.noopCountFrequency);
1390
+ assert(serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1391
+ this.noopHeuristic = new NoopHeuristic(serviceConfiguration.noopTimeFrequency, serviceConfiguration.noopCountFrequency);
1392
+ this.noopHeuristic.on("wantsNoop", () => {
1393
+ // On disconnect we notify the heuristic which should prevent it from wanting a noop.
1394
+ // Hitting this assert would imply we lost activeConnection between notifying the heuristic of a processed message and
1395
+ // running the microtask that the heuristic queued in response.
1396
+ assert(this.activeConnection(), 0x241 /* "Trying to send noop without active connection" */);
1397
+ this.submitMessage(MessageType.NoOp);
1398
+ });
1399
+ }
1400
+ this.noopHeuristic.notifyMessageProcessed(message);
1401
+ // The contract with the protocolHandler is that returning "immediateNoOp" is equivalent to "please immediately accept the proposal I just processed".
1402
+ if (result.immediateNoOp === true) {
1403
+ // ADO:1385: Remove cast and use MessageType once definition changes propagate
1404
+ this.submitMessage(MessageType2.Accept);
1359
1405
  }
1360
- this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
1361
1406
  }
1362
1407
  this.emit("op", message);
1363
1408
  }
@@ -1371,7 +1416,7 @@ export class Container extends EventEmitterWithErrorHandling {
1371
1416
  }
1372
1417
  else {
1373
1418
  const local = this.clientId === message.clientId;
1374
- this.context.processSignal(message, local);
1419
+ this.runtime.processSignal(message, local);
1375
1420
  }
1376
1421
  }
1377
1422
  /**
@@ -1404,21 +1449,37 @@ export class Container extends EventEmitterWithErrorHandling {
1404
1449
  await this.instantiateContext(existing, codeDetails, snapshot);
1405
1450
  }
1406
1451
  async instantiateContext(existing, codeDetails, snapshot, pendingLocalState) {
1407
- var _a;
1408
- assert(((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing context not disposed" */);
1452
+ var _a, _b;
1453
+ assert(((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing runtime not disposed" */);
1409
1454
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1410
1455
  // are set. Global requests will still go directly to the loader
1411
1456
  const maybeLoader = this.scope;
1412
1457
  const loader = new RelativeLoader(this, maybeLoader.ILoader);
1413
- 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, referenceSequenceNumber) => this.submitSummaryMessage(summaryOp, referenceSequenceNumber), (batch, referenceSequenceNumber) => this.submitBatch(batch, referenceSequenceNumber), (message) => this.submitSignal(message), (error) => this.dispose(error), (error) => this.close(error), Container.version, (dirty) => this.updateDirtyContainerState(dirty), existing, pendingLocalState);
1414
- this.emit("contextChanged", codeDetails);
1415
- }
1416
- updateDirtyContainerState(dirty) {
1417
- if (this._dirtyContainer === dirty) {
1418
- return;
1458
+ const loadCodeResult = await PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "CodeLoad" }, async () => this.codeLoader.load(codeDetails));
1459
+ this._loadedModule = {
1460
+ module: loadCodeResult.module,
1461
+ // An older interface ICodeLoader could return an IFluidModule which didn't have details.
1462
+ // If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
1463
+ // TODO: Determine if this is still a realistic scenario or if this fixup could be removed.
1464
+ details: (_b = loadCodeResult.details) !== null && _b !== void 0 ? _b : codeDetails,
1465
+ };
1466
+ const fluidExport = this._loadedModule.module.fluidExport;
1467
+ const runtimeFactory = fluidExport === null || fluidExport === void 0 ? void 0 : fluidExport.IRuntimeFactory;
1468
+ if (runtimeFactory === undefined) {
1469
+ throw new Error(packageNotFactoryError);
1419
1470
  }
1420
- this._dirtyContainer = dirty;
1421
- this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
1471
+ const deltaManagerProxy = new DeltaManagerProxy(this._deltaManager);
1472
+ const quorumProxy = new QuorumProxy(this.protocolHandler.quorum);
1473
+ const context = new ContainerContext(this.options, this.scope, snapshot, this._loadedFromVersion, deltaManagerProxy, this.storageAdapter, quorumProxy, this.protocolHandler.audience, loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp, referenceSequenceNumber) => this.submitSummaryMessage(summaryOp, referenceSequenceNumber), (batch, referenceSequenceNumber) => this.submitBatch(batch, referenceSequenceNumber), (message) => this.submitSignal(message), (error) => this.dispose(error), (error) => this.close(error), this.updateDirtyContainerState, this.getAbsoluteUrl, () => { var _a; return (_a = this.resolvedUrl) === null || _a === void 0 ? void 0 : _a.id; }, () => this.clientId, () => deltaManagerProxy.serviceConfiguration, () => this.attachState, () => this.connected, this._deltaManager.clientDetails, existing, this.subLogger, pendingLocalState);
1474
+ this._lifecycleEvents.once("disposed", () => {
1475
+ context.dispose();
1476
+ quorumProxy.dispose();
1477
+ deltaManagerProxy.dispose();
1478
+ });
1479
+ this._runtime = await PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "InstantiateRuntime" }, async () => runtimeFactory.instantiateRuntime(context, existing));
1480
+ this._lifecycleEvents.emit("runtimeInstantiated");
1481
+ this._loadedCodeDetails = codeDetails;
1482
+ this.emit("contextChanged", codeDetails);
1422
1483
  }
1423
1484
  /**
1424
1485
  * Set the connected state of the ContainerContext
@@ -1428,16 +1489,15 @@ export class Container extends EventEmitterWithErrorHandling {
1428
1489
  */
1429
1490
  setContextConnectedState(state, readonly) {
1430
1491
  var _a;
1431
- if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1492
+ if (((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1432
1493
  /**
1433
1494
  * We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
1434
1495
  * ops getting through to the DeltaManager.
1435
1496
  * The ContainerRuntime's "connected" state simply means it is ok to send ops
1436
1497
  * See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
1437
1498
  */
1438
- this.context.setConnectionState(state && !readonly, this.clientId);
1499
+ this.runtime.setConnectionState(state && !readonly, this.clientId);
1439
1500
  }
1440
1501
  }
1441
1502
  }
1442
- Container.version = "^0.1.0";
1443
1503
  //# sourceMappingURL=container.js.map