@fluidframework/container-loader 0.53.0-46105 → 0.54.1

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 (59) hide show
  1. package/dist/connectionManager.d.ts +153 -0
  2. package/dist/connectionManager.d.ts.map +1 -0
  3. package/dist/connectionManager.js +664 -0
  4. package/dist/connectionManager.js.map +1 -0
  5. package/dist/container.d.ts +2 -1
  6. package/dist/container.d.ts.map +1 -1
  7. package/dist/container.js +25 -28
  8. package/dist/container.js.map +1 -1
  9. package/dist/contracts.d.ts +112 -0
  10. package/dist/contracts.d.ts.map +1 -0
  11. package/dist/contracts.js +14 -0
  12. package/dist/contracts.js.map +1 -0
  13. package/dist/deltaManager.d.ts +26 -135
  14. package/dist/deltaManager.d.ts.map +1 -1
  15. package/dist/deltaManager.js +142 -768
  16. package/dist/deltaManager.js.map +1 -1
  17. package/dist/loader.d.ts +9 -4
  18. package/dist/loader.d.ts.map +1 -1
  19. package/dist/loader.js +5 -4
  20. package/dist/loader.js.map +1 -1
  21. package/dist/packageVersion.d.ts +1 -1
  22. package/dist/packageVersion.d.ts.map +1 -1
  23. package/dist/packageVersion.js +1 -1
  24. package/dist/packageVersion.js.map +1 -1
  25. package/dist/protocolTreeDocumentStorageService.d.ts +2 -2
  26. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  27. package/lib/connectionManager.d.ts +153 -0
  28. package/lib/connectionManager.d.ts.map +1 -0
  29. package/lib/connectionManager.js +660 -0
  30. package/lib/connectionManager.js.map +1 -0
  31. package/lib/container.d.ts +2 -1
  32. package/lib/container.d.ts.map +1 -1
  33. package/lib/container.js +25 -28
  34. package/lib/container.js.map +1 -1
  35. package/lib/contracts.d.ts +112 -0
  36. package/lib/contracts.d.ts.map +1 -0
  37. package/lib/contracts.js +11 -0
  38. package/lib/contracts.js.map +1 -0
  39. package/lib/deltaManager.d.ts +26 -135
  40. package/lib/deltaManager.d.ts.map +1 -1
  41. package/lib/deltaManager.js +146 -772
  42. package/lib/deltaManager.js.map +1 -1
  43. package/lib/loader.d.ts +9 -4
  44. package/lib/loader.d.ts.map +1 -1
  45. package/lib/loader.js +6 -5
  46. package/lib/loader.js.map +1 -1
  47. package/lib/packageVersion.d.ts +1 -1
  48. package/lib/packageVersion.d.ts.map +1 -1
  49. package/lib/packageVersion.js +1 -1
  50. package/lib/packageVersion.js.map +1 -1
  51. package/lib/protocolTreeDocumentStorageService.d.ts +2 -2
  52. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  53. package/package.json +9 -9
  54. package/src/connectionManager.ts +892 -0
  55. package/src/container.ts +39 -39
  56. package/src/contracts.ts +156 -0
  57. package/src/deltaManager.ts +181 -979
  58. package/src/loader.ts +31 -9
  59. package/src/packageVersion.ts +1 -1
package/src/container.ts CHANGED
@@ -96,7 +96,8 @@ import {
96
96
  } from "@fluidframework/telemetry-utils";
97
97
  import { Audience } from "./audience";
98
98
  import { ContainerContext } from "./containerContext";
99
- import { IConnectionArgs, DeltaManager, ReconnectMode } from "./deltaManager";
99
+ import { ReconnectMode, IConnectionManagerFactoryArgs } from "./contracts";
100
+ import { DeltaManager, IConnectionArgs } from "./deltaManager";
100
101
  import { DeltaManagerProxy } from "./deltaManagerProxy";
101
102
  import { ILoaderOptions, Loader, RelativeLoader } from "./loader";
102
103
  import { pkgVersion } from "./packageVersion";
@@ -107,6 +108,7 @@ import { BlobOnlyStorage, ContainerStorageAdapter } from "./containerStorageAdap
107
108
  import { getSnapshotTreeFromSerializedContainer } from "./utils";
108
109
  import { QuorumProxy } from "./quorum";
109
110
  import { CollabWindowTracker } from "./collabWindowTracker";
