@fluidframework/container-loader 2.0.0-internal.6.4.0 → 2.0.0-internal.7.1.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 (126) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/api-extractor.json +9 -1
  3. package/api-report/container-loader.api.md +142 -0
  4. package/dist/catchUpMonitor.d.ts +2 -2
  5. package/dist/catchUpMonitor.d.ts.map +1 -1
  6. package/dist/connectionManager.d.ts +1 -0
  7. package/dist/connectionManager.d.ts.map +1 -1
  8. package/dist/connectionManager.js +109 -83
  9. package/dist/connectionManager.js.map +1 -1
  10. package/dist/connectionState.js +1 -1
  11. package/dist/connectionState.js.map +1 -1
  12. package/dist/connectionStateHandler.js +9 -9
  13. package/dist/connectionStateHandler.js.map +1 -1
  14. package/dist/container-loader-alpha.d.ts +297 -0
  15. package/dist/container-loader-beta.d.ts +297 -0
  16. package/dist/container-loader-public.d.ts +297 -0
  17. package/dist/container-loader.d.ts +297 -0
  18. package/dist/container.d.ts +6 -1
  19. package/dist/container.d.ts.map +1 -1
  20. package/dist/container.js +215 -215
  21. package/dist/container.js.map +1 -1
  22. package/dist/containerContext.js +16 -16
  23. package/dist/containerContext.js.map +1 -1
  24. package/dist/containerStorageAdapter.d.ts.map +1 -1
  25. package/dist/containerStorageAdapter.js +6 -8
  26. package/dist/containerStorageAdapter.js.map +1 -1
  27. package/dist/contracts.d.ts +5 -4
  28. package/dist/contracts.d.ts.map +1 -1
  29. package/dist/contracts.js +1 -1
  30. package/dist/contracts.js.map +1 -1
  31. package/dist/debugLogger.d.ts.map +1 -1
  32. package/dist/debugLogger.js +4 -4
  33. package/dist/debugLogger.js.map +1 -1
  34. package/dist/deltaManager.d.ts.map +1 -1
  35. package/dist/deltaManager.js +90 -89
  36. package/dist/deltaManager.js.map +1 -1
  37. package/dist/deltaQueue.js +14 -14
  38. package/dist/deltaQueue.js.map +1 -1
  39. package/dist/index.d.ts +1 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +4 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/loader.d.ts +3 -6
  44. package/dist/loader.d.ts.map +1 -1
  45. package/dist/loader.js +20 -85
  46. package/dist/loader.js.map +1 -1
  47. package/dist/location-redirection-utilities/index.d.ts +6 -0
  48. package/dist/location-redirection-utilities/index.d.ts.map +1 -0
  49. package/dist/location-redirection-utilities/index.js +11 -0
  50. package/dist/location-redirection-utilities/index.js.map +1 -0
  51. package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts +22 -0
  52. package/dist/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -0
  53. package/dist/location-redirection-utilities/resolveWithLocationRedirection.js +51 -0
  54. package/dist/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -0
  55. package/dist/packageVersion.d.ts +1 -1
  56. package/dist/packageVersion.js +1 -1
  57. package/dist/packageVersion.js.map +1 -1
  58. package/dist/protocol.d.ts +1 -2
  59. package/dist/protocol.d.ts.map +1 -1
  60. package/dist/protocol.js +3 -5
  61. package/dist/protocol.js.map +1 -1
  62. package/dist/tsdoc-metadata.json +1 -1
  63. package/lib/catchUpMonitor.d.ts +2 -2
  64. package/lib/catchUpMonitor.d.ts.map +1 -1
  65. package/lib/connectionManager.d.ts +1 -0
  66. package/lib/connectionManager.d.ts.map +1 -1
  67. package/lib/connectionManager.js +112 -84
  68. package/lib/connectionManager.js.map +1 -1
  69. package/lib/connectionStateHandler.js +9 -9
  70. package/lib/connectionStateHandler.js.map +1 -1
  71. package/lib/container.d.ts +6 -1
  72. package/lib/container.d.ts.map +1 -1
  73. package/lib/container.js +216 -216
  74. package/lib/container.js.map +1 -1
  75. package/lib/containerContext.js +16 -16
  76. package/lib/containerContext.js.map +1 -1
  77. package/lib/containerStorageAdapter.d.ts.map +1 -1
  78. package/lib/containerStorageAdapter.js +6 -8
  79. package/lib/containerStorageAdapter.js.map +1 -1
  80. package/lib/contracts.d.ts +5 -4
  81. package/lib/contracts.d.ts.map +1 -1
  82. package/lib/contracts.js.map +1 -1
  83. package/lib/debugLogger.d.ts.map +1 -1
  84. package/lib/debugLogger.js +4 -4
  85. package/lib/debugLogger.js.map +1 -1
  86. package/lib/deltaManager.d.ts.map +1 -1
  87. package/lib/deltaManager.js +90 -89
  88. package/lib/deltaManager.js.map +1 -1
  89. package/lib/deltaQueue.js +14 -14
  90. package/lib/deltaQueue.js.map +1 -1
  91. package/lib/index.d.ts +1 -0
  92. package/lib/index.d.ts.map +1 -1
  93. package/lib/index.js +1 -0
  94. package/lib/index.js.map +1 -1
  95. package/lib/loader.d.ts +3 -6
  96. package/lib/loader.d.ts.map +1 -1
  97. package/lib/loader.js +20 -85
  98. package/lib/loader.js.map +1 -1
  99. package/lib/location-redirection-utilities/index.d.ts +6 -0
  100. package/lib/location-redirection-utilities/index.d.ts.map +1 -0
  101. package/lib/location-redirection-utilities/index.js +6 -0
  102. package/lib/location-redirection-utilities/index.js.map +1 -0
  103. package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.ts +22 -0
  104. package/lib/location-redirection-utilities/resolveWithLocationRedirection.d.ts.map +1 -0
  105. package/lib/location-redirection-utilities/resolveWithLocationRedirection.js +46 -0
  106. package/lib/location-redirection-utilities/resolveWithLocationRedirection.js.map +1 -0
  107. package/lib/packageVersion.d.ts +1 -1
  108. package/lib/packageVersion.js +1 -1
  109. package/lib/packageVersion.js.map +1 -1
  110. package/lib/protocol.d.ts +1 -2
  111. package/lib/protocol.d.ts.map +1 -1
  112. package/lib/protocol.js +1 -3
  113. package/lib/protocol.js.map +1 -1
  114. package/package.json +23 -24
  115. package/src/connectionManager.ts +57 -16
  116. package/src/container.ts +15 -15
  117. package/src/containerStorageAdapter.ts +0 -6
  118. package/src/contracts.ts +8 -4
  119. package/src/debugLogger.ts +4 -1
  120. package/src/deltaManager.ts +11 -9
  121. package/src/index.ts +4 -0
  122. package/src/loader.ts +24 -92
  123. package/src/location-redirection-utilities/index.ts +9 -0
  124. package/src/location-redirection-utilities/resolveWithLocationRedirection.ts +59 -0
  125. package/src/packageVersion.ts +1 -1
  126. package/src/protocol.ts +2 -6
