@fluidframework/container-loader 2.0.0-internal.5.1.0 → 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/dist/container.js CHANGED
@@ -28,13 +28,14 @@ const containerStorageAdapter_1 = require("./containerStorageAdapter");
28
28
  const connectionStateHandler_1 = require("./connectionStateHandler");
29
29
  const utils_1 = require("./utils");
30
30
  const quorum_1 = require("./quorum");
31
- const collabWindowTracker_1 = require("./collabWindowTracker");
31
+ const noopHeuristic_1 = require("./noopHeuristic");
32
32
  const connectionManager_1 = require("./connectionManager");
33
33
  const connectionState_1 = require("./connectionState");
34
34
  const protocol_1 = require("./protocol");
35
35
  const detachedContainerRefSeqNumber = 0;
36
36
  const dirtyContainerEvent = "dirty";
37
37
  const savedContainerEvent = "saved";
38
+ const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
38
39
  /**
39
40
  * Waits until container connects to delta storage and gets up-to-date.
40
41
  *
@@ -164,8 +165,23 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
164
165
  this.attachStarted = false;
165
166
  this._dirtyContainer = false;
166
167
  this.savedOps = [];
168
+ this.clientsWhoShouldHaveLeft = new Set();
167
169
  this.setAutoReconnectTime = common_utils_1.performance.now();
170
+ this._lifecycleEvents = new common_utils_1.TypedEventEmitter();
168
171
  this._disposed = false;
172
+ this.getAbsoluteUrl = async (relativeUrl) => {
173
+ if (this.resolvedUrl === undefined) {
174
+ return undefined;
175
+ }
176
+ return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, (0, contracts_1.getPackageName)(this._loadedCodeDetails));
177
+ };
178
+ this.updateDirtyContainerState = (dirty) => {
179
+ if (this._dirtyContainer === dirty) {
180
+ return;
181
+ }
182
+ this._dirtyContainer = dirty;
183
+ this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
184
+ };
169
185
  const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
170
186
  this.connectionTransitionTimes[connectionState_1.ConnectionState.Disconnected] = common_utils_1.performance.now();
171
187
  const pendingLocalState = loadProps === null || loadProps === void 0 ? void 0 : loadProps.pendingLocalState;
@@ -210,8 +226,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
210
226
  dmInitialSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.initialSequenceNumber; },
211
227
  dmLastProcessedSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.lastSequenceNumber; },
212
228
  dmLastKnownSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.lastKnownSeqNumber; },
213
- containerLoadedFromVersionId: () => { var _a; return (_a = this.loadedFromVersion) === null || _a === void 0 ? void 0 : _a.id; },
214
- containerLoadedFromVersionDate: () => { var _a; return (_a = this.loadedFromVersion) === null || _a === void 0 ? void 0 : _a.date; },
229
+ containerLoadedFromVersionId: () => { var _a; return (_a = this._loadedFromVersion) === null || _a === void 0 ? void 0 : _a.id; },
230
+ containerLoadedFromVersionDate: () => { var _a; return (_a = this._loadedFromVersion) === null || _a === void 0 ? void 0 : _a.date; },
215
231
  // message information to associate errors with the specific execution state
216
232
  // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
217
233
  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; },
@@ -262,6 +278,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
262
278
  this.connect();
263
279
  }
264
280
  },
281
+ clientShouldHaveLeft: (clientId) => {
282
+ this.clientsWhoShouldHaveLeft.add(clientId);
283
+ },
265
284
  }, this.deltaManager, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId);
266
285
  this.on(savedContainerEvent, () => {
267
286
  this.connectionStateHandler.containerSaved();
@@ -304,6 +323,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
304
323
  static async load(loadProps, createProps) {
305
324
  const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
306
325
  const container = new Container(createProps, loadProps);
326
+ const disableRecordHeapSize = container.mc.config.getBoolean("Fluid.Loader.DisableRecordHeapSize");
307
327
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
308
328
  const defaultMode = { opsBeforeReturn: "cached" };
309
329
  // if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
@@ -331,7 +351,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
331
351
  container.close(err);
332
352
  onClosed(err);
333
353
  });
334
- }), { start: true, end: true, cancel: "generic" });
354
+ }), { start: true, end: true, cancel: "generic" }, disableRecordHeapSize !== true /* recordHeapSize */);
335
355
  }