111
+ import { ConnectionManager } from "./connectionManager";
110
112
 
111
113
  const detachedContainerRefSeqNumber = 0;
112
114
 
@@ -169,7 +171,7 @@ export enum ConnectionState {
169
171
  * false: storage does not provide indication of how far the client is. Container processed
170
172
  * all the ops known to it, but it maybe still behind.
171
173
  */
172
- export async function waitContainerToCatchUp(container: Container) {
174
+ export async function waitContainerToCatchUp(container: IContainer) {
173
175
  // Make sure we stop waiting if container is closed.
174
176
  if (container.closed) {
175
177
  throw new Error("Container is closed");
@@ -216,7 +218,9 @@ export async function waitContainerToCatchUp(container: Container) {
216
218
  };
217
219
  container.on(connectedEventName, callback);
218
220
 
219
- container.resume();
221
+ // TODO: Remove null check after next release #8523
222
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
223
+ container.resume!();
220
224
  });
221
225
  }
222
226
 
@@ -374,7 +378,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
374
378
  }
375
379
 
376
380
  private readonly clientDetailsOverride: IClientDetails | undefined;
377
- private readonly _deltaManager: DeltaManager;
381
+ private readonly _deltaManager: DeltaManager<ConnectionManager>;
378
382
  private service: IDocumentService | undefined;
379
383
  private readonly _audience: Audience;
380
384
 
@@ -415,6 +419,8 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
415
419
  this.loader.services.options?.noopCountFrequency,
416
420
  );
417
421
 
422
+ private get connectionMode() { return this._deltaManager.connectionManager.connectionMode; }
423
+
418
424
  public get IFluidRouter(): IFluidRouter { return this; }
419
425
 
420
426
  public get resolvedUrl(): IResolvedUrl | undefined {
@@ -433,7 +439,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
433
439
  * Tracks host requiring read-only mode.
434
440
  */
435
441
  public forceReadonly(readonly: boolean) {
436
- this._deltaManager.forceReadonly(readonly);
442
+ this._deltaManager.connectionManager.forceReadonly(readonly);
437
443
  }
438
444
 
439
445
  public get id(): string {
@@ -473,7 +479,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
473
479
  * Set once this.connected is true, otherwise undefined
474
480
  */
475
481
  public get scopes(): string[] | undefined {
476
- return this._deltaManager.scopes;
482
+ return this._deltaManager.connectionManager.scopes;
477
483
  }
478
484
 
479
485
  public get clientDetails(): IClientDetails {
@@ -595,7 +601,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
595
601
  protocolHandler: () => this._protocolHandler,
596
602
  logConnectionStateChangeTelemetry: (value, oldState, reason) =>
597
603
  this.logConnectionStateChangeTelemetry(value, oldState, reason),
598
- shouldClientJoinWrite: () => this._deltaManager.shouldJoinWrite(),
604
+ shouldClientJoinWrite: () => this._deltaManager.connectionManager.shouldJoinWrite(),
599
605
  maxClientLeaveWaitTime: this.loader.services.options.maxClientLeaveWaitTime,
600
606
  logConnectionIssue: (eventName: string) => {
601
607
  // We get here when socket does not receive any ops on "write" connection, including
@@ -915,7 +921,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
915
921
  throw new Error("Attempting to setAutoReconnect() a closed Container");
916
922
  }
917
923
  const mode = reconnect ? ReconnectMode.Enabled : ReconnectMode.Disabled;
918
- const currentMode = this._deltaManager.reconnectMode;
924
+ const currentMode = this._deltaManager.connectionManager.reconnectMode;
919
925
 
920
926
  if (currentMode === mode) {
921
927
  return;
@@ -927,12 +933,12 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
927
933
 
928
934
  this.logger.sendTelemetryEvent({
929
935
  eventName: reconnect ? "AutoReconnectEnabled" : "AutoReconnectDisabled",
930
- connectionMode: this._deltaManager.connectionMode,
936
+ connectionMode: this.connectionMode,
931
937
  connectionState: ConnectionState[this.connectionState],
932
938
  duration,
933
939
  });
934
940
 
935
- this._deltaManager.setAutoReconnect(mode);
941
+ this._deltaManager.connectionManager.setAutoReconnect(mode);
936
942
 
937
943
  // If container state is not attached and resumed, then don't connect to delta stream. Also don't set the
938
944
  // manual reconnection flag to true as we haven't made the initial connection yet.
@@ -943,13 +949,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
943
949
  }
944
950
 
945
951
  // Ensure connection to web socket
946
- this.connectToDeltaStream({ reason: "autoReconnect" }).catch((error) => {
947
- // All errors are reported through events ("error" / "disconnected") and telemetry in DeltaManager
948
- // So there shouldn't be a need to record error here.
949
- // But we have number of cases where reconnects do not happen, and no errors are recorded, so
950
- // adding this log point for easier diagnostics
951
- this.logger.sendTelemetryEvent({ eventName: "setAutoReconnectError" }, error);
952
- });
952
+ this.connectToDeltaStream({ reason: "autoReconnect" });
953
953
  }
954
954
  }
955
955
 
@@ -973,8 +973,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
973
973
  }
