@fluidframework/container-loader 2.0.0-dev.5.2.0.169897 → 2.0.0-dev.5.3.2.178189

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 (145) hide show
  1. package/CHANGELOG.md +31 -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 +1 -1
  6. package/dist/connectionManager.d.ts.map +1 -1
  7. package/dist/connectionManager.js.map +1 -1
  8. package/dist/connectionStateHandler.d.ts +4 -1
  9. package/dist/connectionStateHandler.d.ts.map +1 -1
  10. package/dist/connectionStateHandler.js +10 -8
  11. package/dist/connectionStateHandler.js.map +1 -1
  12. package/dist/container.d.ts +30 -31
  13. package/dist/container.d.ts.map +1 -1
  14. package/dist/container.js +180 -108
  15. package/dist/container.js.map +1 -1
  16. package/dist/containerContext.d.ts +23 -66
  17. package/dist/containerContext.d.ts.map +1 -1
  18. package/dist/containerContext.js +28 -213
  19. package/dist/containerContext.js.map +1 -1
  20. package/dist/containerStorageAdapter.d.ts +1 -1
  21. package/dist/containerStorageAdapter.d.ts.map +1 -1
  22. package/dist/containerStorageAdapter.js +38 -6
  23. package/dist/containerStorageAdapter.js.map +1 -1
  24. package/dist/contracts.d.ts +1 -3
  25. package/dist/contracts.d.ts.map +1 -1
  26. package/dist/contracts.js.map +1 -1
  27. package/dist/deltaManager.d.ts +2 -1
  28. package/dist/deltaManager.d.ts.map +1 -1
  29. package/dist/deltaManager.js.map +1 -1
  30. package/dist/disposal.d.ts +13 -0
  31. package/dist/disposal.d.ts.map +1 -0
  32. package/dist/disposal.js +25 -0
  33. package/dist/disposal.js.map +1 -0
  34. package/dist/loader.d.ts +1 -2
  35. package/dist/loader.d.ts.map +1 -1
  36. package/dist/loader.js.map +1 -1
  37. package/dist/noopHeuristic.d.ts +23 -0
  38. package/dist/noopHeuristic.d.ts.map +1 -0
  39. package/dist/{collabWindowTracker.js → noopHeuristic.js} +30 -42
  40. package/dist/noopHeuristic.js.map +1 -0
  41. package/dist/packageVersion.d.ts +1 -1
  42. package/dist/packageVersion.js +1 -1
  43. package/dist/packageVersion.js.map +1 -1
  44. package/dist/protocol.d.ts +7 -12
  45. package/dist/protocol.d.ts.map +1 -1
  46. package/dist/protocol.js +17 -19
  47. package/dist/protocol.js.map +1 -1
  48. package/dist/protocolTreeDocumentStorageService.d.ts +1 -1
  49. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  50. package/dist/protocolTreeDocumentStorageService.js.map +1 -1
  51. package/dist/quorum.d.ts +1 -17
  52. package/dist/quorum.d.ts.map +1 -1
  53. package/dist/quorum.js +1 -17
  54. package/dist/quorum.js.map +1 -1
  55. package/dist/retriableDocumentStorageService.d.ts +1 -1
  56. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  57. package/dist/retriableDocumentStorageService.js.map +1 -1
  58. package/lib/catchUpMonitor.d.ts +1 -1
  59. package/lib/catchUpMonitor.d.ts.map +1 -1
  60. package/lib/catchUpMonitor.js.map +1 -1
  61. package/lib/connectionManager.d.ts +1 -1
  62. package/lib/connectionManager.d.ts.map +1 -1
  63. package/lib/connectionManager.js.map +1 -1
  64. package/lib/connectionStateHandler.d.ts +4 -1
  65. package/lib/connectionStateHandler.d.ts.map +1 -1
  66. package/lib/connectionStateHandler.js +10 -8
  67. package/lib/connectionStateHandler.js.map +1 -1
  68. package/lib/container.d.ts +30 -31
  69. package/lib/container.d.ts.map +1 -1
  70. package/lib/container.js +184 -112
  71. package/lib/container.js.map +1 -1
  72. package/lib/containerContext.d.ts +23 -66
  73. package/lib/containerContext.d.ts.map +1 -1
  74. package/lib/containerContext.js +28 -213
  75. package/lib/containerContext.js.map +1 -1
  76. package/lib/containerStorageAdapter.d.ts +1 -1
  77. package/lib/containerStorageAdapter.d.ts.map +1 -1
  78. package/lib/containerStorageAdapter.js +38 -6
  79. package/lib/containerStorageAdapter.js.map +1 -1
  80. package/lib/contracts.d.ts +1 -3
  81. package/lib/contracts.d.ts.map +1 -1
  82. package/lib/contracts.js.map +1 -1
  83. package/lib/deltaManager.d.ts +2 -1
  84. package/lib/deltaManager.d.ts.map +1 -1
  85. package/lib/deltaManager.js.map +1 -1
  86. package/lib/disposal.d.ts +13 -0
  87. package/lib/disposal.d.ts.map +1 -0
  88. package/lib/disposal.js +21 -0
  89. package/lib/disposal.js.map +1 -0
  90. package/lib/loader.d.ts +1 -2
  91. package/lib/loader.d.ts.map +1 -1
  92. package/lib/loader.js.map +1 -1
  93. package/lib/noopHeuristic.d.ts +23 -0
  94. package/lib/noopHeuristic.d.ts.map +1 -0
  95. package/lib/{collabWindowTracker.js → noopHeuristic.js} +30 -42
  96. package/lib/noopHeuristic.js.map +1 -0
  97. package/lib/packageVersion.d.ts +1 -1
  98. package/lib/packageVersion.js +1 -1
  99. package/lib/packageVersion.js.map +1 -1
  100. package/lib/protocol.d.ts +7 -12
  101. package/lib/protocol.d.ts.map +1 -1
  102. package/lib/protocol.js +15 -18
  103. package/lib/protocol.js.map +1 -1
  104. package/lib/protocolTreeDocumentStorageService.d.ts +1 -1
  105. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  106. package/lib/protocolTreeDocumentStorageService.js.map +1 -1
  107. package/lib/quorum.d.ts +1 -17
  108. package/lib/quorum.d.ts.map +1 -1
  109. package/lib/quorum.js +1 -16
  110. package/lib/quorum.js.map +1 -1
  111. package/lib/retriableDocumentStorageService.d.ts +1 -1
  112. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  113. package/lib/retriableDocumentStorageService.js.map +1 -1
  114. package/package.json +18 -14
  115. package/src/catchUpMonitor.ts +1 -1
  116. package/src/connectionManager.ts +1 -1
  117. package/src/connectionStateHandler.ts +15 -10
  118. package/src/container.ts +279 -139
  119. package/src/containerContext.ts +33 -335
  120. package/src/containerStorageAdapter.ts +47 -5
  121. package/src/contracts.ts +1 -3
  122. package/src/deltaManager.ts +15 -8
  123. package/src/disposal.ts +25 -0
  124. package/src/loader.ts +1 -1
  125. package/src/{collabWindowTracker.ts → noopHeuristic.ts} +37 -47
  126. package/src/packageVersion.ts +1 -1
  127. package/src/protocol.ts +18 -39
  128. package/src/protocolTreeDocumentStorageService.ts +1 -1
  129. package/src/quorum.ts +2 -31
  130. package/src/retriableDocumentStorageService.ts +2 -1
  131. package/dist/collabWindowTracker.d.ts +0 -19
  132. package/dist/collabWindowTracker.d.ts.map +0 -1
  133. package/dist/collabWindowTracker.js.map +0 -1
  134. package/dist/deltaManagerProxy.d.ts +0 -42
  135. package/dist/deltaManagerProxy.d.ts.map +0 -1
  136. package/dist/deltaManagerProxy.js +0 -79
  137. package/dist/deltaManagerProxy.js.map +0 -1
  138. package/lib/collabWindowTracker.d.ts +0 -19
  139. package/lib/collabWindowTracker.d.ts.map +0 -1
  140. package/lib/collabWindowTracker.js.map +0 -1
  141. package/lib/deltaManagerProxy.d.ts +0 -42
  142. package/lib/deltaManagerProxy.d.ts.map +0 -1
  143. package/lib/deltaManagerProxy.js +0 -74
  144. package/lib/deltaManagerProxy.js.map +0 -1
  145. package/src/deltaManagerProxy.ts +0 -109