336
356
  /**
337
357
  * Create a new container in a detached state.
@@ -370,14 +390,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
370
390
  this._lifecycleState === "disposing" ||
371
391
  this._lifecycleState === "disposed");
372
392
  }
373
- get storage() {
374
- return this.storageAdapter;
375
- }
376
- get context() {
377
- if (this._context === undefined) {
378
- throw new container_utils_1.GenericError("Attempted to access context before it was defined");
393
+ get runtime() {
394
+ if (this._runtime === undefined) {
395
+ throw new Error("Attempted to access runtime before it was defined");
379
396
  }
380
- return this._context;
397
+ return this._runtime;
381
398
  }
382
399
  get protocolHandler() {
383
400
  if (this._protocolHandler === undefined) {
@@ -406,15 +423,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
406
423
  */
407
424
  return (_a = this.service) === null || _a === void 0 ? void 0 : _a.resolvedUrl;
408
425
  }
409
- get loadedFromVersion() {
410
- return this._loadedFromVersion;
411
- }
412
426
  get readOnlyInfo() {
413
427
  return this._deltaManager.readOnlyInfo;
414
428
  }
415
- get closeSignal() {
416
- return this._deltaManager.closeAbortController.signal;
417
- }
418
429
  /**
419
430
  * Tracks host requiring read-only mode.
420
431
  */
@@ -430,13 +441,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
430
441
  get connected() {
431
442
  return this.connectionStateHandler.connectionState === connectionState_1.ConnectionState.Connected;
432
443
  }
433
- /**
434
- * Service configuration details. If running in offline mode will be undefined otherwise will contain service
435
- * configuration details returned as part of the initial connection.
436
- */
437
- get serviceConfiguration() {
438
- return this._deltaManager.serviceConfiguration;
439
- }
440
444
  /**
441
445
  * The server provided id of the client.
442
446
  * Set once this.connected is true, otherwise undefined
@@ -444,21 +448,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
444
448
  get clientId() {
445
449
  return this._clientId;
446
450
  }
447
- /**
448
- * The server provided claims of the client.
449
- * Set once this.connected is true, otherwise undefined
450
- */
451
- get scopes() {
452
- return this._deltaManager.connectionManager.scopes;
453
- }
454
- get clientDetails() {
455
- return this._deltaManager.clientDetails;
456
- }
457
451
  get offlineLoadEnabled() {
458
452
  var _a, _b;
459
453
  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;
460
454
  // summarizer will not have any pending state we want to save
461
- return enabled && this.clientDetails.capabilities.interactive;
455
+ return enabled && this.deltaManager.clientDetails.capabilities.interactive;
462
456
  }
463
457
  /**
464
458
  * Get the code details that are currently specified for the container.
@@ -473,8 +467,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
473
467
  * loaded.
474
468
  */
475
469
  getLoadedCodeDetails() {
476
- var _a;
477
- return (_a = this._context) === null || _a === void 0 ? void 0 : _a.codeDetails;
470
+ return this._loadedCodeDetails;
478
471
  }
479
472
  /**
480
473
  * Retrieves the audience associated with the document
@@ -495,32 +488,26 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
495
488
  */