974
974
 
975
975
  // Ensure connection to web socket
976
- // All errors are reported through events ("error" / "disconnected") and telemetry in DeltaManager
977
- this.connectToDeltaStream(args).catch(() => { });
976
+ this.connectToDeltaStream(args);
978
977
  }
979
978
 
980
979
  /**
@@ -1129,7 +1128,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1129
1128
  }
1130
1129
  }
1131
1130
 
1132
- private async connectToDeltaStream(args: IConnectionArgs) {
1131
+ private connectToDeltaStream(args: IConnectionArgs) {
1133
1132
  this.recordConnectStartTime();
1134
1133
 
1135
1134
  // All agents need "write" access, including summarizer.
@@ -1137,7 +1136,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1137
1136
  args.mode = "write";
1138
1137
  }
1139
1138
 
1140
- return this._deltaManager.connect(args);
1139
+ this._deltaManager.connect(args);
1141
1140
  }
1142
1141
 
1143
1142
  /**
@@ -1157,8 +1156,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1157
1156
  }
1158
1157
  this.service = await this.serviceFactory.createDocumentService(this._resolvedUrl, this.subLogger);
1159
1158
 
1160
- let startConnectionP: Promise<IConnectionDetails> | undefined;
1161
-
1162
1159
  // Ideally we always connect as "read" by default.
1163
1160
  // Currently that works with SPO & r11s, because we get "write" connection when connecting to non-existing file.
1164
1161
  // We should not rely on it by (one of them will address the issue, but we need to address both)
@@ -1173,8 +1170,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1173
1170
  // Start websocket connection as soon as possible. Note that there is no op handler attached yet, but the
1174
1171
  // DeltaManager is resilient to this and will wait to start processing ops until after it is attached.
1175
1172
  if (loadMode.deltaConnection === undefined) {
1176
- startConnectionP = this.connectToDeltaStream(connectionArgs);
1177
- startConnectionP.catch((error) => { });
1173
+ this.connectToDeltaStream(connectionArgs);
1178
1174
  }
1179
1175
 
1180
1176
  await this.connectStorageService();
@@ -1538,16 +1534,21 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1538
1534
  */
1539
1535
  private activeConnection() {
1540
1536
  return this.connectionState === ConnectionState.Connected &&
1541
- this._deltaManager.connectionMode === "write";
1537
+ this.connectionMode === "write";
1542
1538
  }
1543
1539
 