package/lib/container.js CHANGED
@@ -5,30 +5,30 @@
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";
15
15
  import { ContainerContext } from "./containerContext";
16
16
  import { ReconnectMode, getPackageName } from "./contracts";
17
17
  import { DeltaManager } from "./deltaManager";
18
- import { DeltaManagerProxy } from "./deltaManagerProxy";
19
18
  import { RelativeLoader } from "./loader";
20
19
  import { pkgVersion } from "./packageVersion";
21
20
  import { ContainerStorageAdapter, getBlobContentsFromTree, getBlobContentsFromTreeWithBlobContents, } from "./containerStorageAdapter";
22
21
  import { createConnectionStateHandler } from "./connectionStateHandler";
23
22
  import { getProtocolSnapshotTree, getSnapshotTreeFromSerializedContainer } from "./utils";
24
- import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues, QuorumProxy, } from "./quorum";
25
- import { CollabWindowTracker } from "./collabWindowTracker";
23
+ import { initQuorumValuesFromCodeDetails, getCodeDetailsFromQuorumValues } from "./quorum";
24
+ import { NoopHeuristic } from "./noopHeuristic";
26
25
  import { ConnectionManager } from "./connectionManager";
27
26
  import { ConnectionState } from "./connectionState";
28
- import { OnlyValidTermValue, ProtocolHandler, } from "./protocol";
27
+ import { OnlyValidTermValue, ProtocolHandler, protocolHandlerShouldProcessSignal, } from "./protocol";
29
28
  const detachedContainerRefSeqNumber = 0;
30
29
  const dirtyContainerEvent = "dirty";
31
30
  const savedContainerEvent = "saved";
