@fluidframework/container-loader 1.2.2 → 2.0.0-internal.1.0.0.82159

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 (127) hide show
  1. package/dist/audience.d.ts +2 -2
  2. package/dist/audience.d.ts.map +1 -1
  3. package/dist/audience.js.map +1 -1
  4. package/dist/catchUpMonitor.d.ts +40 -0
  5. package/dist/catchUpMonitor.d.ts.map +1 -0
  6. package/dist/catchUpMonitor.js +74 -0
  7. package/dist/catchUpMonitor.js.map +1 -0
  8. package/dist/connectionManager.d.ts.map +1 -1
  9. package/dist/connectionManager.js +0 -1
  10. package/dist/connectionManager.js.map +1 -1
  11. package/dist/connectionState.d.ts +0 -5
  12. package/dist/connectionState.d.ts.map +1 -1
  13. package/dist/connectionState.js +0 -5
  14. package/dist/connectionState.js.map +1 -1
  15. package/dist/connectionStateHandler.d.ts +12 -4
  16. package/dist/connectionStateHandler.d.ts.map +1 -1
  17. package/dist/connectionStateHandler.js +47 -15
  18. package/dist/connectionStateHandler.js.map +1 -1
  19. package/dist/container.d.ts +8 -6
  20. package/dist/container.d.ts.map +1 -1
  21. package/dist/container.js +82 -46
  22. package/dist/container.js.map +1 -1
  23. package/dist/containerStorageAdapter.d.ts +2 -2
  24. package/dist/containerStorageAdapter.d.ts.map +1 -1
  25. package/dist/containerStorageAdapter.js +2 -2
  26. package/dist/containerStorageAdapter.js.map +1 -1
  27. package/dist/deltaManager.d.ts.map +1 -1
  28. package/dist/deltaManager.js +6 -6
  29. package/dist/deltaManager.js.map +1 -1
  30. package/dist/deltaManagerProxy.d.ts +4 -1
  31. package/dist/deltaManagerProxy.d.ts.map +1 -1
  32. package/dist/deltaQueue.d.ts +9 -2
  33. package/dist/deltaQueue.d.ts.map +1 -1
  34. package/dist/deltaQueue.js +31 -26
  35. package/dist/deltaQueue.js.map +1 -1
  36. package/dist/index.d.ts +1 -0
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js.map +1 -1
  39. package/dist/loader.d.ts +7 -0
  40. package/dist/loader.d.ts.map +1 -1
  41. package/dist/loader.js +4 -3
  42. package/dist/loader.js.map +1 -1
  43. package/dist/packageVersion.d.ts +1 -1
  44. package/dist/packageVersion.d.ts.map +1 -1
  45. package/dist/packageVersion.js +1 -1
  46. package/dist/packageVersion.js.map +1 -1
  47. package/dist/protocol.d.ts +22 -0
  48. package/dist/protocol.d.ts.map +1 -0
  49. package/dist/protocol.js +52 -0
  50. package/dist/protocol.js.map +1 -0
  51. package/dist/protocolTreeDocumentStorageService.d.ts +1 -1
  52. package/dist/protocolTreeDocumentStorageService.d.ts.map +1 -1
  53. package/dist/retriableDocumentStorageService.d.ts +2 -2
  54. package/dist/retriableDocumentStorageService.d.ts.map +1 -1
  55. package/dist/retriableDocumentStorageService.js +2 -2
  56. package/dist/retriableDocumentStorageService.js.map +1 -1
  57. package/lib/audience.d.ts +2 -2
  58. package/lib/audience.d.ts.map +1 -1
  59. package/lib/audience.js.map +1 -1
  60. package/lib/catchUpMonitor.d.ts +40 -0
  61. package/lib/catchUpMonitor.d.ts.map +1 -0
  62. package/lib/catchUpMonitor.js +69 -0
  63. package/lib/catchUpMonitor.js.map +1 -0
  64. package/lib/connectionManager.d.ts.map +1 -1
  65. package/lib/connectionManager.js +0 -1
  66. package/lib/connectionManager.js.map +1 -1
  67. package/lib/connectionState.d.ts +0 -5
  68. package/lib/connectionState.d.ts.map +1 -1
  69. package/lib/connectionState.js +0 -5
  70. package/lib/connectionState.js.map +1 -1
  71. package/lib/connectionStateHandler.d.ts +12 -4
  72. package/lib/connectionStateHandler.d.ts.map +1 -1
  73. package/lib/connectionStateHandler.js +47 -15
  74. package/lib/connectionStateHandler.js.map +1 -1
  75. package/lib/container.d.ts +8 -6
  76. package/lib/container.d.ts.map +1 -1
  77. package/lib/container.js +82 -46
  78. package/lib/container.js.map +1 -1
  79. package/lib/containerStorageAdapter.d.ts +2 -2
  80. package/lib/containerStorageAdapter.d.ts.map +1 -1
  81. package/lib/containerStorageAdapter.js +2 -2
  82. package/lib/containerStorageAdapter.js.map +1 -1
  83. package/lib/deltaManager.d.ts.map +1 -1
  84. package/lib/deltaManager.js +6 -6
  85. package/lib/deltaManager.js.map +1 -1
  86. package/lib/deltaManagerProxy.d.ts +4 -1
  87. package/lib/deltaManagerProxy.d.ts.map +1 -1
  88. package/lib/deltaQueue.d.ts +9 -2
  89. package/lib/deltaQueue.d.ts.map +1 -1
  90. package/lib/deltaQueue.js +32 -27
  91. package/lib/deltaQueue.js.map +1 -1
  92. package/lib/index.d.ts +1 -0
  93. package/lib/index.d.ts.map +1 -1
  94. package/lib/index.js.map +1 -1
  95. package/lib/loader.d.ts +7 -0
  96. package/lib/loader.d.ts.map +1 -1
  97. package/lib/loader.js +4 -3
  98. package/lib/loader.js.map +1 -1
  99. package/lib/packageVersion.d.ts +1 -1
  100. package/lib/packageVersion.d.ts.map +1 -1
  101. package/lib/packageVersion.js +1 -1
  102. package/lib/packageVersion.js.map +1 -1
  103. package/lib/protocol.d.ts +22 -0
  104. package/lib/protocol.d.ts.map +1 -0
  105. package/lib/protocol.js +48 -0
  106. package/lib/protocol.js.map +1 -0
  107. package/lib/protocolTreeDocumentStorageService.d.ts +1 -1
  108. package/lib/protocolTreeDocumentStorageService.d.ts.map +1 -1
  109. package/lib/retriableDocumentStorageService.d.ts +2 -2
  110. package/lib/retriableDocumentStorageService.d.ts.map +1 -1
  111. package/lib/retriableDocumentStorageService.js +2 -2
  112. package/lib/retriableDocumentStorageService.js.map +1 -1
  113. package/package.json +23 -15
  114. package/src/audience.ts +2 -2
  115. package/src/catchUpMonitor.ts +99 -0
  116. package/src/connectionManager.ts +0 -1
  117. package/src/connectionState.ts +0 -6
  118. package/src/connectionStateHandler.ts +55 -15
  119. package/src/container.ts +115 -63
  120. package/src/containerStorageAdapter.ts +8 -2
  121. package/src/deltaManager.ts +6 -4
  122. package/src/deltaQueue.ts +34 -28
  123. package/src/index.ts +4 -0
  124. package/src/loader.ts +13 -2
  125. package/src/packageVersion.ts +1 -1
  126. package/src/protocol.ts +96 -0
  127. package/src/retriableDocumentStorageService.ts +8 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-loader",