1544
1540
  private createDeltaManager() {
1545
- const deltaManager: DeltaManager = new DeltaManager(
1546
- () => this.service,
1547
- this.client,
1541
+ const serviceProvider = () => this.service;
1542
+ const deltaManager = new DeltaManager<ConnectionManager>(
1543
+ serviceProvider,
1548
1544
  ChildLogger.create(this.subLogger, "DeltaManager"),
1549
- this._canReconnect,
1550
1545
  () => this.activeConnection(),
1546
+ (props: IConnectionManagerFactoryArgs) => new ConnectionManager(
1547
+ serviceProvider,
1548
+ this.client,
1549
+ this._canReconnect,
1550
+ ChildLogger.create(this.subLogger, "ConnectionManager"),
1551
+ props),
1551
1552
  );
1552
1553
 
1553
1554
  // Disable inbound queues as Container is not ready to accept any ops until we are fully loaded!
@@ -1557,17 +1558,17 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1557
1558
  deltaManager.inboundSignal.pause();
1558
1559
 
1559
1560
  deltaManager.on("connect", (details: IConnectionDetails, opsBehind?: number) => {
1560
- this.connectionStateHandler.receivedConnectEvent(
1561
- this._deltaManager.connectionMode,
1562
- details,
1563
- );
1564
-
1565
1561
  // Back-compat for new client and old server.
1566
1562
  this._audience.clear();
1567
1563
 
1568
1564
  for (const priorClient of details.initialClients ?? []) {
1569
1565
  this._audience.addMember(priorClient.clientId, priorClient.client);
1570
1566
  }
1567
+
1568
+ this.connectionStateHandler.receivedConnectEvent(
1569
+ this.connectionMode,
1570
+ details,
1571
+ );
1571
1572
  });
1572
1573
 
1573
1574
  deltaManager.on("disconnect", (reason: string) => {
@@ -1624,7 +1625,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1624
1625
  let checkpointSequenceNumber: number | undefined;
1625
1626
  let opsBehind: number | undefined;
1626
1627
  if (value === ConnectionState.Disconnected) {
1627
- autoReconnect = this._deltaManager.reconnectMode;
1628
+ autoReconnect = this._deltaManager.connectionManager.reconnectMode;
1628
1629
  } else {
1629
1630
  if (value === ConnectionState.Connected) {
1630
1631
  durationFromDisconnected = time - this.connectionTransitionTimes[ConnectionState.Disconnected];
@@ -1652,7 +1653,6 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1652
1653
  durationFromDisconnected,
1653
1654
  reason,
1654
1655
  connectionInitiationReason,
1655
- socketDocumentId: this._deltaManager.socketDocumentId,
1656
1656
  pendingClientId: this.connectionStateHandler.pendingClientId,
1657
1657
  clientId: this.clientId,
1658
1658
  autoReconnect,
@@ -1660,7 +1660,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1660
1660
  online: OnlineStatus[isOnline()],
1661
1661
  lastVisible: this.lastVisible !== undefined ? performance.now() - this.lastVisible : undefined,
1662
1662
  checkpointSequenceNumber,
1663
- ...this._deltaManager.connectionProps(),
1663
+ ...this._deltaManager.connectionProps,
1664
1664
  });
1665
1665
 
1666
1666
  if (value === ConnectionState.Connected) {
@@ -1673,7 +1673,7 @@ export class Container extends EventEmitterWithErrorHandling<IContainerEvents> i
1673
1673
  const logOpsOnReconnect: boolean =
1674
1674
  this.connectionState === ConnectionState.Connected &&
1675
1675
  !this.firstConnection &&
1676
- this._deltaManager.connectionMode === "write";
1676
+ this.connectionMode === "write";
1677
1677
  if (logOpsOnReconnect) {
1678
1678
  this.messageCountAfterDisconnection = 0;
1679
1679
  }
@@ -0,0 +1,156 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import {
7
+ ITelemetryProperties,
8
+ } from "@fluidframework/common-definitions";
9
+ import {
10
+ IDeltaQueue,
11
+ ReadOnlyInfo,
12
+ IConnectionDetails,
13
+ } from "@fluidframework/container-definitions";
14
+ import {
15
+ ConnectionMode,
16
+ IDocumentMessage,
17
+ ISequencedDocumentMessage,
18
+ IClientConfiguration,
19
+ IClientDetails,
20
+ ISignalMessage,
21
+ } from "@fluidframework/protocol-definitions";
22
+
23
+ export enum ReconnectMode {
24
+ Never = "Never",
25
+ Disabled = "Disabled",
26
+ Enabled = "Enabled",
27
+ }
28
+
29
+ /**
30
+ * Connection manager (implements this interface) is responsible for maintaining connection
31
+ * to relay service.
32
+ */
33
+ export interface IConnectionManager {
34
+ readonly connected: boolean;
35
+
36
+ readonly clientId: string | undefined;
37
+
38
+ /** The queue of outbound delta messages */
39
+ readonly outbound: IDeltaQueue<IDocumentMessage[]>;
40
+
41
+ /** Details of client */
42
+ readonly clientDetails: IClientDetails;
43
+
44
+ /** Protocol version being used to communicate with the service */
45
+ readonly version: string;
46
+
47
+ /** Max message size allowed to the delta manager */
48
+ readonly maxMessageSize: number;
49
+
50
+ /** Service configuration provided by the service. */
51
+ readonly serviceConfiguration: IClientConfiguration | undefined;
52
+
53
+ readonly readOnlyInfo: ReadOnlyInfo;
54
+
55
+ // Various connectivity propetries for telemetry describing type of current connection
56
+ // Things like connection mode, service info, etc.
57
+ // Called when connection state changes (connect / disconnect)
58
+ readonly connectionProps: ITelemetryProperties;
59
+
60
+ // Verbose information about connection logged to telemetry in case of issues with
61
+ // maintaining healphy connection, including op gaps, not receiving join op in time, etc.
62
+ // Contains details information, like sequence numbers at connection time, initial ops info, etc.
63
+ readonly connectionVerboseProps: ITelemetryProperties;
64
+
65
+ /**
66
+ * Prepares message to be sent. Fills in clientSequenceNumber.
67
+ * Called only when active connection is present.
68
+ */
69
+ prepareMessageToSend(message: Omit<IDocumentMessage, "clientSequenceNumber">): IDocumentMessage | undefined;
70
+
71
+ /**
72
+ * Called before incomming message is processed. Incomming messages can be comming from connection,
73
+ * but also could come from storage.
74
+ * This call allows connection manager to adjust knowledge about acked ops sent on previous connection.
75
+ * Can be called at any time, including when there is no active connection.
76
+ */
77
+ beforeProcessingIncomingOp(message: ISequencedDocumentMessage): void;
78
+
79
+ /**
80
+ * Submits signal to relay service.
81
+ * Called only when active connection is present.
82
+ */
83
+ submitSignal(content: any): void;
84
+
85
+ /**
86
+ * Submits messages to relay service.
87
+ * Called only when active connection is present.
88
+ */
89
+ sendMessages(messages: IDocumentMessage[]): void;
90
+
91
+ /**
92
+ * Initiates connection to relay service (noop if already connected).
93
+ */
94
+ connect(connectionMode?: ConnectionMode): void;
95
+
96
+ /**
97
+ * Disposed connection manager
98
+ */
99
+ dispose(error: any): void;
100
+ }
101
+
102
+ /**
103
+ * This interface represents a set of callbacks provided by DeltaManager to IConnectionManager on its creation
104
+ * IConnectionManager instance will use them to communicate to DeltaManager abour various events.
105
+ */
106
+ export interface IConnectionManagerFactoryArgs {
107
+ /**
108
+ * Called by connection manager for each incomming op. Some ops maybe delivered before
109
+ * connectHandler is called (initial ops on socket connection)
110
+ */
111
+ readonly incomingOpHandler: (messages: ISequencedDocumentMessage[], reason: string) => void,
112
+
113
+ /**
114
+ * Called by connection manager for each incoming signals.
115
+ * Maybe called before connectHandler is called (initial signals on socket connection)
116
+ */
117
+ readonly signalHandler: (message: ISignalMessage) => void,
118
+
119
+ /**
120
+ * Called when connection manager experiences delay in connecting to relay service.
121
+ * This can happen because client is offline, or service is busy and asks to not connect for some time.
122
+ * Can be called many times while not connected.
123
+ * Situation is considered resolved when connection is established and connectHandler is called.
124
+ */
125
+ readonly reconnectionDelayHandler: (delayMs: number, error: unknown) => void,
126
+
127
+ /**
128
+ * Called by connection manager whwnever critical error happens and container should be closed.
129
+ * Expects dispose() call in respose to this call.
130
+ */
131
+ readonly closeHandler: (error?: any) => void,
132
+
133
+ /**
134
+ * Called whenever connection to relay service is lost.
135
+ */
136
+ readonly disconnectHandler: (reason: string) => void,
137
+
138
+ /**
139
+ * Called whenever new connection to rely service is established
140
+ */
141
+ readonly connectHandler: (connection: IConnectionDetails) => void,
142
+
143
+ /**
144
+ * Called whenever ping/pong messages are roundtripped on connection.
145
+ */
146
+ readonly pongHandler: (latency: number) => void,
147
+
148
+ /**
149
+ * Called whenever connection type changes from writable to read-only or vice versa.
150
+ * Connection can be read-only if user has no edit permissions, or if container forced
151
+ * connection to be read-only.
152
+ * This should not be confused with "read" / "write"connection mode which is internal
153
+ * optimization.
154
+ */
155
+ readonly readonlyChangeHandler: (readonly?: boolean) => void,
156
+ }