31
+ const packageNotFactoryError = "Code package does not implement IRuntimeFactory";
32
32
  /**
33
33
  * Waits until container connects to delta storage and gets up-to-date.
34
34
  *
@@ -156,8 +156,23 @@ export class Container extends EventEmitterWithErrorHandling {
156
156
  this.attachStarted = false;
157
157
  this._dirtyContainer = false;
158
158
  this.savedOps = [];
159
+ this.clientsWhoShouldHaveLeft = new Set();
159
160
  this.setAutoReconnectTime = performance.now();
161
+ this._lifecycleEvents = new TypedEventEmitter();
160
162
  this._disposed = false;
163
+ this.getAbsoluteUrl = async (relativeUrl) => {
164
+ if (this.resolvedUrl === undefined) {
165
+ return undefined;
166
+ }
167
+ return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, getPackageName(this._loadedCodeDetails));
168
+ };
169
+ this.updateDirtyContainerState = (dirty) => {
170
+ if (this._dirtyContainer === dirty) {
171
+ return;
172
+ }
173
+ this._dirtyContainer = dirty;
174
+ this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
175
+ };
161
176
  const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
162
177
  this.connectionTransitionTimes[ConnectionState.Disconnected] = performance.now();
163
178
  const pendingLocalState = loadProps === null || loadProps === void 0 ? void 0 : loadProps.pendingLocalState;
@@ -202,13 +217,18 @@ export class Container extends EventEmitterWithErrorHandling {
202
217
  dmInitialSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.initialSequenceNumber; },
203
218
  dmLastProcessedSeqNumber: () => { var _a; return (_a = this._deltaManager) === null || _a === void 0 ? void 0 : _a.lastSequenceNumber; },
204
219
  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; },
220
+ containerLoadedFromVersionId: () => { var _a; return (_a = this._loadedFromVersion) === null || _a === void 0 ? void 0 : _a.id; },
221
+ containerLoadedFromVersionDate: () => { var _a; return (_a = this._loadedFromVersion) === null || _a === void 0 ? void 0 : _a.date; },
207
222
  // message information to associate errors with the specific execution state
208
223
  // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
209
224
  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; },
210
225
  dmLastMsqSeqTimestamp: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.timestamp; },
211
- dmLastMsqSeqClientId: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientId; },
226
+ dmLastMsqSeqClientId: () => {
227
+ var _a, _b, _c, _d;
228
+ return ((_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientId) === null
229
+ ? "null"
230
+ : (_d = (_c = this.deltaManager) === null || _c === void 0 ? void 0 : _c.lastMessage) === null || _d === void 0 ? void 0 : _d.clientId;
231
+ },
212
232
  dmLastMsgClientSeq: () => { var _a, _b; return (_b = (_a = this.deltaManager) === null || _a === void 0 ? void 0 : _a.lastMessage) === null || _b === void 0 ? void 0 : _b.clientSequenceNumber; },
213
233
  connectionStateDuration: () => performance.now() - this.connectionTransitionTimes[this.connectionState],
214
234
  },
@@ -254,6 +274,9 @@ export class Container extends EventEmitterWithErrorHandling {
254
274
  this.connect();
255
275
  }
256
276
  },
277
+ clientShouldHaveLeft: (clientId) => {
278
+ this.clientsWhoShouldHaveLeft.add(clientId);
279
+ },
257
280
  }, this.deltaManager, pendingLocalState === null || pendingLocalState === void 0 ? void 0 : pendingLocalState.clientId);
258
281
  this.on(savedContainerEvent, () => {
259
282
  this.connectionStateHandler.containerSaved();
@@ -363,14 +386,11 @@ export class Container extends EventEmitterWithErrorHandling {
363
386
  this._lifecycleState === "disposing" ||
364
387
  this._lifecycleState === "disposed");
365
388
  }
366
- get storage() {
367
- return this.storageAdapter;
368
- }
369
- get context() {
370
- if (this._context === undefined) {
371
- throw new GenericError("Attempted to access context before it was defined");
389
+ get runtime() {
390
+ if (this._runtime === undefined) {
391
+ throw new Error("Attempted to access runtime before it was defined");
372
392
  }
373
- return this._context;
393
+ return this._runtime;
374
394
  }
375
395
  get protocolHandler() {
376
396
  if (this._protocolHandler === undefined) {
@@ -399,15 +419,9 @@ export class Container extends EventEmitterWithErrorHandling {
399
419
  */
400
420
  return (_a = this.service) === null || _a === void 0 ? void 0 : _a.resolvedUrl;
401
421
  }
402
- get loadedFromVersion() {
403
- return this._loadedFromVersion;
404
- }
405
422
  get readOnlyInfo() {
406
423
  return this._deltaManager.readOnlyInfo;
407
424
  }
408
- get closeSignal() {
409
- return this._deltaManager.closeAbortController.signal;
410
- }
411
425
  /**
412
426
  * Tracks host requiring read-only mode.
413
427
  */