package/dist/container.js CHANGED
@@ -129,208 +129,6 @@ async function ReportIfTooLong(logger, eventName, action) {
129
129
  exports.ReportIfTooLong = ReportIfTooLong;
130
130
  const summarizerClientType = "summarizer";
131
131
  class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
132
- /**
133
- * @internal
134
- */
135
- constructor(createProps, loadProps) {
136
- super((name, error) => {
137
- this.mc.logger.sendErrorEvent({
138
- eventName: "ContainerEventHandlerException",
139
- name: typeof name === "string" ? name : undefined,
140
- }, error);
141
- });
142
- /**
143
- * Lifecycle state of the container, used mainly to prevent re-entrancy and telemetry
144
- *
145
- * States are allowed to progress to further states:
146
- * "loading" - "loaded" - "closing" - "disposing" - "closed" - "disposed"
147
- *
148
- * For example, moving from "closed" to "disposing" is not allowed since it is an earlier state.
149
- *
150
- * loading: Container has been created, but is not yet in normal/loaded state
151
- * loaded: Container is in normal/loaded state
152
- * closing: Container has started closing process (for re-entrancy prevention)
153
- * disposing: Container has started disposing process (for re-entrancy prevention)
154
- * closed: Container has closed
155
- * disposed: Container has been disposed
156
- */
157
- this._lifecycleState = "loading";
158
- this._attachState = container_definitions_1.AttachState.Detached;
159
- /** During initialization we pause the inbound queues. We track this state to ensure we only call resume once */
160
- this.inboundQueuePausedFromInit = true;
161
- this.firstConnection = true;
162
- this.connectionTransitionTimes = [];
163
- this.attachStarted = false;
164
- this._dirtyContainer = false;
165
- this.savedOps = [];
166
- this.clientsWhoShouldHaveLeft = new Set();
167
- this.setAutoReconnectTime = client_utils_1.performance.now();
168
- this._lifecycleEvents = new client_utils_1.TypedEventEmitter();
169
- this._disposed = false;
170
- this.getAbsoluteUrl = async (relativeUrl) => {
171
- if (this.resolvedUrl === undefined) {
172
- return undefined;
173
- }
174
- return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, (0, contracts_1.getPackageName)(this._loadedCodeDetails));
175
- };
176
- this.updateDirtyContainerState = (dirty) => {
177
- if (this._dirtyContainer === dirty) {
178
- return;
179
- }
180
- this._dirtyContainer = dirty;
181
- this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
182
- };
183
- const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
184
- this.connectionTransitionTimes[connectionState_1.ConnectionState.Disconnected] = client_utils_1.performance.now();
185
- const pendingLocalState = loadProps?.pendingLocalState;
186
- this._clientId = pendingLocalState?.clientId;
187
- this._canReconnect = canReconnect ?? true;
188
- this.clientDetailsOverride = clientDetailsOverride;
189
- this.urlResolver = urlResolver;
190
- this.serviceFactory = documentServiceFactory;
191
- this.codeLoader = codeLoader;
192
- // Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
193
- // all clients that were loaded from the same loader (including summarizer clients).
194
- // Tracking alternative ways to handle this in AB#4129.
195
- this.options = { ...options };
196
- this.scope = scope;
197
- this.detachedBlobStorage = detachedBlobStorage;
198
- this.protocolHandlerBuilder =
199
- protocolHandlerBuilder ??
200
- ((attributes, quorumSnapshot, sendProposal) => new protocol_1.ProtocolHandler(attributes, quorumSnapshot, sendProposal, new audience_1.Audience(), (clientId) => this.clientsWhoShouldHaveLeft.has(clientId)));
201
- // Note that we capture the createProps here so we can replicate the creation call when we want to clone.
202
- this.clone = async (_loadProps, createParamOverrides) => {
203
- return Container.load(_loadProps, {
204
- ...createProps,
205
- ...createParamOverrides,
206
- });
207
- };
208
- // Create logger for data stores to use
209
- const type = this.client.details.type;
210
- const interactive = this.client.details.capabilities.interactive;
211
- const clientType = `${interactive ? "interactive" : "noninteractive"}${type !== undefined && type !== "" ? `/${type}` : ""}`;
212
- // Need to use the property getter for docId because for detached flow we don't have the docId initially.
213
- // We assign the id later so property getter is used.
214
- this.subLogger = (0, telemetry_utils_1.createChildLogger)({
215
- logger: subLogger,
216
- properties: {
217
- all: {
218
- clientType,
219
- containerId: (0, uuid_1.v4)(),
220
- docId: () => this.resolvedUrl?.id,
221
- containerAttachState: () => this._attachState,
222
- containerLifecycleState: () => this._lifecycleState,
223
- containerConnectionState: () => connectionState_1.ConnectionState[this.connectionState],
224
- serializedContainer: pendingLocalState !== undefined,
225
- },
226
- // we need to be judicious with our logging here to avoid generating too much data
227
- // all data logged here should be broadly applicable, and not specific to a
228
- // specific error or class of errors
229
- error: {
230
- // load information to associate errors with the specific load point
231
- dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
232
- dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
233
- dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
234
- containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
235
- containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
236
- // message information to associate errors with the specific execution state
237
- // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
238
- dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
239
- dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
240
- dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId === null
241
- ? "null"
242
- : this.deltaManager?.lastMessage?.clientId,
243
- dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
244
- connectionStateDuration: () => client_utils_1.performance.now() - this.connectionTransitionTimes[this.connectionState],
245
- },
246
- },
247
- });
248
- // Prefix all events in this file with container-loader
249
- this.mc = (0, telemetry_utils_1.createChildMonitoringContext)({ logger: this.subLogger, namespace: "Container" });
250
- this._deltaManager = this.createDeltaManager();
251
- this.connectionStateHandler = (0, connectionStateHandler_1.createConnectionStateHandler)({
252
- logger: this.mc.logger,
253
- connectionStateChanged: (value, oldState, reason) => {
254
- if (value === connectionState_1.ConnectionState.Connected) {
255
- this._clientId = this.connectionStateHandler.pendingClientId;
256
- }
257
- this.logConnectionStateChangeTelemetry(value, oldState, reason);
258
- if (this._lifecycleState === "loaded") {
259
- this.propagateConnectionState(false /* initial transition */, value === connectionState_1.ConnectionState.Disconnected
260
- ? reason
261
- : undefined /* disconnectedReason */);
262
- }
263
- },
264
- shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
265
- maxClientLeaveWaitTime: options.maxClientLeaveWaitTime,
266
- logConnectionIssue: (eventName, category, details) => {
267
- const mode = this.connectionMode;
268
- // We get here when socket does not receive any ops on "write" connection, including
269
- // its own join op.
270
- // Report issues only if we already loaded container - op processing is paused while container is loading,
271
- // so we always time-out processing of join op in cases where fetching snapshot takes a minute.
272
- // It's not a problem with op processing itself - such issues should be tracked as part of boot perf monitoring instead.
273
- this._deltaManager.logConnectionIssue({
274
- eventName,
275
- mode,
276
- category: this._lifecycleState === "loading" ? "generic" : category,
277
- duration: client_utils_1.performance.now() -
278
- this.connectionTransitionTimes[connectionState_1.ConnectionState.CatchingUp],
279
- ...(details === undefined ? {} : { details: JSON.stringify(details) }),
280
- });
281
- // If this is "write" connection, it took too long to receive join op. But in most cases that's due
282
- // to very slow op fetches and we will eventually get there.
283
- // For "read" connections, we get here due to self join signal not arriving on time. We will need to
284
- // better understand when and why it may happen.
285
- // For now, attempt to recover by reconnecting. In future, maybe we can query relay service for
286
- // current state of audience.
287
- // Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
288
- // to call this.applyForConnectedState("addMemberEvent") for "read" connections)
289
- if (mode === "read") {
290
- const reason = { text: "NoJoinSignal" };
291
- this.disconnectInternal(reason);
292
- this.connectInternal({ reason, fetchOpsFromStorage: false });
293
- }
294
- },
295
- clientShouldHaveLeft: (clientId) => {
296
- this.clientsWhoShouldHaveLeft.add(clientId);
297
- },
298
- }, this.deltaManager, pendingLocalState?.clientId);
299
- this.on(savedContainerEvent, () => {
300
- this.connectionStateHandler.containerSaved();
301
- });
302
- // We expose our storage publicly, so it's possible others may call uploadSummaryWithContext() with a
303
- // non-combined summary tree (in particular, ContainerRuntime.submitSummary). We'll intercept those calls
304
- // using this callback and fix them up.
305
- const addProtocolSummaryIfMissing = (summaryTree) => (0, driver_utils_1.isCombinedAppAndProtocolSummary)(summaryTree) === true
306
- ? summaryTree
307
- : (0, utils_1.combineAppAndProtocolSummary)(summaryTree, this.captureProtocolSummary());
308
- // Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
309
- // Even if not forced on via this flag, combined summaries may still be enabled by service policy.
310
- const forceEnableSummarizeProtocolTree = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
311
- options.summarizeProtocolTree;
312
- this.storageAdapter = new containerStorageAdapter_1.ContainerStorageAdapter(detachedBlobStorage, this.mc.logger, pendingLocalState?.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
313
- const isDomAvailable = typeof document === "object" &&
314
- document !== null &&
315
- typeof document.addEventListener === "function" &&
316
- document.addEventListener !== null;
317
- // keep track of last time page was visible for telemetry (on interactive clients only)
318
- if (isDomAvailable && interactive) {
319
- this.lastVisible = document.hidden ? client_utils_1.performance.now() : undefined;
320
- this.visibilityEventHandler = () => {
321
- if (document.hidden) {
322
- this.lastVisible = client_utils_1.performance.now();
323
- }
324
- else {
325
- // settimeout so this will hopefully fire after disconnect event if being hidden caused it
326
- setTimeout(() => {
327
- this.lastVisible = undefined;
328
- }, 0);
329
- }
330
- };
331
- document.addEventListener("visibilitychange", this.visibilityEventHandler);
332
- }
333
- }
334
132
  /**
335
133
  * Load an existing container.
336
134
  * @internal
@@ -425,6 +223,10 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
425
223
  get connectionMode() {
426
224
  return this._deltaManager.connectionManager.connectionMode;
427
225
  }
226
+ /**
227
+ * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md
228
+ */
229
+ // eslint-disable-next-line import/no-deprecated
428
230
  get IFluidRouter() {
429
231
  return this;
430
232
  }
@@ -540,6 +342,208 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
540
342
  this._lifecycleEvents.once("disposed", disposedHandler);
541
343
  });
