@fluidframework/container-loader 2.0.0-internal.5.1.1 → 2.0.0-internal.5.3.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 (145) hide show
  1. package/CHANGELOG.md +27 -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 +182 -109
  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 +186 -113
  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 +284 -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();
@@ -296,6 +319,7 @@ export class Container extends EventEmitterWithErrorHandling {
296
319
  static async load(loadProps, createProps) {
297
320
  const { version, pendingLocalState, loadMode, resolvedUrl } = loadProps;
298
321
  const container = new Container(createProps, loadProps);
322
+ const disableRecordHeapSize = container.mc.config.getBoolean("Fluid.Loader.DisableRecordHeapSize");
299
323
  return PerformanceEvent.timedExecAsync(container.mc.logger, { eventName: "Load" }, async (event) => new Promise((resolve, reject) => {
300
324
  const defaultMode = { opsBeforeReturn: "cached" };
301
325
  // if we have pendingLocalState, anything we cached is not useful and we shouldn't wait for connection
@@ -323,7 +347,7 @@ export class Container extends EventEmitterWithErrorHandling {
323
347
  container.close(err);
324
348
  onClosed(err);
325
349
  });
326
- }), { start: true, end: true, cancel: "generic" });
350
+ }), { start: true, end: true, cancel: "generic" }, disableRecordHeapSize !== true /* recordHeapSize */);
327
351
  }
328
352
  /**
329
353
  * Create a new container in a detached state.
@@ -362,14 +386,11 @@ export class Container extends EventEmitterWithErrorHandling {
362
386
  this._lifecycleState === "disposing" ||
363
387
  this._lifecycleState === "disposed");
364
388
  }
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");
389
+ get runtime() {
390
+ if (this._runtime === undefined) {
391
+ throw new Error("Attempted to access runtime before it was defined");
371
392
  }
372
- return this._context;
393
+ return this._runtime;
373
394
  }
374
395
  get protocolHandler() {
375
396
  if (this._protocolHandler === undefined) {
@@ -398,15 +419,9 @@ export class Container extends EventEmitterWithErrorHandling {
398
419
  */
399
420
  return (_a = this.service) === null || _a === void 0 ? void 0 : _a.resolvedUrl;
400
421
  }
401
- get loadedFromVersion() {
402
- return this._loadedFromVersion;
403
- }
404
422
  get readOnlyInfo() {
405
423
  return this._deltaManager.readOnlyInfo;
406
424
  }
407
- get closeSignal() {
408
- return this._deltaManager.closeAbortController.signal;
409
- }
410
425
  /**
411
426
  * Tracks host requiring read-only mode.
412
427
  */
@@ -422,13 +437,6 @@ export class Container extends EventEmitterWithErrorHandling {
422
437
  get connected() {
423
438
  return this.connectionStateHandler.connectionState === ConnectionState.Connected;
424
439
  }
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
440
  /**
433
441
  * The server provided id of the client.
434
442
  * Set once this.connected is true, otherwise undefined
@@ -436,21 +444,11 @@ export class Container extends EventEmitterWithErrorHandling {
436
444
  get clientId() {
437
445
  return this._clientId;
438
446
  }
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
447
  get offlineLoadEnabled() {
450
448
  var _a, _b;
451
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;
452
450
  // summarizer will not have any pending state we want to save
453
- return enabled && this.clientDetails.capabilities.interactive;
451
+ return enabled && this.deltaManager.clientDetails.capabilities.interactive;
454
452
  }
455
453
  /**
456
454
  * Get the code details that are currently specified for the container.
@@ -465,8 +463,7 @@ export class Container extends EventEmitterWithErrorHandling {
465
463
  * loaded.
466
464
  */
467
465
  getLoadedCodeDetails() {
468
- var _a;
469
- return (_a = this._context) === null || _a === void 0 ? void 0 : _a.codeDetails;
466
+ return this._loadedCodeDetails;
470
467
  }
471
468
  /**
472
469
  * Retrieves the audience associated with the document
@@ -487,32 +484,26 @@ export class Container extends EventEmitterWithErrorHandling {
487
484
  */
488
485
  async getEntryPoint() {
489
486
  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");
495
- }
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 */);
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);
512
492
  }
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));
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
+ });
516
507
  }