@@ -423,13 +437,6 @@ export class Container extends EventEmitterWithErrorHandling {
423
437
  get connected() {
424
438
  return this.connectionStateHandler.connectionState === ConnectionState.Connected;
425
439
  }
426
- /**
427
- * Service configuration details. If running in offline mode will be undefined otherwise will contain service
428
- * configuration details returned as part of the initial connection.
429
- */
430
- get serviceConfiguration() {
431
- return this._deltaManager.serviceConfiguration;
432
- }
433
440
  /**
434
441
  * The server provided id of the client.
435
442
  * Set once this.connected is true, otherwise undefined
@@ -437,21 +444,11 @@ export class Container extends EventEmitterWithErrorHandling {
437
444
  get clientId() {
438
445
  return this._clientId;
439
446
  }
440
- /**
441
- * The server provided claims of the client.
442
- * Set once this.connected is true, otherwise undefined
443
- */
444
- get scopes() {
445
- return this._deltaManager.connectionManager.scopes;
446
- }
447
- get clientDetails() {
448
- return this._deltaManager.clientDetails;
449
- }
450
447
  get offlineLoadEnabled() {
451
448
  var _a, _b;
452
449
  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;
453
450
  // summarizer will not have any pending state we want to save
454
- return enabled && this.clientDetails.capabilities.interactive;
451
+ return enabled && this.deltaManager.clientDetails.capabilities.interactive;
455
452
  }
456
453
  /**
457
454
  * Get the code details that are currently specified for the container.
@@ -466,8 +463,7 @@ export class Container extends EventEmitterWithErrorHandling {
466
463
  * loaded.
467
464
  */
468
465
  getLoadedCodeDetails() {
469
- var _a;
470
- return (_a = this._context) === null || _a === void 0 ? void 0 : _a.codeDetails;
466
+ return this._loadedCodeDetails;
471
467
  }
472
468
  /**
473
469
  * Retrieves the audience associated with the document
@@ -488,32 +484,26 @@ export class Container extends EventEmitterWithErrorHandling {
488
484
  */
489
485
  async getEntryPoint() {
490
486
  var _a, _b;
491
- // Only the disposing/disposed lifecycle states should prevent access to the entryPoint; closing/closed should still
492
- // allow it since they mean a kind of read-only state for the Container.
493
- // Note that all 4 are lifecycle states but only 'closed' and 'disposed' are emitted as events.
494
- if (this._lifecycleState === "disposing" || this._lifecycleState === "disposed") {
495
- throw new UsageError("The container is disposing or disposed");
496
- }
497
- while (this._context === undefined) {
498
- await new Promise((resolve, reject) => {
499
- const contextChangedHandler = () => {
500
- resolve();
501
- this.off("disposed", disposedHandler);
502
- };
503
- const disposedHandler = (error) => {
504
- reject(error !== null && error !== void 0 ? error : "The Container is disposed");
505
- this.off("contextChanged", contextChangedHandler);
506
- };
507
- this.once("contextChanged", contextChangedHandler);
508
- this.once("disposed", disposedHandler);
509
- });
510
- // The Promise above should only resolve (vs reject) if the 'contextChanged' event was emitted and that
511
- // should have set this._context; making sure.
512
- assert(this._context !== undefined, 0x5a2 /* Context still not defined after contextChanged event */);
487
+ if (this._disposed) {
488
+ throw new UsageError("The context is already disposed");
489
+ }
490
+ if (this._runtime !== undefined) {
491
+ return (_b = (_a = this._runtime).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a);
513
492
  }
514
- // Disable lint rule for the sake of more complete stack traces
515
- // eslint-disable-next-line no-return-await
516
- return await ((_b = (_a = this._context).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
493
+ return new Promise((resolve, reject) => {
494
+ const runtimeInstantiatedHandler = () => {
495
+ var _a, _b;
496
+ assert(this._runtime !== undefined, 0x5a3 /* runtimeInstantiated fired but runtime is still undefined */);
497
+ resolve((_b = (_a = this._runtime).getEntryPoint) === null || _b === void 0 ? void 0 : _b.call(_a));
498
+ this._lifecycleEvents.off("disposed", disposedHandler);
499
+ };
500
+ const disposedHandler = () => {
501
+ reject(new Error("ContainerContext was disposed"));
502
+ this._lifecycleEvents.off("runtimeInstantiated", runtimeInstantiatedHandler);
503
+ };
504
+ this._lifecycleEvents.once("runtimeInstantiated", runtimeInstantiatedHandler);
505
+ this._lifecycleEvents.once("disposed", disposedHandler);
506
+ });
517
507
  }
518
508
  /**
519
509
  * Retrieves the quorum associated with the document
@@ -567,6 +557,10 @@ export class Container extends EventEmitterWithErrorHandling {
567
557
  }
568
558
  finally {
569
559
  this._lifecycleState = "closed";
560
+ // There is no user for summarizer, so we need to ensure dispose is called
561
+ if (this.client.details.type === summarizerClientType) {
562
+ this.dispose(error);
563
+ }
570
564
  }
571
565
  }
572
566
  disposeCore(error) {
@@ -580,7 +574,8 @@ export class Container extends EventEmitterWithErrorHandling {
580
574
  // This gives us a chance to know what errors happened on open vs. on fully loaded container.
581
575
  this.mc.logger.sendTelemetryEvent({
582
576
  eventName: "ContainerDispose",
583
- category: "generic",
577
+ // Only log error if container isn't closed
578
+ category: !this.closed && error !== undefined ? "error" : "generic",
584
579
  }, error);
585
580
  // ! Progressing from "closed" to "disposing" is not allowed
586
581
  if (this._lifecycleState !== "closed") {
@@ -588,7 +583,8 @@ export class Container extends EventEmitterWithErrorHandling {
588
583
  }
589
584
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
590
585
  this.connectionStateHandler.dispose();
591
- (_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);
592
588
  this.storageAdapter.dispose();
593
589
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
594
590
  // about file, like file being overwritten in storage, but client having stale local cache.
@@ -606,6 +602,7 @@ export class Container extends EventEmitterWithErrorHandling {
606
602
  }
607
603
  finally {
608
604
  this._lifecycleState = "disposed";
605
+ this._lifecycleEvents.emit("disposed");
609
606
  }
610
607
  }
611
608
  closeAndGetPendingLocalState() {
@@ -620,12 +617,15 @@ export class Container extends EventEmitterWithErrorHandling {
620
617
  if (!this.offlineLoadEnabled) {
621
618
  throw new UsageError("Can't get pending local state unless offline load is enabled");
622
619
  }
620
+ if (this.closed || this._disposed) {
621
+ throw new UsageError("Pending state cannot be retried if the container is closed or disposed");
622
+ }
623
623
  assert(this.attachState === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
624
624
  assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
625
625
  assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
626
626
  assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
627
627
  const pendingState = {
628
- pendingRuntimeState: this.context.getPendingLocalState(),
628
+ pendingRuntimeState: this.runtime.getPendingLocalState(),
629
629
  baseSnapshot: this.baseSnapshot,
630
630
  snapshotBlobs: this.baseSnapshotBlobs,
631
631
  savedOps: this.savedOps,
@@ -641,7 +641,7 @@ export class Container extends EventEmitterWithErrorHandling {
641
641
  }
642
642
  serialize() {
643
643
  assert(this.attachState === AttachState.Detached, 0x0d3 /* "Should only be called in detached container" */);
644
- const appSummary = this.context.createSummary();
644
+ const appSummary = this.runtime.createSummary();
645
645
  const protocolSummary = this.captureProtocolSummary();
646
646
  const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
647
647
  if (this.detachedBlobStorage && this.detachedBlobStorage.size > 0) {
@@ -670,7 +670,7 @@ export class Container extends EventEmitterWithErrorHandling {
670
670
  if (!hasAttachmentBlobs) {
671
671
  // Get the document state post attach - possibly can just call attach but we need to change the
672
672
  // semantics around what the attach means as far as async code goes.
673
- const appSummary = this.context.createSummary();
673
+ const appSummary = this.runtime.createSummary();
674
674
  const protocolSummary = this.captureProtocolSummary();
675
675
  summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
676
676
  // Set the state as attaching as we are starting the process of attaching container.
@@ -678,6 +678,7 @@ export class Container extends EventEmitterWithErrorHandling {
678
678
  // starting to attach the container to storage.
679
679
  // Also, this should only be fired in detached container.
680
680
  this._attachState = AttachState.Attaching;
681
+ this.runtime.setAttachState(AttachState.Attaching);
681
682
  this.emit("attaching");
682
683
  if (this.offlineLoadEnabled) {
683
684
  const snapshot = getSnapshotTreeFromSerializedContainer(summary);
@@ -692,7 +693,7 @@ export class Container extends EventEmitterWithErrorHandling {
692
693
  assert(this.client.details.type !== summarizerClientType &&
693
694
  createNewResolvedUrl !== undefined, 0x2c4 /* "client should not be summarizer before container is created" */);
694
695
  this.service = await runWithRetry(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger, false), "containerAttach", this.mc.logger, {
695
- cancel: this.closeSignal,
696
+ cancel: this._deltaManager.closeAbortController.signal,
696
697
  });
697
698
  }
698
699
  await this.storageAdapter.connectToService(this.service);
@@ -714,10 +715,11 @@ export class Container extends EventEmitterWithErrorHandling {
714
715
  }
715
716
  }
716
717
  // take summary and upload
717
- const appSummary = this.context.createSummary(redirectTable);
718
+ const appSummary = this.runtime.createSummary(redirectTable);
718
719
  const protocolSummary = this.captureProtocolSummary();
719
720
  summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
720
721
  this._attachState = AttachState.Attaching;
722
+ this.runtime.setAttachState(AttachState.Attaching);
721
723
  this.emit("attaching");
722
724
  if (this.offlineLoadEnabled) {
723
725
  const snapshot = getSnapshotTreeFromSerializedContainer(summary);
@@ -732,6 +734,7 @@ export class Container extends EventEmitterWithErrorHandling {
732
734
  });
733
735
  }
734
736
  this._attachState = AttachState.Attached;
737
+ this.runtime.setAttachState(AttachState.Attached);
735
738
  this.emit("attached");
736
739
  if (!this.closed) {
737
740
  this.resumeInternal({
@@ -750,7 +753,7 @@ export class Container extends EventEmitterWithErrorHandling {
750
753
  }, { start: true, end: true, cancel: "generic" });
751
754
  }
752
755
  async request(path) {
753
- return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.context.request(path), { end: true, cancel: "error" });
756
+ return PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.runtime.request(path), { end: true, cancel: "error" });
754
757
  }
755
758
  setAutoReconnectInternal(mode) {
756
759
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
@@ -816,13 +819,6 @@ export class Container extends EventEmitterWithErrorHandling {
816
819
  // Ensure connection to web socket
817
820
  this.connectToDeltaStream(args);
818
821
  }
819
- async getAbsoluteUrl(relativeUrl) {
820
- var _a;
821
- if (this.resolvedUrl === undefined) {
822
- return undefined;
823
- }
824
- return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, getPackageName((_a = this._context) === null || _a === void 0 ? void 0 : _a.codeDetails));
825
- }
826
822
  async proposeCodeDetails(codeDetails) {
827
823
  if (!isFluidCodeDetails(codeDetails)) {
828
824
  throw new Error("Provided codeDetails are not IFluidCodeDetails");
@@ -844,7 +840,7 @@ export class Container extends EventEmitterWithErrorHandling {
844
840
  this.deltaManager.inbound.pause(),
845
841
  this.deltaManager.inboundSignal.pause(),
846
842
  ]);
847
- if ((await this.context.satisfies(codeDetails)) === true) {
843
+ if ((await this.satisfies(codeDetails)) === true) {
848
844
  this.deltaManager.inbound.resume();
849
845
  this.deltaManager.inboundSignal.resume();
850
846
  return;
@@ -853,6 +849,38 @@ export class Container extends EventEmitterWithErrorHandling {
853
849
  const error = new GenericError("Existing context does not satisfy incoming proposal");
854
850
  this.close(error);
855
851
  }
852
+ /**
853
+ * Determines if the currently loaded module satisfies the incoming constraint code details
854
+ */
855
+ async satisfies(constraintCodeDetails) {
856
+ var _a, _b;
857
+ // If we have no module, it can't satisfy anything.
858
+ if (this._loadedModule === undefined) {
859
+ return false;
860
+ }
861
+ const comparers = [];
862
+ const maybeCompareCodeLoader = this.codeLoader;
863
+ if (maybeCompareCodeLoader.IFluidCodeDetailsComparer !== undefined) {
864
+ comparers.push(maybeCompareCodeLoader.IFluidCodeDetailsComparer);
865
+ }
866
+ const maybeCompareExport = (_a = this._loadedModule) === null || _a === void 0 ? void 0 : _a.module.fluidExport;
867
+ if ((maybeCompareExport === null || maybeCompareExport === void 0 ? void 0 : maybeCompareExport.IFluidCodeDetailsComparer) !== undefined) {
868
+ comparers.push(maybeCompareExport.IFluidCodeDetailsComparer);
869
+ }
870
+ // If there are no comparers, then it's impossible to know if the currently loaded package satisfies
871
+ // the incoming constraint, so we return false. Assuming it does not satisfy is safer, to force a reload
872
+ // rather than potentially running with incompatible code.
873
+ if (comparers.length === 0) {
874
+ return false;
875
+ }
876
+ for (const comparer of comparers) {
877
+ const satisfies = await comparer.satisfies((_b = this._loadedModule) === null || _b === void 0 ? void 0 : _b.details, constraintCodeDetails);
878
+ if (satisfies === false) {
879
+ return false;
880
+ }
881
+ }
882
+ return true;
883
+ }
856
884
  async getVersion(version) {
857
885
  const versions = await this.storageAdapter.getVersions(version, 1);
858
886
  return versions[0];
@@ -870,7 +898,7 @@ export class Container extends EventEmitterWithErrorHandling {
870
898
  * @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
871
899
  */
872
900
  async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState) {
873
- var _a;
901
+ var _a, _b, _c;
874
902
  this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
875
903
  // Ideally we always connect as "read" by default.
876
904
  // Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
@@ -914,7 +942,7 @@ export class Container extends EventEmitterWithErrorHandling {
914
942
  if (this.offlineLoadEnabled) {
915
943
  this.baseSnapshot = snapshot;
916
944
  // Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
917
- this.baseSnapshotBlobs = await getBlobContentsFromTree(snapshot, this.storage);
945
+ this.baseSnapshotBlobs = await getBlobContentsFromTree(snapshot, this.storageAdapter);
918
946
  }
919
947
  }
920
948
  const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
@@ -950,7 +978,7 @@ export class Container extends EventEmitterWithErrorHandling {
950
978
  for (const message of pendingLocalState.savedOps) {
951
979
  this.processRemoteMessage(message);
952
980
  // allow runtime to apply stashed ops at this op's sequence number
953
- await this.context.notifyOpReplay(message);
981
+ await ((_c = (_b = this.runtime).notifyOpReplay) === null || _c === void 0 ? void 0 : _c.call(_b, message));
954
982
  }
955
983
  pendingLocalState.savedOps = [];
956
984
  // now set clientId to stashed clientId so live ops are correctly processed as local
@@ -1189,7 +1217,7 @@ export class Container extends EventEmitterWithErrorHandling {
1189
1217
  });
1190
1218
  deltaManager.on("disconnect", (reason, error) => {
1191
1219
  var _a;
1192
- (_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
1220
+ (_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyDisconnect();
1193
1221
  if (!this.closed) {
1194
1222
  this.connectionStateHandler.receivedDisconnectEvent(reason, error);
1195
1223
  }
@@ -1246,7 +1274,9 @@ export class Container extends EventEmitterWithErrorHandling {
1246
1274
  else if (value === ConnectionState.CatchingUp) {
1247
1275
  // This info is of most interesting while Catching Up.
1248
1276
  checkpointSequenceNumber = this.deltaManager.lastKnownSeqNumber;
1249
- if (this.deltaManager.hasCheckpointSequenceNumber) {
1277
+ // Need to check that we have already loaded and fetched the snapshot.
1278
+ if (this.deltaManager.hasCheckpointSequenceNumber &&
1279
+ this._lifecycleState === "loaded") {
1250
1280
  opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
1251
1281
  }
1252
1282
  }
@@ -1333,7 +1363,7 @@ export class Container extends EventEmitterWithErrorHandling {
1333
1363
  return -1;
1334
1364
  }
1335
1365
  this.messageCountAfterDisconnection += 1;
1336
- (_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
1366
+ (_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyMessageSent();
1337
1367
  return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
1338
1368
  }
1339
1369
  processRemoteMessage(message) {
@@ -1341,24 +1371,51 @@ export class Container extends EventEmitterWithErrorHandling {
1341
1371
  this.savedOps.push(message);
1342
1372
  }
1343
1373
  const local = this.clientId === message.clientId;
1374
+ // Check and report if we're getting messages from a clientId that we previously
1375
+ // flagged should have left, or from a client that's not in the quorum but should be
1376
+ if (message.clientId != null) {
1377
+ const client = this.protocolHandler.quorum.getMember(message.clientId);
1378
+ if (client === undefined && message.type !== MessageType.ClientJoin) {
1379
+ // pre-0.58 error message: messageClientIdMissingFromQuorum
1380
+ throw new Error("Remote message's clientId is missing from the quorum");
1381
+ }
1382
+ // Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
1383
+ // It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
1384
+ // document we don't need to blow up aggressively.
1385
+ if (this.clientsWhoShouldHaveLeft.has(message.clientId) &&
1386
+ !canBeCoalescedByService(message)) {
1387
+ // pre-0.58 error message: messageClientIdShouldHaveLeft
1388
+ throw new Error("Remote message's clientId already should have left");
1389
+ }
1390
+ }
1344
1391
  // Allow the protocol handler to process the message
1345
1392
  const result = this.protocolHandler.processMessage(message, local);
1346
1393
  // Forward messages to the loaded runtime for processing
1347
- this.context.process(message, local);
1394
+ this.runtime.process(message, local);
1348
1395
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
1349
1396
  if (this.activeConnection()) {
1350
- if (this.collabWindowTracker === undefined) {
1397
+ if (this.noopHeuristic === undefined) {
1398
+ const serviceConfiguration = this.deltaManager.serviceConfiguration;
1351
1399
  // Note that config from first connection will be used for this container's lifetime.
1352
1400
  // That means that if relay service changes settings, such changes will impact only newly booted
1353
1401
  // clients.
1354
1402
  // All existing will continue to use settings they got earlier.
1355
- assert(this.serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1356
- this.collabWindowTracker = new CollabWindowTracker((type) => {
1357
- assert(this.activeConnection(), 0x241 /* "disconnect should result in stopSequenceNumberUpdate() call" */);
1358
- this.submitMessage(type);
1359
- }, this.serviceConfiguration.noopTimeFrequency, this.serviceConfiguration.noopCountFrequency);
1403
+ assert(serviceConfiguration !== undefined, 0x2e4 /* "there should be service config for active connection" */);
1404
+ this.noopHeuristic = new NoopHeuristic(serviceConfiguration.noopTimeFrequency, serviceConfiguration.noopCountFrequency);
1405
+ this.noopHeuristic.on("wantsNoop", () => {
1406
+ // On disconnect we notify the heuristic which should prevent it from wanting a noop.
1407
+ // Hitting this assert would imply we lost activeConnection between notifying the heuristic of a processed message and
1408
+ // running the microtask that the heuristic queued in response.
1409
+ assert(this.activeConnection(), 0x241 /* "Trying to send noop without active connection" */);
1410
+ this.submitMessage(MessageType.NoOp);
1411
+ });
1412
+ }
1413
+ this.noopHeuristic.notifyMessageProcessed(message);
1414
+ // The contract with the protocolHandler is that returning "immediateNoOp" is equivalent to "please immediately accept the proposal I just processed".
1415
+ if (result.immediateNoOp === true) {
1416
+ // ADO:1385: Remove cast and use MessageType once definition changes propagate
1417
+ this.submitMessage(MessageType2.Accept);
1360
1418
  }
1361
- this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
1362
1419
  }
1363
1420
  this.emit("op", message);
1364
1421
  }
@@ -1367,12 +1424,12 @@ export class Container extends EventEmitterWithErrorHandling {
1367
1424
  }
1368
1425
  processSignal(message) {
1369
1426
  // No clientId indicates a system signal message.
1370
- if (message.clientId === null) {
1427
+ if (protocolHandlerShouldProcessSignal(message)) {
1371
1428
  this.protocolHandler.processSignal(message);
1372
1429
  }
1373
1430
  else {
1374
1431
  const local = this.clientId === message.clientId;
1375
- this.context.processSignal(message, local);
1432
+ this.runtime.processSignal(message, local);
1376
1433
  }
1377
1434
  }
1378
1435
  /**
@@ -1405,21 +1462,37 @@ export class Container extends EventEmitterWithErrorHandling {
1405
1462
  await this.instantiateContext(existing, codeDetails, snapshot);
1406
1463
  }
1407
1464
  async instantiateContext(existing, codeDetails, snapshot, pendingLocalState) {
1408
- var _a;
1409
- assert(((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing context not disposed" */);
1465
+ var _a, _b;
1466
+ assert(((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing runtime not disposed" */);
1410
1467
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1411
1468
  // are set. Global requests will still go directly to the loader
1412
1469
  const maybeLoader = this.scope;
1413
1470
  const loader = new RelativeLoader(this, maybeLoader.ILoader);
1414
- 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);
1415
- this.emit("contextChanged", codeDetails);
1416
- }
1417
- updateDirtyContainerState(dirty) {
1418
- if (this._dirtyContainer === dirty) {
1419
- return;
1471
+ const loadCodeResult = await PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "CodeLoad" }, async () => this.codeLoader.load(codeDetails));
1472
+ this._loadedModule = {
1473
+ module: loadCodeResult.module,
1474
+ // An older interface ICodeLoader could return an IFluidModule which didn't have details.
1475
+ // If we're using one of those older ICodeLoaders, then we fix up the module with the specified details here.
1476
+ // TODO: Determine if this is still a realistic scenario or if this fixup could be removed.
1477
+ details: (_b = loadCodeResult.details) !== null && _b !== void 0 ? _b : codeDetails,
1478
+ };
1479
+ const fluidExport = this._loadedModule.module.fluidExport;
1480
+ const runtimeFactory = fluidExport === null || fluidExport === void 0 ? void 0 : fluidExport.IRuntimeFactory;
1481
+ if (runtimeFactory === undefined) {
1482
+ throw new Error(packageNotFactoryError);
1420
1483
  }
1421
- this._dirtyContainer = dirty;
1422
- this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
1484
+ const getSpecifiedCodeDetails = () => {
1485
+ var _a;
1486
+ return ((_a = this.protocolHandler.quorum.get("code")) !== null && _a !== void 0 ? _a : this.protocolHandler.quorum.get("code2"));
1487
+ };
1488
+ const context = new ContainerContext(this.options, this.scope, snapshot, this._loadedFromVersion, this._deltaManager, this.storageAdapter, this.protocolHandler.quorum, 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, () => this._deltaManager.serviceConfiguration, () => this.attachState, () => this.connected, getSpecifiedCodeDetails, this._deltaManager.clientDetails, existing, this.subLogger, pendingLocalState);
1489
+ this._lifecycleEvents.once("disposed", () => {
1490
+ context.dispose();
1491
+ });
1492
+ this._runtime = await PerformanceEvent.timedExecAsync(this.subLogger, { eventName: "InstantiateRuntime" }, async () => runtimeFactory.instantiateRuntime(context, existing));
1493
+ this._lifecycleEvents.emit("runtimeInstantiated");
1494
+ this._loadedCodeDetails = codeDetails;
1495
+ this.emit("contextChanged", codeDetails);
1423
1496
  }
1424
1497
  /**
1425
1498
  * Set the connected state of the ContainerContext
@@ -1429,16 +1502,15 @@ export class Container extends EventEmitterWithErrorHandling {
1429
1502
  */
1430
1503
  setContextConnectedState(state, readonly) {
1431
1504
  var _a;
1432
- if (((_a = this._context) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1505
+ if (((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) === false) {
1433
1506
  /**
1434
1507
  * We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
1435
1508
  * ops getting through to the DeltaManager.
1436
1509
  * The ContainerRuntime's "connected" state simply means it is ok to send ops
1437
1510
  * See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
1438
1511
  */
1439
- this.context.setConnectionState(state && !readonly, this.clientId);
1512
+ this.runtime.setConnectionState(state && !readonly, this.clientId);
1440
1513
  }
1441
1514
  }
1442
1515
  }
1443
- Container.version = "^0.1.0";
1444
1516
  //# sourceMappingURL=container.js.map