3
- "version": "1.2.2",
3
+ "version": "2.0.0-internal.1.0.0.82159",
4
4
  "description": "Fluid container loader",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -63,26 +63,27 @@
63
63
  "dependencies": {
64
64
  "@fluidframework/common-definitions": "^0.20.1",
65
65
  "@fluidframework/common-utils": "^0.32.1",
66
- "@fluidframework/container-definitions": "^1.2.2",
67
- "@fluidframework/container-utils": "^1.2.2",
68
- "@fluidframework/core-interfaces": "^1.2.2",
69
- "@fluidframework/driver-definitions": "^1.2.2",
70
- "@fluidframework/driver-utils": "^1.2.2",
71
- "@fluidframework/protocol-base": "^0.1036.5000",
72
- "@fluidframework/protocol-definitions": "^0.1028.2000",
73
- "@fluidframework/telemetry-utils": "^1.2.2",
66
+ "@fluidframework/container-definitions": "2.0.0-internal.1.0.0.82159",
67
+ "@fluidframework/container-utils": "2.0.0-internal.1.0.0.82159",
68
+ "@fluidframework/core-interfaces": "2.0.0-internal.1.0.0.82159",
69
+ "@fluidframework/driver-definitions": "2.0.0-internal.1.0.0.82159",
70
+ "@fluidframework/driver-utils": "2.0.0-internal.1.0.0.82159",
71
+ "@fluidframework/protocol-base": "^0.1037.1000-0",
72
+ "@fluidframework/protocol-definitions": "^0.1029.1000-0",
73
+ "@fluidframework/telemetry-utils": "2.0.0-internal.1.0.0.82159",
74
74
  "abort-controller": "^3.0.0",
75
75
  "double-ended-queue": "^2.1.0-0",
76
76
  "lodash": "^4.17.21",
77
+ "url": "^0.11.0",
77
78
  "uuid": "^8.3.1"
78
79
  },