542
344
  }
345
+ /**
346
+ * @internal
347
+ */
348
+ constructor(createProps, loadProps) {
349
+ super((name, error) => {
350
+ this.mc.logger.sendErrorEvent({
351
+ eventName: "ContainerEventHandlerException",
352
+ name: typeof name === "string" ? name : undefined,
353
+ }, error);
354
+ });
355
+ /**
356
+ * Lifecycle state of the container, used mainly to prevent re-entrancy and telemetry
357
+ *
358
+ * States are allowed to progress to further states:
359
+ * "loading" - "loaded" - "closing" - "disposing" - "closed" - "disposed"
360
+ *
361
+ * For example, moving from "closed" to "disposing" is not allowed since it is an earlier state.
362
+ *
363
+ * loading: Container has been created, but is not yet in normal/loaded state
364
+ * loaded: Container is in normal/loaded state
365
+ * closing: Container has started closing process (for re-entrancy prevention)
366
+ * disposing: Container has started disposing process (for re-entrancy prevention)
367
+ * closed: Container has closed
368
+ * disposed: Container has been disposed
369
+ */
370
+ this._lifecycleState = "loading";
371
+ this._attachState = container_definitions_1.AttachState.Detached;
372
+ /** During initialization we pause the inbound queues. We track this state to ensure we only call resume once */
373
+ this.inboundQueuePausedFromInit = true;
374
+ this.firstConnection = true;
375
+ this.connectionTransitionTimes = [];
376
+ this.attachStarted = false;
377
+ this._dirtyContainer = false;
378
+ this.savedOps = [];
379
+ this.clientsWhoShouldHaveLeft = new Set();
380
+ this.setAutoReconnectTime = client_utils_1.performance.now();
381
+ this._lifecycleEvents = new client_utils_1.TypedEventEmitter();
382
+ this._disposed = false;
383
+ this.getAbsoluteUrl = async (relativeUrl) => {
384
+ if (this.resolvedUrl === undefined) {
385
+ return undefined;
386
+ }
387
+ return this.urlResolver.getAbsoluteUrl(this.resolvedUrl, relativeUrl, (0, contracts_1.getPackageName)(this._loadedCodeDetails));
388
+ };
389
+ this.updateDirtyContainerState = (dirty) => {
390
+ if (this._dirtyContainer === dirty) {
391
+ return;
392
+ }
393
+ this._dirtyContainer = dirty;
394
+ this.emit(dirty ? dirtyContainerEvent : savedContainerEvent);
395
+ };
396
+ const { canReconnect, clientDetailsOverride, urlResolver, documentServiceFactory, codeLoader, options, scope, subLogger, detachedBlobStorage, protocolHandlerBuilder, } = createProps;
397
+ this.connectionTransitionTimes[connectionState_1.ConnectionState.Disconnected] = client_utils_1.performance.now();
398
+ const pendingLocalState = loadProps?.pendingLocalState;
399
+ this._clientId = pendingLocalState?.clientId;
400
+ this._canReconnect = canReconnect ?? true;
401
+ this.clientDetailsOverride = clientDetailsOverride;
402
+ this.urlResolver = urlResolver;
403
+ this.serviceFactory = documentServiceFactory;
404
+ this.codeLoader = codeLoader;
405
+ // Warning: this is only a shallow clone. Mutation of any individual loader option will mutate it for
406
+ // all clients that were loaded from the same loader (including summarizer clients).
407
+ // Tracking alternative ways to handle this in AB#4129.
408
+ this.options = { ...options };
409
+ this.scope = scope;
410
+ this.detachedBlobStorage = detachedBlobStorage;
411
+ this.protocolHandlerBuilder =
412
+ protocolHandlerBuilder ??
413
+ ((attributes, quorumSnapshot, sendProposal) => new protocol_1.ProtocolHandler(attributes, quorumSnapshot, sendProposal, new audience_1.Audience(), (clientId) => this.clientsWhoShouldHaveLeft.has(clientId)));
414
+ // Note that we capture the createProps here so we can replicate the creation call when we want to clone.
415
+ this.clone = async (_loadProps, createParamOverrides) => {
416
+ return Container.load(_loadProps, {
417
+ ...createProps,
418
+ ...createParamOverrides,
419
+ });
420
+ };
421
+ // Create logger for data stores to use
422
+ const type = this.client.details.type;
423
+ const interactive = this.client.details.capabilities.interactive;
424
+ const clientType = `${interactive ? "interactive" : "noninteractive"}${type !== undefined && type !== "" ? `/${type}` : ""}`;
425
+ // Need to use the property getter for docId because for detached flow we don't have the docId initially.
426
+ // We assign the id later so property getter is used.
427
+ this.subLogger = (0, telemetry_utils_1.createChildLogger)({
428
+ logger: subLogger,
429
+ properties: {
430
+ all: {
431
+ clientType,
432
+ containerId: (0, uuid_1.v4)(),
433
+ docId: () => this.resolvedUrl?.id,
434
+ containerAttachState: () => this._attachState,
435
+ containerLifecycleState: () => this._lifecycleState,
436
+ containerConnectionState: () => connectionState_1.ConnectionState[this.connectionState],
437
+ serializedContainer: pendingLocalState !== undefined,
438
+ },
439
+ // we need to be judicious with our logging here to avoid generating too much data
440
+ // all data logged here should be broadly applicable, and not specific to a
441
+ // specific error or class of errors
442
+ error: {
443
+ // load information to associate errors with the specific load point
444
+ dmInitialSeqNumber: () => this._deltaManager?.initialSequenceNumber,
445
+ dmLastProcessedSeqNumber: () => this._deltaManager?.lastSequenceNumber,
446
+ dmLastKnownSeqNumber: () => this._deltaManager?.lastKnownSeqNumber,
447
+ containerLoadedFromVersionId: () => this._loadedFromVersion?.id,
448
+ containerLoadedFromVersionDate: () => this._loadedFromVersion?.date,
449
+ // message information to associate errors with the specific execution state
450
+ // dmLastMsqSeqNumber: if present, same as dmLastProcessedSeqNumber
451
+ dmLastMsqSeqNumber: () => this.deltaManager?.lastMessage?.sequenceNumber,
452
+ dmLastMsqSeqTimestamp: () => this.deltaManager?.lastMessage?.timestamp,
453
+ dmLastMsqSeqClientId: () => this.deltaManager?.lastMessage?.clientId === null
454
+ ? "null"
455
+ : this.deltaManager?.lastMessage?.clientId,
456
+ dmLastMsgClientSeq: () => this.deltaManager?.lastMessage?.clientSequenceNumber,
457
+ connectionStateDuration: () => client_utils_1.performance.now() - this.connectionTransitionTimes[this.connectionState],
458
+ },
459
+ },
460
+ });
461
+ // Prefix all events in this file with container-loader
462
+ this.mc = (0, telemetry_utils_1.createChildMonitoringContext)({ logger: this.subLogger, namespace: "Container" });
463
+ this._deltaManager = this.createDeltaManager();
464
+ this.connectionStateHandler = (0, connectionStateHandler_1.createConnectionStateHandler)({
465
+ logger: this.mc.logger,
466
+ connectionStateChanged: (value, oldState, reason) => {
467
+ if (value === connectionState_1.ConnectionState.Connected) {
468
+ this._clientId = this.connectionStateHandler.pendingClientId;
469
+ }
470
+ this.logConnectionStateChangeTelemetry(value, oldState, reason);
471
+ if (this._lifecycleState === "loaded") {
472
+ this.propagateConnectionState(false /* initial transition */, value === connectionState_1.ConnectionState.Disconnected
473
+ ? reason
474
+ : undefined /* disconnectedReason */);
475
+ }
476
+ },
477
+ shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
478
+ maxClientLeaveWaitTime: options.maxClientLeaveWaitTime,
479
+ logConnectionIssue: (eventName, category, details) => {
480
+ const mode = this.connectionMode;
481
+ // We get here when socket does not receive any ops on "write" connection, including
482
+ // its own join op.
483
+ // Report issues only if we already loaded container - op processing is paused while container is loading,
484
+ // so we always time-out processing of join op in cases where fetching snapshot takes a minute.
485
+ // It's not a problem with op processing itself - such issues should be tracked as part of boot perf monitoring instead.
486
+ this._deltaManager.logConnectionIssue({
487
+ eventName,
488
+ mode,
489
+ category: this._lifecycleState === "loading" ? "generic" : category,
490
+ duration: client_utils_1.performance.now() -
491
+ this.connectionTransitionTimes[connectionState_1.ConnectionState.CatchingUp],
492
+ ...(details === undefined ? {} : { details: JSON.stringify(details) }),
493
+ });
494
+ // If this is "write" connection, it took too long to receive join op. But in most cases that's due
495
+ // to very slow op fetches and we will eventually get there.
496
+ // For "read" connections, we get here due to self join signal not arriving on time. We will need to
497
+ // better understand when and why it may happen.
498
+ // For now, attempt to recover by reconnecting. In future, maybe we can query relay service for
499
+ // current state of audience.
500
+ // Other possible recovery path - move to connected state (i.e. ConnectionStateHandler.joinOpTimer
501
+ // to call this.applyForConnectedState("addMemberEvent") for "read" connections)
502
+ if (mode === "read") {
503
+ const reason = { text: "NoJoinSignal" };
504
+ this.disconnectInternal(reason);
505
+ this.connectInternal({ reason, fetchOpsFromStorage: false });
506
+ }
507
+ },
508
+ clientShouldHaveLeft: (clientId) => {
509
+ this.clientsWhoShouldHaveLeft.add(clientId);
510
+ },
511
+ }, this.deltaManager, pendingLocalState?.clientId);
512
+ this.on(savedContainerEvent, () => {
513
+ this.connectionStateHandler.containerSaved();
514
+ });
515
+ // We expose our storage publicly, so it's possible others may call uploadSummaryWithContext() with a
516
+ // non-combined summary tree (in particular, ContainerRuntime.submitSummary). We'll intercept those calls
517
+ // using this callback and fix them up.
518
+ const addProtocolSummaryIfMissing = (summaryTree) => (0, driver_utils_1.isCombinedAppAndProtocolSummary)(summaryTree) === true
519
+ ? summaryTree
520
+ : (0, utils_1.combineAppAndProtocolSummary)(summaryTree, this.captureProtocolSummary());
521
+ // Whether the combined summary tree has been forced on by either the loader option or the monitoring context.
522
+ // Even if not forced on via this flag, combined summaries may still be enabled by service policy.
523
+ const forceEnableSummarizeProtocolTree = this.mc.config.getBoolean("Fluid.Container.summarizeProtocolTree2") ??
524
+ options.summarizeProtocolTree;
525
+ this.storageAdapter = new containerStorageAdapter_1.ContainerStorageAdapter(detachedBlobStorage, this.mc.logger, pendingLocalState?.snapshotBlobs, addProtocolSummaryIfMissing, forceEnableSummarizeProtocolTree);
526
+ const isDomAvailable = typeof document === "object" &&
527
+ document !== null &&
528
+ typeof document.addEventListener === "function" &&
529
+ document.addEventListener !== null;
530
+ // keep track of last time page was visible for telemetry (on interactive clients only)
531
+ if (isDomAvailable && interactive) {
532
+ this.lastVisible = document.hidden ? client_utils_1.performance.now() : undefined;
533
+ this.visibilityEventHandler = () => {
534
+ if (document.hidden) {
535
+ this.lastVisible = client_utils_1.performance.now();
536
+ }
537
+ else {
538
+ // settimeout so this will hopefully fire after disconnect event if being hidden caused it
539
+ setTimeout(() => {
540
+ this.lastVisible = undefined;
541
+ }, 0);
542
+ }
543
+ };
544
+ document.addEventListener("visibilitychange", this.visibilityEventHandler);
545
+ }
546
+ }
543
547
  /**
544
548
  * Retrieves the quorum associated with the document
545
549
  */