517
508
  /**
518
509
  * Retrieves the quorum associated with the document
@@ -566,6 +557,10 @@ export class Container extends EventEmitterWithErrorHandling {
566
557
  }
567
558
  finally {
568
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
+ }
569
564
  }
570
565
  }
571
566
  disposeCore(error) {
@@ -579,7 +574,8 @@ export class Container extends EventEmitterWithErrorHandling {
579
574
  // This gives us a chance to know what errors happened on open vs. on fully loaded container.
580
575
  this.mc.logger.sendTelemetryEvent({
581
576
  eventName: "ContainerDispose",
582
- category: "generic",
577
+ // Only log error if container isn't closed
578
+ category: !this.closed && error !== undefined ? "error" : "generic",
583
579
  }, error);
584
580
  // ! Progressing from "closed" to "disposing" is not allowed
585
581
  if (this._lifecycleState !== "closed") {
@@ -587,7 +583,8 @@ export class Container extends EventEmitterWithErrorHandling {
587
583
  }
588
584
  (_a = this._protocolHandler) === null || _a === void 0 ? void 0 : _a.close();
589
585
  this.connectionStateHandler.dispose();
590
- (_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);
591
588
  this.storageAdapter.dispose();
592
589
  // Notify storage about critical errors. They may be due to disconnect between client & server knowledge
593
590
  // about file, like file being overwritten in storage, but client having stale local cache.
@@ -605,6 +602,7 @@ export class Container extends EventEmitterWithErrorHandling {
605
602
  }
606
603
  finally {
607
604
  this._lifecycleState = "disposed";
605
+ this._lifecycleEvents.emit("disposed");
608
606
  }
609
607
  }
610
608
  closeAndGetPendingLocalState() {
@@ -619,12 +617,15 @@ export class Container extends EventEmitterWithErrorHandling {
619
617
  if (!this.offlineLoadEnabled) {
620
618
  throw new UsageError("Can't get pending local state unless offline load is enabled");
621
619
  }
620
+ if (this.closed || this._disposed) {
621
+ throw new UsageError("Pending state cannot be retried if the container is closed or disposed");
622
+ }
622
623
  assert(this.attachState === AttachState.Attached, 0x0d1 /* "Container should be attached before close" */);
623
624
  assert(this.resolvedUrl !== undefined && this.resolvedUrl.type === "fluid", 0x0d2 /* "resolved url should be valid Fluid url" */);
624
625
  assert(!!this.baseSnapshot, 0x5d4 /* no base snapshot */);
625
626
  assert(!!this.baseSnapshotBlobs, 0x5d5 /* no snapshot blobs */);