79
80
  "devDependencies": {
80
81
  "@fluidframework/build-common": "^0.24.0",
81
- "@fluidframework/build-tools": "^0.2.74327",
82
- "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@1.2.1",
82
+ "@fluidframework/build-tools": "^0.3.0-0",
83
+ "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@^1.0.0",
83
84
  "@fluidframework/eslint-config-fluid": "^0.28.2000",
84
- "@fluidframework/mocha-test-setup": "^1.2.2",
85
- "@fluidframework/test-loader-utils": "^1.2.2",
85
+ "@fluidframework/mocha-test-setup": "2.0.0-internal.1.0.0.82159",
86
+ "@fluidframework/test-loader-utils": "2.0.0-internal.1.0.0.82159",
86
87
  "@microsoft/api-extractor": "^7.22.2",
87
88
  "@rushstack/eslint-config": "^2.5.1",
88
89
  "@types/double-ended-queue": "^2.1.0",
@@ -102,7 +103,14 @@
102
103
  "typescript-formatter": "7.1.0"
103
104
  },
104
105
  "typeValidation": {
105
- "version": "1.2.2",
106
- "broken": {}
106
+ "version": "2.0.0",
107
+ "broken": {
108
+ "EnumDeclaration_ConnectionState": {
109
+ "forwardCompat": false
110
+ },
111
+ "ClassDeclaration_Container": {
112
+ "forwardCompat": false
113
+ }
114
+ }
107
115
  }
108
116
  }
package/src/audience.ts CHANGED
@@ -3,13 +3,13 @@
3
3
  * Licensed under the MIT License.
4
4
  */
5
5
  import { EventEmitter } from "events";
6
- import { IAudience } from "@fluidframework/container-definitions";
6
+ import { IAudienceOwner } from "@fluidframework/container-definitions";
7
7
  import { IClient } from "@fluidframework/protocol-definitions";
8
8
 
9
9
  /**
10
10
  * Audience represents all clients connected to the op stream.
11
11
  */