496
489
  async getEntryPoint() {
497
490
  var _a, _b;
498
- // Only the disposing/disposed lifecycle states should prevent access to the entryPoint; closing/closed should still
499
- // allow it since they mean a kind of read-only state for the Container.
500
- // Note that all 4 are lifecycle states but only 'closed' and 'disposed' are emitted as events.
501
- if (this._lifecycleState === "disposing" || this._lifecycleState === "disposed") {
502
- throw new container_utils_1.UsageError("The container is disposing or disposed");
491
+ if (this._disposed) {
492
+ throw new container_utils_1.UsageError("The context is already disposed");
503
493
  }
504
- while (this._context === undefined) {
505
- await new Promise((resolve, reject) => {
506
- const contextChangedHandler = () => {
507
- resolve();
508
- this.off("disposed", disposedHandler);
509
- };
510
- const disposedHandler = (error) => {
511
- reject(error !== null && error !== void 0 ? error : "The Container is disposed");
512
- this.off("contextChanged", contextChangedHandler);
513
- };
514
- this.once("contextChanged", contextChangedHandler);
515
- this.once("disposed", disposedHandler);
516
- });
517
- // The Promise above should only resolve (vs reject) if the 'contextChanged' event was emitted and that
518
- // should have set this._context; making sure.
519
- (0, common_utils_1.assert)(this._context !== undefined, 0x5a2 /* Context still not defined after contextChanged event */);
494
+ if (this._runtime !== undefined) {
495
+ return (_b = (_a = this._runtime).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a);
520
496
  }
521
- // Disable lint rule for the sake of more complete stack traces
522
- // eslint-disable-next-line no-return-await
523
- return await ((_b = (_a = this._context).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
497
+ return new Promise((resolve, reject) => {
498
+ const runtimeInstantiatedHandler = () => {
499
+ var _a, _b;
500
+ (0, common_utils_1.assert)(this._runtime !== undefined, 0x5a3 /* runtimeInstantiated fired but runtime is still undefined */);
501
+ resolve((_b = (_a = this._runtime).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
502
+ this._lifecycleEvents.off("disposed", disposedHandler);
503
+ };
504
+ const disposedHandler = () => {
505
+ reject(new Error("ContainerContext was disposed"));
506
+ this._lifecycleEvents.off("runtimeInstantiated", runtimeInstantiatedHandler);
507
+ };
508
+ this._lifecycleEvents.once("runtimeInstantiated", runtimeInstantiatedHandler);
509
+ this._lifecycleEvents.once("disposed", disposedHandler);
510
+ });
524
511
  }
525
512
  /**
526
513
  * Retrieves the quorum associated with the document
@@ -587,7 +574,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
587
574
  // This gives us a chance to know what errors happened on open vs. on fully loaded container.
588
575
  this.mc.logger.sendTelemetryEvent({
589
576
  eventName: "ContainerDispose",
590
- category: "generic",
577
+ // Only log error if container isn't closed
578
+ category: !this.closed && error !== undefined ? "error" : "generic",
591
579
  }, error);
592
580
  // ! Progressing from "closed" to "disposing" is not allowed
593
581
  if (this._lifecycleState !== "closed") {
@@ -595,7 +583,8 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
595
583
  }
596
584
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
597
585
  this.connectionStateHandler.dispose();
598
- (_b = this._context) === null || _b === void 0 ? void 0 : _b.dispose(error !== undefined ? new Error(error.message) : undefined);
586
+ const maybeError = error !== undefined ? new Error(error.message) : undefined;
587
+ (_b = this._runtime) === null || _b === void 0 ? void 0 : _b.dispose(maybeError);
599
588
  this.storageAdapter.dispose();
600
589
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
601
590
  // about file, like file being overwritten in storage, but client having stale local cache.
@@ -613,6 +602,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
613
602
  }
614
603
  finally {
615
604
  this._lifecycleState = "disposed";
605
+ this._lifecycleEvents.emit("disposed");
616
606
  }
617
607
  }
618
608
  closeAndGetPendingLocalState() {
@@ -632,7 +622,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
632
622
  (0, common_utils_1.assert)(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
633
623
  (0, common_utils_1.assert)(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
634
624
  const pendingState = {
635
- pendingRuntimeState: this.context.getPendingLocalState(),
625
+ pendingRuntimeState: this.runtime.getPendingLocalState(),
636
626
  baseSnapshot: this.baseSnapshot,
637
627
  snapshotBlobs: this.baseSnapshotBlobs,
638
628
  savedOps: this.savedOps,
@@ -648,7 +638,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
648
638
  }
649
639
  serialize() {
650
640
  (0, common_utils_1.assert)(this.attachState === container_definitions_1.AttachState.Detached, 0x0d3 /* "Should only be called in detached container" */);
651
- const appSummary = this.context.createSummary();
641
+ const appSummary = this.runtime.createSummary();
652
642
  const protocolSummary = this.captureProtocolSummary();
653
643
  const combinedSummary = (0, driver_utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
654
644
  if (this.detachedBlobStorage && this.detachedBlobStorage.size > 0) {
@@ -677,7 +667,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
677
667
  if (!hasAttachmentBlobs) {
678
668
  // Get the document state post attach - possibly can just call attach but we need to change the
679
669
  // semantics around what the attach means as far as async code goes.
680
- const appSummary = this.context.createSummary();
670
+ const appSummary = this.runtime.createSummary();
681
671
  const protocolSummary = this.captureProtocolSummary();
682
672
  summary = (0, driver_utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
683
673
  // Set the state as attaching as we are starting the process of attaching container.
@@ -685,6 +675,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
685
675
  // starting to attach the container to storage.
686
676
  // Also, this should only be fired in detached container.
687
677
  this._attachState = container_definitions_1.AttachState.Attaching;
678
+ this.runtime.setAttachState(container_definitions_1.AttachState.Attaching);
688
679
  this.emit("attaching");
689
680
  if (this.offlineLoadEnabled) {
690
681
  const snapshot = (0, utils_1.getSnapshotTreeFromSerializedContainer)(summary);
@@ -699,7 +690,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
699
690
  (0, common_utils_1.assert)(this.client.details.type !== summarizerClientType &&
700
691
  createNewResolvedUrl !== undefined, 0x2c4 /* "client should not be summarizer before container is created" */);
701
692
  this.service = await (0, driver_utils_1.runWithRetry)(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger, false), "containerAttach", this.mc.logger, {
702
- cancel: this.closeSignal,
693
+ cancel: this._deltaManager.closeAbortController.signal,
703
694
  });
704
695
  }
705
696
  await this.storageAdapter.connectToService(this.service);
@@ -721,10 +712,11 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
721
712
  }
722
713
  }
723
714
  // take summary and upload
724
- const appSummary = this.context.createSummary(redirectTable);
715
+ const appSummary = this.runtime.createSummary(redirectTable);
725
716
  const protocolSummary = this.captureProtocolSummary();
726
717
  summary = (0, driver_utils_1.combineAppAndProtocolSummary)(appSummary, protocolSummary);
727
718
  this._attachState = container_definitions_1.AttachState.Attaching;
719
+ this.runtime.setAttachState(container_definitions_1.AttachState.Attaching);
728
720
  this.emit("attaching");
729
721
  if (this.offlineLoadEnabled) {
730
722
  const snapshot = (0, utils_1.getSnapshotTreeFromSerializedContainer)(summary);
@@ -739,6 +731,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
739
731
  });
740
732
  }
741
733
  this._attachState = container_definitions_1.AttachState.Attached;
734
+ this.runtime.setAttachState(container_definitions_1.AttachState.Attached);
742
735
  this.emit("attached");
743
736
  if (!this.closed) {
744
737
  this.resumeInternal({
@@ -757,7 +750,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
757
750
  }, { start: true, end: true, cancel: "generic" });
758
751
  }
759
752
  async request(path) {
760
- return telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.context.request(path), { end: true, cancel: "error" });
753
+ return telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.runtime.request(path), { end: true, cancel: "error" });
761
754
  }
762
755
  setAutoReconnectInternal(mode) {
763
756
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
@@ -823,13 +816,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
823
816
  // Ensure connection to web socket
824
817
  this.connectToDeltaStream(args);
825
818
  }
826
- async getAbsoluteUrl(relativeUrl) {
827
- var _a;
828
- if (this.resolvedUrl === undefined) {
829
- return undefined;
830
- }
831
- return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, (0, contracts_1.getPackageName)((_a = this._context) === null || _a === void 0 ? void 0 : _a.codeDetails));
832
- }
833
819
  async proposeCodeDetails(codeDetails) {
834
820
  if (!(0, container_definitions_1.isFluidCodeDetails)(codeDetails)) {
835
821
  throw new Error("Provided codeDetails are not IFluidCodeDetails");
@@ -851,7 +837,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
851
837
  this.deltaManager.inbound.pause(),
852
838
  this.deltaManager.inboundSignal.pause(),
853
839
  ]);
854
- if ((await this.context.satisfies(codeDetails)) === true) {
840
+ if ((await this.satisfies(codeDetails)) === true) {
855
841
  this.deltaManager.inbound.resume();
856
842
  this.deltaManager.inboundSignal.resume();
857
843
  return;
@@ -860,6 +846,38 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
860
846
  const error = new container_utils_1.GenericError("Existing context does not satisfy incoming proposal");
861
847
  this.close(error);
862
848
  }
849
+ /**
850
+ * Determines if the currently loaded module satisfies the incoming constraint code details
851
+ */
852
+ async satisfies(constraintCodeDetails) {
853
+ var _a, _b;
854
+ // If we have no module, it can't satisfy anything.
855
+ if (this._loadedModule === undefined) {
856
+ return false;
857
+ }
858
+ const comparers = [];
859
+ const maybeCompareCodeLoader = this.codeLoader;
860
+ if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
861
+ comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
862
+ }
863
+ const maybeCompareExport = (_a = this._loadedModule) === null || _a === void 0 ? void 0 : _a.module.fluidExport;
864
+ if ((maybeCompareExport === null || maybeCompareExport === void 0 ? void 0 : maybeCompareExport.IFluidCodeDetailsComparer) !== undefined) {
865
+ comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
866
+ }
867
+ // If there are no comparers, then it's impossible to know if the currently loaded package satisfies
868
+ // the incoming constraint, so we return false. Assuming it does not satisfy is safer, to force a reload
869
+ // rather than potentially running with incompatible code.
870
+ if (comparers.length === 0) {
871
+ return false;
872
+ }
873
+ for (const comparer of comparers) {
874
+ const satisfies = await comparer.satisfies((_b = this._loadedModule) === null || _b === void 0 ? void 0 : _b.details, constraintCodeDetails);
875
+ if (satisfies === false) {
876
+ return false;
877
+ }
878
+ }
879
+ return true;
880
+ }
863
881
  async getVersion(version) {
864
882
  const versions = await this.storageAdapter.getVersions(version, 1);
865
883
  return versions[0];
@@ -877,7 +895,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
877
895
  * @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
878
896
  */
879
897
  async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState) {
880
- var _a;
898
+ var _a, _b, _c;
881
899
  this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
882
900
  // Ideally we always connect as "read" by default.
883
901
  // Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
@@ -921,7 +939,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
921
939
  if (this.offlineLoadEnabled) {
922
940
  this.baseSnapshot = snapshot;
923
941
  // Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
924
- this.baseSnapshotBlobs = await (0, containerStorageAdapter_1.getBlobContentsFromTree)(snapshot, this.storage);
942
+ this.baseSnapshotBlobs = await (0, containerStorageAdapter_1.getBlobContentsFromTree)(snapshot, this.storageAdapter);
925
943
  }
926
944
  }
927
945
  const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
@@ -957,7 +975,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
957
975
  for (const message of pendingLocalState.savedOps) {
958
976
  this.processRemoteMessage(message);
959
977
  // allow runtime to apply stashed ops at this op's sequence number
960
- await this.context.notifyOpReplay(message);
978
+ await ((_c = (_b = this.runtime).notifyOpReplay) === null || _c === void 0 ? void 0 : _c.call(_b, message));
961
979
  }
962
980
  pendingLocalState.savedOps = [];
963
981
  // now set clientId to stashed clientId so live ops are correctly processed as local
@@ -1196,7 +1214,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1196
1214
  });