626
627
  const pendingState = {
627
- pendingRuntimeState: this.context.getPendingLocalState(),
628
+ pendingRuntimeState: this.runtime.getPendingLocalState(),
628
629
  baseSnapshot: this.baseSnapshot,
629
630
  snapshotBlobs: this.baseSnapshotBlobs,
630
631
  savedOps: this.savedOps,
@@ -640,7 +641,7 @@ export class Container extends EventEmitterWithErrorHandling {
640
641
  }
641
642
  serialize() {
642
643
  assert(this.attachState === AttachState.Detached, 0x0d3 /* "Should only be called in detached container" */);
643
- const appSummary = this.context.createSummary();
644
+ const appSummary = this.runtime.createSummary();
644
645
  const protocolSummary = this.captureProtocolSummary();
645
646
  const combinedSummary = combineAppAndProtocolSummary(appSummary, protocolSummary);
646
647
  if (this.detachedBlobStorage && this.detachedBlobStorage.size > 0) {
@@ -669,7 +670,7 @@ export class Container extends EventEmitterWithErrorHandling {
669
670
  if (!hasAttachmentBlobs) {
670
671
  // Get the document state post attach - possibly can just call attach but we need to change the
671
672
  // semantics around what the attach means as far as async code goes.
672
- const appSummary = this.context.createSummary();
673
+ const appSummary = this.runtime.createSummary();
673
674
  const protocolSummary = this.captureProtocolSummary();
674
675
  summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
675
676
  // Set the state as attaching as we are starting the process of attaching container.
@@ -677,6 +678,7 @@ export class Container extends EventEmitterWithErrorHandling {
677
678
  // starting to attach the container to storage.
678
679
  // Also, this should only be fired in detached container.
679
680
  this._attachState = AttachState.Attaching;
681
+ this.runtime.setAttachState(AttachState.Attaching);
680
682
  this.emit("attaching");
681
683
  if (this.offlineLoadEnabled) {
682
684
  const snapshot = getSnapshotTreeFromSerializedContainer(summary);
@@ -691,7 +693,7 @@ export class Container extends EventEmitterWithErrorHandling {
691
693
  assert(this.client.details.type !== summarizerClientType &&
692
694
  createNewResolvedUrl !== undefined, 0x2c4 /* "client should not be summarizer before container is created" */);
693
695
  this.service = await runWithRetry(async () => this.serviceFactory.createContainer(summary, createNewResolvedUrl, this.subLogger, false), "containerAttach", this.mc.logger, {
694
- cancel: this.closeSignal,
696
+ cancel: this._deltaManager.closeAbortController.signal,
695
697
  });
696
698
  }
697
699
  await this.storageAdapter.connectToService(this.service);
@@ -713,10 +715,11 @@ export class Container extends EventEmitterWithErrorHandling {
713
715
  }
714
716
  }
715
717
  // take summary and upload
716
- const appSummary = this.context.createSummary(redirectTable);
718
+ const appSummary = this.runtime.createSummary(redirectTable);
717
719
  const protocolSummary = this.captureProtocolSummary();
718
720
  summary = combineAppAndProtocolSummary(appSummary, protocolSummary);
719
721
  this._attachState = AttachState.Attaching;
722
+ this.runtime.setAttachState(AttachState.Attaching);
720
723
  this.emit("attaching");
721
724
  if (this.offlineLoadEnabled) {
722
725
  const snapshot = getSnapshotTreeFromSerializedContainer(summary);
@@ -731,6 +734,7 @@ export class Container extends EventEmitterWithErrorHandling {
731
734
  });
732
735
  }
733
736
  this._attachState = AttachState.Attached;
737
+ this.runtime.setAttachState(AttachState.Attached);
734
738
  this.emit("attached");
735
739
  if (!this.closed) {
736
740
  this.resumeInternal({
@@ -749,7 +753,7 @@ export class Container extends EventEmitterWithErrorHandling {
749
753
  }, { start: true, end: true, cancel: "generic" });
750
754
  }
751
755
  async request(path) {
752
- 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" });
753
757
  }
754
758
  setAutoReconnectInternal(mode) {
755
759
  const currentMode = this._deltaManager.connectionManager.reconnectMode;
@@ -815,13 +819,6 @@ export class Container extends EventEmitterWithErrorHandling {
815
819
  // Ensure connection to web socket
816
820
  this.connectToDeltaStream(args);
817
821
  }
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
822
  async proposeCodeDetails(codeDetails) {
826
823
  if (!isFluidCodeDetails(codeDetails)) {
827
824
  throw new Error("Provided codeDetails are not IFluidCodeDetails");
@@ -843,7 +840,7 @@ export class Container extends EventEmitterWithErrorHandling {
843
840
  this.deltaManager.inbound.pause(),
844
841
  this.deltaManager.inboundSignal.pause(),
845
842
  ]);
846
- if ((await this.context.satisfies(codeDetails)) === true) {
843
+ if ((await this.satisfies(codeDetails)) === true) {
847
844
  this.deltaManager.inbound.resume();
848
845
  this.deltaManager.inboundSignal.resume();
849
846
  return;
@@ -852,6 +849,38 @@ export class Container extends EventEmitterWithErrorHandling {
852
849
  const error = new GenericError("Existing context does not satisfy incoming proposal");
853
850
  this.close(error);
854
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
+ }
855
884
  async getVersion(version) {
856
885
  const versions = await this.storageAdapter.getVersions(version, 1);
857
886
  return versions[0];
@@ -869,7 +898,7 @@ export class Container extends EventEmitterWithErrorHandling {
869
898
  * @param specifiedVersion - Version SHA to load snapshot. If not specified, will fetch the latest snapshot.
870
899
  */
871
900
  async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState) {
872
- var _a;
901
+ var _a, _b, _c;
873
902
  this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
874
903
  // Ideally we always connect as "read" by default.
875
904
  // Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
@@ -913,7 +942,7 @@ export class Container extends EventEmitterWithErrorHandling {
913
942
  if (this.offlineLoadEnabled) {
914
943
  this.baseSnapshot = snapshot;
915
944
  // Save contents of snapshot now, otherwise closeAndGetPendingLocalState() must be async
916
- this.baseSnapshotBlobs = await getBlobContentsFromTree(snapshot, this.storage);
945
+ this.baseSnapshotBlobs = await getBlobContentsFromTree(snapshot, this.storageAdapter);
917
946
  }
918
947
  }
919
948
  const attributes = await this.getDocumentAttributes(this.storageAdapter, snapshot);
@@ -949,7 +978,7 @@ export class Container extends EventEmitterWithErrorHandling {
949
978
  for (const message of pendingLocalState.savedOps) {
950
979
  this.processRemoteMessage(message);
951
980
  // allow runtime to apply stashed ops at this op's sequence number
952
- await this.context.notifyOpReplay(message);
981
+ await ((_c = (_b = this.runtime).notifyOpReplay) === null || _c === void 0 ? void 0 : _c.call(_b, message));
953
982
  }
954
983
  pendingLocalState.savedOps = [];
955
984
  // now set clientId to stashed clientId so live ops are correctly processed as local
@@ -1188,7 +1217,7 @@ export class Container extends EventEmitterWithErrorHandling {
1188
1217
  });
1189
1218
  deltaManager.on("disconnect", (reason, error) => {
1190
1219
  var _a;
1191
- (_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
1220
+ (_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyDisconnect();
1192
1221
  if (!this.closed) {
1193
1222
  this.connectionStateHandler.receivedDisconnectEvent(reason, error);
1194
1223
  }
@@ -1245,7 +1274,9 @@ export class Container extends EventEmitterWithErrorHandling {
1245
1274
  else if (value === ConnectionState.CatchingUp) {
1246
1275
  // This info is of most interesting while Catching Up.
1247
1276
  checkpointSequenceNumber = this.deltaManager.lastKnownSeqNumber;
1248
- 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") {
1249
1280
  opsBehind = checkpointSequenceNumber - this.deltaManager.lastSequenceNumber;
1250
1281
  }
1251
1282
  }
@@ -1332,7 +1363,7 @@ export class Container extends EventEmitterWithErrorHandling {
1332
1363
  return -1;
1333
1364
  }
1334
1365
  this.messageCountAfterDisconnection += 1;
1335
- (_a = this.collabWindowTracker) === null || _a === void 0 ? void 0 : _a.stopSequenceNumberUpdate();
1366
+ (_a = this.noopHeuristic) === null || _a === void 0 ? void 0 : _a.notifyMessageSent();
1336
1367
  return this._deltaManager.submit(type, contents, batch, metadata, compression, referenceSequenceNumber);
1337
1368
  }
1338
1369
  processRemoteMessage(message) {
@@ -1340,24 +1371,51 @@ export class Container extends EventEmitterWithErrorHandling {
1340
1371
  this.savedOps.push(message);
1341
1372
  }
1342
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
+ }
1343
1391
  // Allow the protocol handler to process the message
1344
1392
  const result = this.protocolHandler.processMessage(message, local);
1345
1393
  // Forward messages to the loaded runtime for processing
1346
- this.context.process(message, local);
1394
+ this.runtime.process(message, local);
1347
1395
  // Inactive (not in quorum or not writers) clients don't take part in the minimum sequence number calculation.
1348
1396
  if (this.activeConnection()) {
1349
- if (this.collabWindowTracker === undefined) {
1397
+ if (this.noopHeuristic === undefined) {
1398
+ const serviceConfiguration = this.deltaManager.serviceConfiguration;
1350
1399
  // Note that config from first connection will be used for this container's lifetime.
1351
1400
  // That means that if relay service changes settings, such changes will impact only newly booted
1352
1401
  // clients.
1353
1402
  // 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);
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);
1359
1418
  }
1360
- this.collabWindowTracker.scheduleSequenceNumberUpdate(message, result.immediateNoOp === true);
1361
1419
  }
1362
1420
  this.emit("op", message);
1363
1421
  }
@@ -1366,12 +1424,12 @@ export class Container extends EventEmitterWithErrorHandling {
1366
1424
  }
1367
1425
  processSignal(message) {
1368
1426
  // No clientId indicates a system signal message.
1369
- if (message.clientId === null) {
1427
+ if (protocolHandlerShouldProcessSignal(message)) {
1370
1428
  this.protocolHandler.processSignal(message);
1371
1429
  }
1372
1430
  else {
1373
1431
  const local = this.clientId === message.clientId;
1374
- this.context.processSignal(message, local);
1432
+ this.runtime.processSignal(message, local);
1375
1433
  }
1376
1434
  }
1377
1435
  /**
@@ -1404,21 +1462,37 @@ export class Container extends EventEmitterWithErrorHandling {
1404
1462
  await this.instantiateContext(existing, codeDetails, snapshot);
1405
1463
  }
1406
1464
  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" */);
1465
+ var _a, _b;
1466
+ assert(((_a = this._runtime) === null || _a === void 0 ? void 0 : _a.disposed) !== false, 0x0dd /* "Existing runtime not disposed" */);
1409
1467
  // The relative loader will proxy requests to '/' to the loader itself assuming no non-cache flags
1410
1468
  // are set. Global requests will still go directly to the loader
1411
1469
  const maybeLoader = this.scope;
1412
1470
  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;
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);
1419
1483
  }
1420
- this._dirtyContainer = dirty;
1421
- 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);
1422
1496
  }
1423
1497
  /**
1424
1498
  * Set the connected state of the ContainerContext
@@ -1428,16 +1502,15 @@ export class Container extends EventEmitterWithErrorHandling {
1428
1502
  */
1429
1503
  setContextConnectedState(state, readonly) {
1430
1504
  var _a;
1431
- 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) {
1432
1506
  /**
1433
1507
  * We want to lie to the ContainerRuntime when we are in readonly mode to prevent issues with pending
1434
1508
  * ops getting through to the DeltaManager.
1435
1509
  * The ContainerRuntime's "connected" state simply means it is ok to send ops
1436
1510
  * See https://dev.azure.com/fluidframework/internal/_workitems/edit/1246
1437
1511
  */
1438
- this.context.setConnectionState(state && !readonly, this.clientId);
1512
+ this.runtime.setConnectionState(state && !readonly, this.clientId);
1439
1513
  }
1440
1514
  }
1441
1515
  }
1442
- Container.version = "^0.1.0";
1443
1516
  //# sourceMappingURL=container.js.map