@@ -674,7 +678,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
674
678
  snapshotBlobs: this.baseSnapshotBlobs,
675
679
  savedOps: this.savedOps,
676
680
  url: this.resolvedUrl.url,
677
- term: protocol_1.OnlyValidTermValue,
678
681
  // no need to save this if there is no pending runtime state
679
682
  clientId: pendingRuntimeState !== undefined ? this.clientId : undefined,
680
683
  };
@@ -796,6 +799,9 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
796
799
  }
797
800
  }, { start: true, end: true, cancel: "generic" });
798
801
  }
802
+ /**
803
+ * @deprecated Will be removed in future major release. Migrate all usage of IFluidRouter to the "entryPoint" pattern. Refer to Removing-IFluidRouter.md
804
+ */
799
805
  async request(path) {
800
806
  return telemetry_utils_1.PerformanceEvent.timedExecAsync(this.mc.logger, { eventName: "Request" }, async () => this.runtime.request(path), { end: true, cancel: "error" });
801
807
  }
@@ -946,18 +952,14 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
946
952
  async load(specifiedVersion, loadMode, resolvedUrl, pendingLocalState, loadToSequenceNumber) {
947
953
  const timings = { phase1: client_utils_1.performance.now() };
948
954
  this.service = await this.serviceFactory.createDocumentService(resolvedUrl, this.subLogger, this.client.details.type === summarizerClientType);
949
- // Ideally we always connect as "read" by default.
950
- // Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
951
- // We should not rely on it by (one of them will address the issue, but we need to address both)
952
- // 1) switching create new flow to one where we create file by posting snapshot
953
- // 2) Fixing quorum workflows (have retry logic)
954
- // That all said, "read" does not work with memorylicious workflows (that opens two simultaneous
955
- // connections to same file) in two ways:
956
- // A) creation flow breaks (as one of the clients "sees" file as existing, and hits #2 above)
957
- // B) Once file is created, transition from view-only connection to write does not work - some bugs to be fixed.
955
+ // Except in cases where it has stashed ops or requested by feature gate, the container will connect in "read" mode
956
+ const mode = this.mc.config.getBoolean("Fluid.Container.ForceWriteConnection") === true ||
957
+ (pendingLocalState?.savedOps.length ?? 0) > 0
958
+ ? "write"
959
+ : "read";
958
960
  const connectionArgs = {
959
961
  reason: { text: "DocumentOpen" },
960
- mode: "write",
962
+ mode,
961
963
  fetchOpsFromStorage: false,
962
964
  };
963
965
  // Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
@@ -1122,7 +1124,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1122
1124
  async createDetached(codeDetails) {
1123
1125
  const attributes = {
1124
1126
  sequenceNumber: detachedContainerRefSeqNumber,
1125
- term: protocol_1.OnlyValidTermValue,
1126
1127
  minimumSequenceNumber: 0,
1127
1128
  };
1128
1129
  await this.attachDeltaManagerOpHandler(attributes);
@@ -1162,7 +1163,6 @@ class Container extends telemetry_utils_1.EventEmitterWithErrorHandling {
1162
1163
  return {
1163
1164
  minimumSequenceNumber: 0,
1164
1165
  sequenceNumber: 0,
1165
- term: protocol_1.OnlyValidTermValue,
1166
1166
  };
1167
1167
  }
1168
1168
  // Backward compatibility: old docs would have ".attributes" instead of "attributes"