12
- export class Audience extends EventEmitter implements IAudience {
12
+ export class Audience extends EventEmitter implements IAudienceOwner {
13
13
  private readonly members = new Map<string, IClient>();
14
14
 
15
15
  public on(event: "addMember" | "removeMember", listener: (clientId: string, client: IClient) => void): this;
@@ -0,0 +1,99 @@
1
+ /*!
2
+ * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
+ * Licensed under the MIT License.
4
+ */
5
+
6
+ import { IDisposable, IEvent } from "@fluidframework/common-definitions";
7
+ import { assert, TypedEventEmitter } from "@fluidframework/common-utils";
8
+ import { IDeltaManager } from "@fluidframework/container-definitions";
9
+ import { ISequencedDocumentMessage } from "@fluidframework/protocol-definitions";
10
+
11
+ /** @see ICatchUpMonitor for usage */
12
+ type CaughtUpListener = () => void;
13
+
14
+ /** @see ICatchUpMonitor for usage */
15
+ export interface ICatchUpMonitorEvents extends IEvent {
16
+ (event: "caughtUp", listener: CaughtUpListener): void;
17
+ }
18
+
19
+ /** Monitor that emits an event when a Container has caught up to a given point in the op stream */
20
+ export interface ICatchUpMonitor extends TypedEventEmitter<ICatchUpMonitorEvents>, IDisposable { }
21
+
22
+ /**
23
+ * Monitors a Container's DeltaManager, notifying listeners when all ops have been processed
24
+ * that were known at the time the monitor was created.
25
+ */
26
+ export class CatchUpMonitor extends TypedEventEmitter<ICatchUpMonitorEvents> implements ICatchUpMonitor {
27
+ private readonly targetSeqNumber: number;
28
+ private caughtUp: boolean = false;
29
+
30
+ private readonly opHandler = (message: Pick<ISequencedDocumentMessage, "sequenceNumber">) => {
31
+ if (!this.caughtUp && message.sequenceNumber >= this.targetSeqNumber) {
32
+ this.caughtUp = true;
33
+ this.emit("caughtUp");
34
+ }
35
+ };
36
+
37
+ /**
38
+ * Create the CatchUpMonitor, setting the target sequence number to wait for based on DeltaManager's current state.
39
+ */
40
+ constructor(
41
+ private readonly deltaManager: IDeltaManager<any, any>,
42
+ ) {
43
+ super();
44
+
45
+ this.targetSeqNumber = this.deltaManager.lastKnownSeqNumber;
46
+
47
+ assert(this.targetSeqNumber >= this.deltaManager.lastSequenceNumber,
48
+ "Cannot wait for seqNumber below last processed sequence number");
49
+
50
+ this.deltaManager.on("op", this.opHandler);
51
+
52
+ // Simulate the last processed op to set caughtUp in case we already are
53
+ this.opHandler({ sequenceNumber: this.deltaManager.lastSequenceNumber });
54
+
55
+ // If a listener is added after we are already caught up, notify that new listener immediately
56
+ this.on("newListener", (event: string, listener) => {
57
+ if (event === "caughtUp") {
58
+ const caughtUpListener = listener as CaughtUpListener;
59
+ if (this.caughtUp) {
60
+ caughtUpListener();
61
+ }
62
+ }
63
+ });
64
+ }
65
+
66
+ public disposed: boolean = false;
67
+ public dispose() {
68
+ if (this.disposed) {
69
+ return;
70
+ }
71
+ this.disposed = true;
72
+
73
+ this.removeAllListeners();
74
+ this.deltaManager.off("op", this.opHandler);
75
+ }
76
+ }
77
+
78
+ /** Monitor that always notifies listeners immediately */
79
+ export class ImmediateCatchUpMonitor extends TypedEventEmitter<ICatchUpMonitorEvents> implements ICatchUpMonitor {
80
+ constructor() {
81
+ super();
82
+ this.on("newListener", (event: string, listener) => {
83
+ if (event === "caughtUp") {
84
+ const caughtUpListener = listener as CaughtUpListener;
85
+ caughtUpListener();
86
+ }
87
+ });
88
+ }
89
+
90
+ public disposed: boolean = false;
91
+ public dispose() {
92
+ if (this.disposed) {
93
+ return;
94
+ }
95
+ this.disposed = true;
96
+
97
+ this.removeAllListeners();
98
+ }
99
+ }
@@ -97,7 +97,6 @@ class NoDeltaStream
97
97
  serviceConfiguration: IClientConfiguration = {
98
98
  maxMessageSize: 0,
99
99
  blockSize: 0,
100
- summary: undefined as any,
101
100
  };
102
101
  checkpointSequenceNumber?: number | undefined = undefined;
103
102
  submit(messages: IDocumentMessage[]): void {
@@ -17,12 +17,6 @@ export enum ConnectionState {
17
17
  */
18
18
  EstablishingConnection = 3,
19
19
 
20
- /**
21
- * See {@link ConnectionState.CatchingUp}, which is the new name for this state.
22
- * @deprecated - This state itself is not gone, just being renamed. Please use {@link ConnectionState.CatchingUp}.
23
- */
24
- Connecting = 1,
25
-
26
20
  /**
27
21
  * The container has an inbound connection only, and is catching up to the latest known state from the service.
28
22
  */
@@ -5,11 +5,12 @@
5
5
 
6
6
  import { ITelemetryLogger, ITelemetryProperties } from "@fluidframework/common-definitions";
7
7
  import { assert, Timer } from "@fluidframework/common-utils";
8
- import { IConnectionDetails } from "@fluidframework/container-definitions";
8
+ import { IConnectionDetails, IDeltaManager } from "@fluidframework/container-definitions";
9
9
  import { ILocalSequencedClient, IProtocolHandler } from "@fluidframework/protocol-base";
10
10
  import { ConnectionMode, IQuorumClients } from "@fluidframework/protocol-definitions";
11
11
  import { PerformanceEvent } from "@fluidframework/telemetry-utils";
12
12
  import { ConnectionState } from "./connectionState";
13
+ import { CatchUpMonitor, ICatchUpMonitor, ImmediateCatchUpMonitor } from "./catchUpMonitor";
13
14
 
14
15
  /** Constructor parameter type for passing in dependencies needed by the ConnectionStateHandler */
15
16
  export interface IConnectionStateHandlerInputs {
@@ -36,7 +37,7 @@ const JoinOpTimeoutMs = 45000;
36
37
  * sequenced or blocked by the server before emitting the new "connected" event and allowing runtime to resubmit ops.
37
38
  *
38
39
  * Each connection is assigned a clientId by the service, and the connection is book-ended by a Join and a Leave op
39
- * generated by the service. Due to the distributed nature of the ordering service, in the case of reconnect we cannot
40
+ * generated by the service. Due to the distributed nature of the Relay Service, in the case of reconnect we cannot
40
41
  * make any assumptions about ordering of operations between the old and new connections - i.e. new Join op could
41
42
  * be sequenced before old Leave op (and some acks from pending ops that were in flight when we disconnected).
42
43
  *
@@ -47,14 +48,17 @@ const JoinOpTimeoutMs = 45000;
47
48
  * pending ops can safely be submitted with the new clientId without fear of duplication in the sequenced op stream.
48
49
  * (B) We process the Join op for the new clientId (identified when the underlying connection was first established),
49
50
  * indicating the service is ready to sequence ops sent with the new clientId.
51
+ * (C) We process all ops known at the time the underlying connection was established (so we are "caught up")
50
52
  *
51
53
  * For (A) we give up waiting after some time (same timeout as server uses), and go ahead and transition to Connected.
52
54
  * For (B) we log telemetry if it takes too long, but still only transition to Connected when the Join op is processed
53
55
  * and we are added to the Quorum.
56
+ * For (C) this is optional behavior, controlled by the parameters of receivedConnectEvent
54
57
  */
55
58
  export class ConnectionStateHandler {
56
59
  private _connectionState = ConnectionState.Disconnected;
57
60
  private _pendingClientId: string | undefined;
61
+ private catchUpMonitor: ICatchUpMonitor | undefined;
58
62
  private readonly prevClientLeftTimer: Timer;
59
63
  private readonly joinOpTimer: Timer;
60
64
 
@@ -106,7 +110,7 @@ export class ConnectionStateHandler {
106
110
  const quorumClients = this.handler.quorumClients();
107
111
  const details = {
108
112
  quorumInitialized: quorumClients !== undefined,
109
- hasPendingClientId: this.pendingClientId !== undefined,
113
+ pendingClientId: this.pendingClientId,
110
114
  inQuorum: quorumClients?.getMember(this.pendingClientId ?? "") !== undefined,
111
115
  waitingForLeaveOp: this.waitingForLeaveOp,
112
116
  };
@@ -184,7 +188,10 @@ export class ConnectionStateHandler {
184
188
  && !this.waitingForLeaveOp
185
189
  ) {
186
190
  this.waitEvent?.end({ source });
187
- this.setConnectionState(ConnectionState.Connected);
191
+
192
+ assert(this.catchUpMonitor !== undefined,
193
+ "catchUpMonitor should always be set if pendingClientId is set");
194
+ this.catchUpMonitor.on("caughtUp", this.transitionToConnectedState);
188
195
  } else {
189
196
  // Adding this event temporarily so that we can get help debugging if something goes wrong.
190
197
  this.logger.sendTelemetryEvent({
@@ -210,32 +217,47 @@ export class ConnectionStateHandler {
210
217
  }
211
218
 
212
219
  public receivedDisconnectEvent(reason: string) {
213
- if (this.joinOpTimer.hasTimer) {
214
- this.stopJoinOpTimer();
215
- }
216
220
  this.setConnectionState(ConnectionState.Disconnected, reason);
217
221
  }
218
222
 
223
+ private readonly transitionToConnectedState = () => {
224
+ // Defensive measure, we should always be in CatchingUp state when this is called.
225
+ if (this._connectionState === ConnectionState.CatchingUp) {
226
+ this.setConnectionState(ConnectionState.Connected);
227
+ } else {
228
+ this.logger.sendTelemetryEvent({
229
+ eventName: "cannotTransitionToConnectedState",
230
+ connectionState: ConnectionState[this._connectionState],
231
+ });
232
+ }
233
+ };
234
+
219
235
  /**
220
236
  * The "connect" event indicates the connection to the Relay Service is live.
221
237
  * However, some additional conditions must be met before we can fully transition to
222
238
  * "Connected" state. This function handles that interim period, known as "Connecting" state.
223
239
  * @param connectionMode - Read or Write connection
224
- * @param details - Connection details returned from the ordering service
240
+ * @param details - Connection details returned from the Relay Service
241
+ * @param deltaManager - DeltaManager to be used for delaying Connected transition until caught up.
242
+ * If it's undefined, then don't delay and transition to Connected as soon as Leave/Join op are accounted for
225
243
  */
226
244
  public receivedConnectEvent(
227
245
  connectionMode: ConnectionMode,
228
246
  details: IConnectionDetails,
247
+ deltaManager?: IDeltaManager<any, any>,
229
248
  ) {
230
249
  const oldState = this._connectionState;
231
250
  this._connectionState = ConnectionState.CatchingUp;
232
251
 
233
252
  const writeConnection = connectionMode === "write";
234
- assert(writeConnection || !this.handler.shouldClientJoinWrite(),
253
+ assert(!this.handler.shouldClientJoinWrite() || writeConnection,
235
254
  0x30a /* shouldClientJoinWrite should imply this is a writeConnection */);
236
- assert(writeConnection || !this.waitingForLeaveOp,
255
+ assert(!this.waitingForLeaveOp || writeConnection,
237
256
  0x2a6 /* "waitingForLeaveOp should imply writeConnection (we need to be ready to flush pending ops)" */);
238
257
 
258
+ // Defensive measure in case catchUpMonitor from previous connection attempt wasn't already cleared
259
+ this.catchUpMonitor?.dispose();
260
+
239
261
  // Note that this may be undefined since the connection is established proactively on load
240
262
  // and the quorum may still be under initialization.
241
263
  const quorumClients: IQuorumClients | undefined = this.handler.quorumClients();
@@ -248,6 +270,11 @@ export class ConnectionStateHandler {
248
270
  // we know there can no longer be outstanding ops that we sent with the previous client id.
249
271
  this._pendingClientId = details.clientId;
250
272
 
273
+ // We may want to catch up to known ops as of now before transitioning to Connected state
274
+ this.catchUpMonitor = deltaManager !== undefined
275
+ ? new CatchUpMonitor(deltaManager)
276
+ : new ImmediateCatchUpMonitor();
277
+
251
278
  // IMPORTANT: Report telemetry after we set _pendingClientId, but before transitioning to Connected state
252
279
  this.handler.logConnectionStateChangeTelemetry(ConnectionState.CatchingUp, oldState);
253
280
 
@@ -264,9 +291,21 @@ export class ConnectionStateHandler {
264
291
  this.startJoinOpTimer();
265
292
  } else if (!this.waitingForLeaveOp) {
266
293
  // We're not waiting for Join or Leave op (if read-only connection those don't even apply),
267
- // go ahead and declare the state to be Connected!
268
- // If we are waiting for Leave op still, do nothing for now, we will transition to Connected later.
269
- this.setConnectionState(ConnectionState.Connected);
294
+ // but we do need to wait until we are caught up (to now-known ops) before transitioning to Connected state.
295
+ this.catchUpMonitor.on("caughtUp", this.transitionToConnectedState);
296
+ }
297
+ // else - We are waiting for Leave op still, do nothing for now, we will transition to Connected later
298
+ }
299
+
300
+ /** Clear all the state used during the Connecting phase (set in receivedConnectEvent) */
301
+ private clearPendingConnectionState() {
302
+ this._pendingClientId = undefined;
303
+
304
+ this.catchUpMonitor?.dispose();
305
+ this.catchUpMonitor = undefined;
306
+
307
+ if (this.joinOpTimer.hasTimer) {
308
+ this.stopJoinOpTimer();
270
309
  }
271
310
  }
272
311
 
@@ -295,8 +334,9 @@ export class ConnectionStateHandler {
295
334
  }
296
335
  this._clientId = this.pendingClientId;
297
336
  } else if (value === ConnectionState.Disconnected) {
298
- // Important as we process our own joinSession message through delta request
299
- this._pendingClientId = undefined;
337
+ // Clear pending state immediately to prepare for reconnect
338
+ this.clearPendingConnectionState();
339
+
300
340
  // Only wait for "leave" message if the connected client exists in the quorum because only the write
301
341
  // client will exist in the quorum and only for those clients we will receive "removeMember" event and
302
342
  // the client has some unacked ops.