1197
1215
  deltaManager.on("disconnect", (reason, error) => {
1198
1216
  var _a;
1199
- (_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
1217
+ (_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyDisconnect();
1200
1218
  if (!this.closed) {
1201
1219
  this.connectionStateHandler.receivedDisconnectEvent(reason, error);
1202
1220
  }
@@ -1340,7 +1358,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1340
1358
  return -1;
1341
1359
  }
1342
1360
  this.messageCountAfterDisconnection += 1;
1343
- (_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
1361
+ (_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyMessageSent();
1344
1362
  return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
1345
1363
  }
1346
1364
  processRemoteMessage(message) {
@@ -1348,24 +1366,51 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1348
1366
  this.savedOps.push(message);
1349
1367
  }
1350
1368
  const local = this.clientId === message.clientId;
1369
+ // Check and report if we're getting messages from a clientId that we previously
1370
+ // flagged should have left, or from a client that's not in the quorum but should be
1371
+ if (message.clientId != null) {
1372
+ const client = this.protocolHandler.quorum.getMember(message.clientId);
1373
+ if (client === undefined && message.type !== protocol_definitions_1.MessageType.ClientJoin) {
1374
+ // pre-0.58 error message: messageClientIdMissingFromQuorum
1375
+ throw new Error("Remote message's clientId is missing from the quorum");
1376
+ }
1377
+ // Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
1378
+ // It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
1379
+ // document we don't need to blow up aggressively.
1380
+ if (this.clientsWhoShouldHaveLeft.has(message.clientId) &&
1381
+ !(0, driver_utils_1.canBeCoalescedByService)(message)) {
1382
+ // pre-0.58 error message: messageClientIdShouldHaveLeft
1383
+ throw new Error("Remote message's clientId already should have left");
1384
+ }
1385
+ }
1351
1386
  // Allow the protocol handler to process the message
1352
1387
  const result = this.protocolHandler.processMessage(message, local);
1353
1388
  // Forward messages to the loaded runtime for processing
1354
- this.context.process(message, local);
1389
+ this.runtime.process(message, local);
1355
1390
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
1356
1391
  if (this.activeConnection()) {
1357
- if (this.collabWindowTracker === undefined) {
1392
+ if (this.noopHeuristic === undefined) {
1393
+ const serviceConfiguration = this.deltaManager.serviceConfiguration;
1358
1394
  // Note that config from first connection will be used for this container's lifetime.
1359
1395
  // That means that if relay service changes settings, such changes will impact only newly booted
1360
1396
  // clients.
1361
1397
  // All existing will continue to use settings they got earlier.
1362
- (0, common_utils_1.assert)(this.serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1363
- this.collabWindowTracker = new collabWindowTracker_1.CollabWindowTracker((type) => {
1364
- (0, common_utils_1.assert)(this.activeConnection(), 0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
1365
- this.submitMessage(type);
1366
- }, this.serviceConfiguration.noopTimeFrequency, this.serviceConfiguration.noopCountFrequency);
1398
+ (0, common_utils_1.assert)(serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1399
+ this.noopHeuristic = new noopHeuristic_1.NoopHeuristic(serviceConfiguration.noopTimeFrequency, serviceConfiguration.noopCountFrequency);
1400
+ this.noopHeuristic.on("wantsNoop", () => {
1401
+ // On disconnect we notify the heuristic which should prevent it from wanting a noop.
1402
+ // Hitting this assert would imply we lost activeConnection between notifying the heuristic of a processed message and
1403
+ // running the microtask that the heuristic queued in response.
1404
+ (0, common_utils_1.assert)(this.activeConnection(), 0x241 /* "Trying to send noop without active connection" */);
1405
+ this.submitMessage(protocol_definitions_1.MessageType.NoOp);
1406
+ });
1407
+ }
1408
+ this.noopHeuristic.notifyMessageProcessed(message);
1409
+ // The contract with the protocolHandler is that returning "immediateNoOp" is equivalent to "please immediately accept the proposal I just processed".
1410
+ if (result.immediateNoOp === true) {
1411
+ // ADO:1385: Remove cast and use MessageType once definition changes propagate
1412
+ this.submitMessage(driver_utils_1.MessageType2.Accept);
1367
1413
  }
1368
- this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
1369
1414
  }
1370
1415
  this.emit("op", message);
1371
1416
  }
@@ -1379,7 +1424,7 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1379
1424
  }
1380
1425
  else {
1381
1426
  const local = this.clientId === message.clientId;
1382
- this.context.processSignal(message, local);
1427
+ this.runtime.processSignal(message, local);
1383
1428
  }
1384
1429
  }
1385
1430
  /**
@@ -1412,21 +1457,37 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1412
1457
  await this.instantiateContext(existing, codeDetails, snapshot);
1413
1458
  }
1414
1459
  async instantiateContext(existing, codeDetails, snapshot, pendingLocalState) {
1415
- var _a;
1416
- (0, common_utils_1.assert)(((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing context not disposed" */);
1460
+ var _a, _b;
1461
+ (0, common_utils_1.assert)(((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing runtime not disposed" */);
1417
1462
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1418
1463
  // are set. Global requests will still go directly to the loader
1419
1464
  const maybeLoader = this.scope;
1420
1465
  const loader = new loader_1.RelativeLoader(this, maybeLoader.ILoader);
1421
- this._context = await containerContext_1.ContainerContext.createOrLoad(this, this.scope, this.codeLoader, codeDetails, snapshot, new deltaManagerProxy_1.DeltaManagerProxy(this._deltaManager), new quorum_1.QuorumProxy(this.protocolHandler.quorum), loader, (type, contents, batch, metadata) => this.submitContainerMessage(type, contents, batch, metadata), (summaryOp, 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);
1422
- this.emit("contextChanged", codeDetails);
1423
- }
1424
- updateDirtyContainerState(dirty) {
1425
- if (this._dirtyContainer === dirty) {
1426
- return;
1466
+ const loadCodeResult = await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "CodeLoad" }, async () => this.codeLoader.load(codeDetails));
1467
+ this._loadedModule = {
1468
+ module: loadCodeResult.module,
1469
+ // An older interface ICodeLoader could return an IFluidModule which didn't have details.
1470
+ // If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
1471
+ // TODO: Determine if this is still a realistic scenario or if this fixup could be removed.
1472
+ details: (_b = loadCodeResult.details) !== null && _b !== void 0 ? _b : codeDetails,
1473
+ };
1474
+ const fluidExport = this._loadedModule.module.fluidExport;
1475
+ const runtimeFactory = fluidExport === null || fluidExport === void 0 ? void 0 : fluidExport.IRuntimeFactory;
1476
+ if (runtimeFactory === undefined) {
1477
+ throw new Error(packageNotFactoryError);
1427
1478
  }
1428
- this._dirtyContainer = dirty;
1429
- this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
1479
+ const deltaManagerProxy = new deltaManagerProxy_1.DeltaManagerProxy(this._deltaManager);
1480
+ const quorumProxy = new quorum_1.QuorumProxy(this.protocolHandler.quorum);
1481
+ const context = new containerContext_1.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);
1482
+ this._lifecycleEvents.once("disposed", () => {
1483
+ context.dispose();
1484
+ quorumProxy.dispose();
1485
+ deltaManagerProxy.dispose();
1486
+ });
1487
+ this._runtime = await telemetry_utils_1.PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "InstantiateRuntime" }, async () => runtimeFactory.instantiateRuntime(context, existing));
1488
+ this._lifecycleEvents.emit("runtimeInstantiated");
1489
+ this._loadedCodeDetails = codeDetails;
1490
+ this.emit("contextChanged", codeDetails);
1430
1491
  }
1431
1492
  /**
1432
1493
  * Set the connected state of the ContainerContext
@@ -1436,17 +1497,16 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1436
1497
  */
1437
1498
  setContextConnectedState(state, readonly) {
1438
1499
  var _a;
1439
- if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1500
+ if (((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1440
1501
  /**
1441
1502
  * We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
1442
1503
  * ops getting through to the DeltaManager.
1443
1504
  * The ContainerRuntime's "connected" state simply means it is ok to send ops
1444
1505
  * See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
1445
1506
  */
1446
- this.context.setConnectionState(state && !readonly, this.clientId);
1507
+ this.runtime.setConnectionState(state && !readonly, this.clientId);
1447
1508
  }
1448
1509
  }
1449
1510
  }
1450
1511
  exports.Container = Container;
1451
- Container.version = "^0.1.0";
1452
1512
  //# sourceMappingURL=container.js.map