@fluidframework/container-loader 2.0.0-internal.1.1.2 → 2.0.0-internal.1.2.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 (92) hide show
  1. package/dist/catchUpMonitor.d.ts +6 -17
  2. package/dist/catchUpMonitor.d.ts.map +1 -1
  3. package/dist/catchUpMonitor.js +5 -36
  4. package/dist/catchUpMonitor.js.map +1 -1
  5. package/dist/collabWindowTracker.d.ts +1 -1
  6. package/dist/collabWindowTracker.d.ts.map +1 -1
  7. package/dist/collabWindowTracker.js +2 -1
  8. package/dist/collabWindowTracker.js.map +1 -1
  9. package/dist/connectionManager.d.ts +1 -1
  10. package/dist/connectionManager.d.ts.map +1 -1
  11. package/dist/connectionManager.js +8 -11
  12. package/dist/connectionManager.js.map +1 -1
  13. package/dist/connectionStateHandler.d.ts +80 -26
  14. package/dist/connectionStateHandler.d.ts.map +1 -1
  15. package/dist/connectionStateHandler.js +170 -89
  16. package/dist/connectionStateHandler.js.map +1 -1
  17. package/dist/container.d.ts +22 -11
  18. package/dist/container.d.ts.map +1 -1
  19. package/dist/container.js +130 -142
  20. package/dist/container.js.map +1 -1
  21. package/dist/containerContext.d.ts +18 -7
  22. package/dist/containerContext.d.ts.map +1 -1
  23. package/dist/containerContext.js +18 -8
  24. package/dist/containerContext.js.map +1 -1
  25. package/dist/containerStorageAdapter.d.ts +10 -24
  26. package/dist/containerStorageAdapter.d.ts.map +1 -1
  27. package/dist/containerStorageAdapter.js +50 -16
  28. package/dist/containerStorageAdapter.js.map +1 -1
  29. package/dist/deltaManager.d.ts +1 -1
  30. package/dist/deltaManager.d.ts.map +1 -1
  31. package/dist/deltaManager.js +18 -6
  32. package/dist/deltaManager.js.map +1 -1
  33. package/dist/loader.d.ts +1 -1
  34. package/dist/loader.js.map +1 -1
  35. package/dist/packageVersion.d.ts +1 -1
  36. package/dist/packageVersion.js +1 -1
  37. package/dist/packageVersion.js.map +1 -1
  38. package/dist/protocol.d.ts.map +1 -1
  39. package/dist/protocol.js +2 -1
  40. package/dist/protocol.js.map +1 -1
  41. package/lib/catchUpMonitor.d.ts +6 -17
  42. package/lib/catchUpMonitor.d.ts.map +1 -1
  43. package/lib/catchUpMonitor.js +5 -35
  44. package/lib/catchUpMonitor.js.map +1 -1
  45. package/lib/collabWindowTracker.d.ts +1 -1
  46. package/lib/collabWindowTracker.d.ts.map +1 -1
  47. package/lib/collabWindowTracker.js +3 -2
  48. package/lib/collabWindowTracker.js.map +1 -1
  49. package/lib/connectionManager.d.ts +1 -1
  50. package/lib/connectionManager.d.ts.map +1 -1
  51. package/lib/connectionManager.js +9 -14
  52. package/lib/connectionManager.js.map +1 -1
  53. package/lib/connectionStateHandler.d.ts +80 -26
  54. package/lib/connectionStateHandler.d.ts.map +1 -1
  55. package/lib/connectionStateHandler.js +170 -90
  56. package/lib/connectionStateHandler.js.map +1 -1
  57. package/lib/container.d.ts +22 -11
  58. package/lib/container.d.ts.map +1 -1
  59. package/lib/container.js +133 -145
  60. package/lib/container.js.map +1 -1
  61. package/lib/containerContext.d.ts +18 -7
  62. package/lib/containerContext.d.ts.map +1 -1
  63. package/lib/containerContext.js +19 -9
  64. package/lib/containerContext.js.map +1 -1
  65. package/lib/containerStorageAdapter.d.ts +10 -24
  66. package/lib/containerStorageAdapter.d.ts.map +1 -1
  67. package/lib/containerStorageAdapter.js +50 -15
  68. package/lib/containerStorageAdapter.js.map +1 -1
  69. package/lib/deltaManager.d.ts +1 -1
  70. package/lib/deltaManager.d.ts.map +1 -1
  71. package/lib/deltaManager.js +18 -6
  72. package/lib/deltaManager.js.map +1 -1
  73. package/lib/loader.d.ts +1 -1
  74. package/lib/loader.js.map +1 -1
  75. package/lib/packageVersion.d.ts +1 -1
  76. package/lib/packageVersion.js +1 -1
  77. package/lib/packageVersion.js.map +1 -1
  78. package/lib/protocol.d.ts.map +1 -1
  79. package/lib/protocol.js +2 -1
  80. package/lib/protocol.js.map +1 -1
  81. package/package.json +13 -13
  82. package/src/catchUpMonitor.ts +7 -47
  83. package/src/collabWindowTracker.ts +4 -3
  84. package/src/connectionManager.ts +9 -11
  85. package/src/connectionStateHandler.ts +231 -106
  86. package/src/container.ts +156 -168
  87. package/src/containerContext.ts +22 -8
  88. package/src/containerStorageAdapter.ts +64 -15
  89. package/src/deltaManager.ts +20 -7
  90. package/src/loader.ts +1 -1
  91. package/src/packageVersion.ts +1 -1
  92. package/src/protocol.ts +2 -1
@@ -4,23 +4,74 @@
4
4
  */
5
5
  import { ITelemetryLogger, ITelemetryProperties } from "@fluidframework/common-definitions";
6
6
  import { IConnectionDetails, IDeltaManager } from "@fluidframework/container-definitions";
7
- import { IProtocolHandler } from "@fluidframework/protocol-base";
8
- import { ConnectionMode, IQuorumClients } from "@fluidframework/protocol-definitions";
7
+ import { ConnectionMode } from "@fluidframework/protocol-definitions";
9
8
  import { ConnectionState } from "./connectionState";
9
+ import { IProtocolHandler } from "./protocol";
10
10
  /** Constructor parameter type for passing in dependencies needed by the ConnectionStateHandler */
11
11
  export interface IConnectionStateHandlerInputs {
12
- /** Provides access to the clients currently in the quorum */
13
- quorumClients: () => IQuorumClients | undefined;
12
+ logger: ITelemetryLogger;
14
13
  /** Log to telemetry any change in state, included to Connecting */
15
- logConnectionStateChangeTelemetry: (value: ConnectionState, oldState: ConnectionState, reason?: string | undefined) => void;
14
+ connectionStateChanged: (value: ConnectionState, oldState: ConnectionState, reason?: string | undefined) => void;
16
15
  /** Whether to expect the client to join in write mode on next connection */
17
16
  shouldClientJoinWrite: () => boolean;
18
17
  /** (Optional) How long should we wait on our previous client's Leave op before transitioning to Connected again */
19
18
  maxClientLeaveWaitTime: number | undefined;
20
19
  /** Log an issue encountered while in the Connecting state. details will be logged as a JSON string */
21
20
  logConnectionIssue: (eventName: string, details?: ITelemetryProperties) => void;
22
- /** Callback whenever the ConnectionState changes between Disconnected and Connected */
23
- connectionStateChanged: () => void;
21
+ }
22
+ /**
23
+ * interface that connection state handler implements
24
+ */
25
+ export interface IConnectionStateHandler {
26
+ readonly connectionState: ConnectionState;
27
+ readonly pendingClientId: string | undefined;
28
+ containerSaved(): void;
29
+ dispose(): void;
30
+ initProtocol(protocol: IProtocolHandler): void;
31
+ receivedConnectEvent(connectionMode: ConnectionMode, details: IConnectionDetails): void;
32
+ receivedDisconnectEvent(reason: string): void;
33
+ }
34
+ export declare function createConnectionStateHandler(inputs: IConnectionStateHandlerInputs, deltaManager: IDeltaManager<any, any>, clientId?: string): ConnectionStateHandler | ConnectionStateCatchup;
35
+ export declare function createConnectionStateHandlerCore(wait: boolean, inputs: IConnectionStateHandlerInputs, deltaManager: IDeltaManager<any, any>, clientId?: string): ConnectionStateHandler | ConnectionStateCatchup;
36
+ /**
37
+ * Class that can be used as a base class for building IConnectionStateHandler adapters / pipeline.
38
+ * It implements both ends of communication interfaces and passes data back and forward
39
+ */
40
+ declare class ConnectionStateHandlerPassThrough implements IConnectionStateHandler, IConnectionStateHandlerInputs {
41
+ protected readonly inputs: IConnectionStateHandlerInputs;
42
+ protected readonly pimpl: IConnectionStateHandler;
43
+ constructor(inputs: IConnectionStateHandlerInputs, pimplFactory: (handler: IConnectionStateHandlerInputs) => IConnectionStateHandler);
44
+ /**
45
+ * IConnectionStateHandler
46
+ */
47
+ get connectionState(): ConnectionState;
48
+ get pendingClientId(): string | undefined;
49
+ containerSaved(): void;
50
+ dispose(): void;
51
+ initProtocol(protocol: IProtocolHandler): void;
52
+ receivedDisconnectEvent(reason: string): void;
53
+ receivedConnectEvent(connectionMode: ConnectionMode, details: IConnectionDetails): void;
54
+ /**
55
+ * IConnectionStateHandlerInputs
56
+ */
57
+ get logger(): ITelemetryLogger;
58
+ connectionStateChanged(value: ConnectionState, oldState: ConnectionState, reason?: string | undefined): void;
59
+ shouldClientJoinWrite(): boolean;
60
+ get maxClientLeaveWaitTime(): number | undefined;
61
+ logConnectionIssue(eventName: string, details?: ITelemetryProperties): void;
62
+ }
63
+ /**
64
+ * Implementation of IConnectionStateHandler pass-through adapter that waits for specific sequence number
65
+ * before raising connected event
66
+ */
67
+ declare class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
68
+ private readonly deltaManager;
69
+ private catchUpMonitor;
70
+ constructor(inputs: IConnectionStateHandlerInputs, pimplFactory: (handler: IConnectionStateHandlerInputs) => IConnectionStateHandler, deltaManager: IDeltaManager<any, any>);
71
+ private _connectionState;
72
+ get connectionState(): ConnectionState;
73
+ connectionStateChanged(value: ConnectionState, oldState: ConnectionState, reason?: string | undefined): void;
74
+ private readonly transitionToConnectedState;
24
75
  }
25
76
  /**
26
77
  * In the lifetime of a container, the connection will likely disconnect and reconnect periodically.
@@ -34,33 +85,36 @@ export interface IConnectionStateHandlerInputs {
34
85
  *
35
86
  * The job of this class is to encapsulate the transition period during reconnect, which is identified by
36
87
  * ConnectionState.CatchingUp. Specifically, before moving to Connected state with the new clientId, it ensures that:
37
- * (A) We process the Leave op for the previous clientId. This allows us to properly handle any acks from in-flight ops
38
- * that got sequenced with the old clientId (we'll recognize them as local ops). After the Leave op, any other
39
- * pending ops can safely be submitted with the new clientId without fear of duplication in the sequenced op stream.
40
- * (B) We process the Join op for the new clientId (identified when the underlying connection was first established),
41
- * indicating the service is ready to sequence ops sent with the new clientId.
42
- * (C) We process all ops known at the time the underlying connection was established (so we are "caught up")
43
88
  *
44
- * For (A) we give up waiting after some time (same timeout as server uses), and go ahead and transition to Connected.
45
- * For (B) we log telemetry if it takes too long, but still only transition to Connected when the Join op is processed
89
+ * a. We process the Leave op for the previous clientId. This allows us to properly handle any acks from in-flight ops
90
+ * that got sequenced with the old clientId (we'll recognize them as local ops). After the Leave op, any other
91
+ * pending ops can safely be submitted with the new clientId without fear of duplication in the sequenced op stream.
92
+ *
93
+ * b. We process the Join op for the new clientId (identified when the underlying connection was first established),
94
+ * indicating the service is ready to sequence ops sent with the new clientId.
95
+ *
96
+ * c. We process all ops known at the time the underlying connection was established (so we are "caught up")
97
+ *
98
+ * For (a) we give up waiting after some time (same timeout as server uses), and go ahead and transition to Connected.
99
+ *
100
+ * For (b) we log telemetry if it takes too long, but still only transition to Connected when the Join op is processed
46
101
  * and we are added to the Quorum.
47
- * For (C) this is optional behavior, controlled by the parameters of receivedConnectEvent
102
+ *
103
+ * For (c) this is optional behavior, controlled by the parameters of receivedConnectEvent
48
104
  */
49
- export declare class ConnectionStateHandler {
105
+ declare class ConnectionStateHandler implements IConnectionStateHandler {
50
106
  private readonly handler;
51
- private readonly logger;
52
107
  private _clientId?;
53
108
  private _connectionState;
54
109
  private _pendingClientId;
55
- private catchUpMonitor;
56
110
  private readonly prevClientLeftTimer;
57
111
  private readonly joinOpTimer;
112
+ private protocol?;
58
113
  private waitEvent;
59
114
  get connectionState(): ConnectionState;
60
- get connected(): boolean;
61
- get clientId(): string | undefined;
115
+ private get clientId();
62
116
  get pendingClientId(): string | undefined;
63
- constructor(handler: IConnectionStateHandlerInputs, logger: ITelemetryLogger, _clientId?: string | undefined);
117
+ constructor(handler: IConnectionStateHandlerInputs, _clientId?: string | undefined);
64
118
  private startJoinOpTimer;
65
119
  private stopJoinOpTimer;
66
120
  private get waitingForLeaveOp();
@@ -70,7 +124,6 @@ export declare class ConnectionStateHandler {
70
124
  private applyForConnectedState;
71
125
  private receivedRemoveMemberEvent;
72
126
  receivedDisconnectEvent(reason: string): void;
73
- private readonly transitionToConnectedState;
74
127
  /**
75
128
  * The "connect" event indicates the connection to the Relay Service is live.
76
129
  * However, some additional conditions must be met before we can fully transition to
@@ -80,10 +133,11 @@ export declare class ConnectionStateHandler {
80
133
  * @param deltaManager - DeltaManager to be used for delaying Connected transition until caught up.
81
134
  * If it's undefined, then don't delay and transition to Connected as soon as Leave/Join op are accounted for
82
135
  */
83
- receivedConnectEvent(connectionMode: ConnectionMode, details: IConnectionDetails, deltaManager?: IDeltaManager<any, any>): void;
84
- /** Clear all the state used during the Connecting phase (set in receivedConnectEvent) */
85
- private clearPendingConnectionState;
136
+ receivedConnectEvent(connectionMode: ConnectionMode, details: IConnectionDetails): void;
86
137
  private setConnectionState;
138
+ protected get membership(): import("@fluidframework/protocol-definitions").IQuorum | undefined;
87
139
  initProtocol(protocol: IProtocolHandler): void;
140
+ protected hasMember(clientId?: string): boolean;
88
141
  }
142
+ export {};
89
143
  //# sourceMappingURL=connectionStateHandler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"connectionStateHandler.d.ts","sourceRoot":"","sources":["../src/connectionStateHandler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAE5F,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAC1F,OAAO,EAAyB,gBAAgB,EAAE,MAAM,+BAA+B,CAAC;AACxF,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAEtF,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAGpD,kGAAkG;AAClG,MAAM,WAAW,6BAA6B;IAC1C,6DAA6D;IAC7D,aAAa,EAAE,MAAM,cAAc,GAAG,SAAS,CAAC;IAChD,mEAAmE;IACnE,iCAAiC,EAC7B,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAC7F,4EAA4E;IAC5E,qBAAqB,EAAE,MAAM,OAAO,CAAC;IACrC,mHAAmH;IACnH,sBAAsB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,sGAAsG;IACtG,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAChF,uFAAuF;IACvF,sBAAsB,EAAE,MAAM,IAAI,CAAC;CACtC;AAID;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,sBAAsB;IA0B3B,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,SAAS,CAAC;IA3BtB,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,cAAc,CAA8B;IACpD,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAQ;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IAEpC,OAAO,CAAC,SAAS,CAA+B;IAEhD,IAAW,eAAe,IAAI,eAAe,CAE5C;IAED,IAAW,SAAS,IAAI,OAAO,CAE9B;IAED,IAAW,QAAQ,IAAI,MAAM,GAAG,SAAS,CAExC;IAED,IAAW,eAAe,IAAI,MAAM,GAAG,SAAS,CAE/C;gBAGoB,OAAO,EAAE,6BAA6B,EACtC,MAAM,EAAE,gBAAgB,EACjC,SAAS,CAAC,oBAAQ;IAoC9B,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,eAAe;IAKvB,OAAO,KAAK,iBAAiB,GAE5B;IAEM,OAAO;IAKP,cAAc;IASrB,OAAO,CAAC,sBAAsB;IAwB9B,OAAO,CAAC,sBAAsB;IAqC9B,OAAO,CAAC,yBAAyB;IAQ1B,uBAAuB,CAAC,MAAM,EAAE,MAAM;IAI7C,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAUzC;IAEF;;;;;;;;OAQG;IACI,oBAAoB,CACvB,cAAc,EAAE,cAAc,EAC9B,OAAO,EAAE,kBAAkB,EAC3B,YAAY,CAAC,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC;IAqD1C,yFAAyF;IACzF,OAAO,CAAC,2BAA2B;IAWnC,OAAO,CAAC,kBAAkB;IA2DnB,YAAY,CAAC,QAAQ,EAAE,gBAAgB;CAcjD"}
1
+ {"version":3,"file":"connectionStateHandler.d.ts","sourceRoot":"","sources":["../src/connectionStateHandler.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAE5F,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,uCAAuC,CAAC;AAE1F,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAEtE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAI9C,kGAAkG;AAClG,MAAM,WAAW,6BAA6B;IAC1C,MAAM,EAAE,gBAAgB,CAAC;IACzB,mEAAmE;IACnE,sBAAsB,EAClB,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAC7F,4EAA4E;IAC5E,qBAAqB,EAAE,MAAM,OAAO,CAAC;IACrC,mHAAmH;IACnH,sBAAsB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,sGAAsG;IACtG,kBAAkB,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,KAAK,IAAI,CAAC;CACnF;AAED;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACpC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IAE7C,cAAc,IAAI,IAAI,CAAC;IACvB,OAAO,IAAI,IAAI,CAAC;IAChB,YAAY,CAAC,QAAQ,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC/C,oBAAoB,CAAC,cAAc,EAAE,cAAc,EAAE,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACxF,uBAAuB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACjD;AAED,wBAAgB,4BAA4B,CACxC,MAAM,EAAE,6BAA6B,EACrC,YAAY,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,EACrC,QAAQ,CAAC,EAAE,MAAM,mDASpB;AAED,wBAAgB,gCAAgC,CAC5C,IAAI,EAAE,OAAO,EACb,MAAM,EAAE,6BAA6B,EACrC,YAAY,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,EACrC,QAAQ,CAAC,EAAE,MAAM,mDASpB;AAED;;;GAGG;AACH,cAAM,iCAAkC,YAAW,uBAAuB,EAAE,6BAA6B;IAIjG,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,6BAA6B;IAH5D,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,uBAAuB,CAAC;gBAG3B,MAAM,EAAE,6BAA6B,EACxD,YAAY,EAAE,CAAC,OAAO,EAAE,6BAA6B,KAAK,uBAAuB;IAKrF;;OAEG;IACH,IAAW,eAAe,oBAAyC;IACnE,IAAW,eAAe,uBAAyC;IAE5D,cAAc;IACd,OAAO;IACP,YAAY,CAAC,QAAQ,EAAE,gBAAgB;IACvC,uBAAuB,CAAC,MAAM,EAAE,MAAM;IAEtC,oBAAoB,CAAC,cAAc,EAAE,cAAc,EAAE,OAAO,EAAE,kBAAkB;IAIvF;;OAEG;IAEH,IAAW,MAAM,qBAAiC;IAC3C,sBAAsB,CACzB,KAAK,EAAE,eAAe,EACtB,QAAQ,EAAE,eAAe,EACzB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS;IAIxB,qBAAqB;IAC5B,IAAW,sBAAsB,uBAAiD;IAC3E,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB;CAG9E;AAED;;;GAGG;AACH,cAAM,sBAAuB,SAAQ,iCAAiC;IAM9D,OAAO,CAAC,QAAQ,CAAC,YAAY;IALjC,OAAO,CAAC,cAAc,CAA8B;gBAGhD,MAAM,EAAE,6BAA6B,EACrC,YAAY,EAAE,CAAC,OAAO,EAAE,6BAA6B,KAAK,uBAAuB,EAChE,YAAY,EAAE,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC;IAM1D,OAAO,CAAC,gBAAgB,CAAkB;IAC1C,IAAW,eAAe,oBAEzB;IAEM,sBAAsB,CAAC,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS;IA0B5G,OAAO,CAAC,QAAQ,CAAC,0BAA0B,CAOzC;CACL;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,cAAM,sBAAuB,YAAW,uBAAuB;IAsBvD,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,SAAS,CAAC;IAtBtB,OAAO,CAAC,gBAAgB,CAAgC;IACxD,OAAO,CAAC,gBAAgB,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAQ;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,CAAmB;IAEpC,OAAO,CAAC,SAAS,CAA+B;IAEhD,IAAW,eAAe,IAAI,eAAe,CAE5C;IAED,OAAO,KAAK,QAAQ,GAEnB;IAED,IAAW,eAAe,IAAI,MAAM,GAAG,SAAS,CAE/C;gBAGoB,OAAO,EAAE,6BAA6B,EAC/C,SAAS,CAAC,oBAAQ;IAmC9B,OAAO,CAAC,gBAAgB;IAKxB,OAAO,CAAC,eAAe;IAKvB,OAAO,KAAK,iBAAiB,GAE5B;IAEM,OAAO;IAKP,cAAc;IASrB,OAAO,CAAC,sBAAsB;IAwB9B,OAAO,CAAC,sBAAsB;IA+B9B,OAAO,CAAC,yBAAyB;IAQ1B,uBAAuB,CAAC,MAAM,EAAE,MAAM;IAI7C;;;;;;;;OAQG;IACI,oBAAoB,CACvB,cAAc,EAAE,cAAc,EAC9B,OAAO,EAAE,kBAAkB;IA6C/B,OAAO,CAAC,kBAAkB;IA6D1B,SAAS,KAAK,UAAU,uEAEvB;IAEM,YAAY,CAAC,QAAQ,EAAE,gBAAgB;IAwB9C,SAAS,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,MAAM;CAGxC"}
@@ -4,12 +4,106 @@
4
4
  * Licensed under the MIT License.
5
5
  */
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.ConnectionStateHandler = void 0;
7
+ exports.createConnectionStateHandlerCore = exports.createConnectionStateHandler = void 0;
8
8
  const common_utils_1 = require("@fluidframework/common-utils");
9
9
  const telemetry_utils_1 = require("@fluidframework/telemetry-utils");
10
10
  const connectionState_1 = require("./connectionState");
11
11
  const catchUpMonitor_1 = require("./catchUpMonitor");
12
12
  const JoinOpTimeoutMs = 45000;
13
+ function createConnectionStateHandler(inputs, deltaManager, clientId) {
14
+ const mc = (0, telemetry_utils_1.loggerToMonitoringContext)(inputs.logger);
15
+ return createConnectionStateHandlerCore(mc.config.getBoolean("Fluid.Container.CatchUpBeforeDeclaringConnected") === true, inputs, deltaManager, clientId);
16
+ }
17
+ exports.createConnectionStateHandler = createConnectionStateHandler;
18
+ function createConnectionStateHandlerCore(wait, inputs, deltaManager, clientId) {
19
+ if (!wait) {
20
+ return new ConnectionStateHandler(inputs, clientId);
21
+ }
22
+ return new ConnectionStateCatchup(inputs, (handler) => new ConnectionStateHandler(handler, clientId), deltaManager);
23
+ }
24
+ exports.createConnectionStateHandlerCore = createConnectionStateHandlerCore;
25
+ /**
26
+ * Class that can be used as a base class for building IConnectionStateHandler adapters / pipeline.
27
+ * It implements both ends of communication interfaces and passes data back and forward
28
+ */
29
+ class ConnectionStateHandlerPassThrough {
30
+ constructor(inputs, pimplFactory) {
31
+ this.inputs = inputs;
32
+ this.pimpl = pimplFactory(this);
33
+ }
34
+ /**
35
+ * IConnectionStateHandler
36
+ */
37
+ get connectionState() { return this.pimpl.connectionState; }
38
+ get pendingClientId() { return this.pimpl.pendingClientId; }
39
+ containerSaved() { return this.pimpl.containerSaved(); }
40
+ dispose() { return this.pimpl.dispose(); }
41
+ initProtocol(protocol) { return this.pimpl.initProtocol(protocol); }
42
+ receivedDisconnectEvent(reason) { return this.pimpl.receivedDisconnectEvent(reason); }
43
+ receivedConnectEvent(connectionMode, details) {
44
+ return this.pimpl.receivedConnectEvent(connectionMode, details);
45
+ }
46
+ /**
47
+ * IConnectionStateHandlerInputs
48
+ */
49
+ get logger() { return this.inputs.logger; }
50
+ connectionStateChanged(value, oldState, reason) {
51
+ return this.inputs.connectionStateChanged(value, oldState, reason);
52
+ }
53
+ shouldClientJoinWrite() { return this.inputs.shouldClientJoinWrite(); }
54
+ get maxClientLeaveWaitTime() { return this.inputs.maxClientLeaveWaitTime; }
55
+ logConnectionIssue(eventName, details) {
56
+ return this.inputs.logConnectionIssue(eventName, details);
57
+ }
58
+ }
59
+ /**
60
+ * Implementation of IConnectionStateHandler pass-through adapter that waits for specific sequence number
61
+ * before raising connected event
62
+ */
63
+ class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
64
+ constructor(inputs, pimplFactory, deltaManager) {
65
+ super(inputs, pimplFactory);
66
+ this.deltaManager = deltaManager;
67
+ this.transitionToConnectedState = () => {
68
+ // Defensive measure, we should always be in Connecting state when this is called.
69
+ const state = this.pimpl.connectionState;
70
+ (0, common_utils_1.assert)(state === connectionState_1.ConnectionState.Connected, 0x3e5 /* invariant broken */);
71
+ (0, common_utils_1.assert)(this._connectionState === connectionState_1.ConnectionState.CatchingUp, 0x3e6 /* invariant broken */);
72
+ this._connectionState = connectionState_1.ConnectionState.Connected;
73
+ this.inputs.connectionStateChanged(connectionState_1.ConnectionState.Connected, connectionState_1.ConnectionState.CatchingUp, "caught up");
74
+ };
75
+ this._connectionState = this.pimpl.connectionState;
76
+ }
77
+ get connectionState() {
78
+ return this._connectionState;
79
+ }
80
+ connectionStateChanged(value, oldState, reason) {
81
+ var _a;
82
+ switch (value) {
83
+ case connectionState_1.ConnectionState.Connected:
84
+ (0, common_utils_1.assert)(this._connectionState === connectionState_1.ConnectionState.CatchingUp, 0x3e1 /* connectivity transitions */);
85
+ // Create catch-up monitor here (not earlier), as we might get more exact info by now about how far
86
+ // client is behind through join signal. This is only true if base layer uses signals (i.e. audience,
87
+ // not quorum, including for "rea" connections) to make decisions about moving to "connected" state.
88
+ // In addition to that, in its current form, doing this in ConnectionState.CatchingUp is dangerous as
89
+ // we might get callback right away, and it will screw up state transition (as code outside of switch
90
+ // statement will overwrite current state).
91
+ (0, common_utils_1.assert)(this.catchUpMonitor === undefined, 0x3eb /* catchUpMonitor should be gone */);
92
+ this.catchUpMonitor = new catchUpMonitor_1.CatchUpMonitor(this.deltaManager, this.transitionToConnectedState);
93
+ return;
94
+ case connectionState_1.ConnectionState.Disconnected:
95
+ (_a = this.catchUpMonitor) === null || _a === void 0 ? void 0 : _a.dispose();
96
+ this.catchUpMonitor = undefined;
97
+ break;
98
+ case connectionState_1.ConnectionState.CatchingUp:
99
+ (0, common_utils_1.assert)(this._connectionState === connectionState_1.ConnectionState.Disconnected, 0x3e3 /* connectivity transitions */);
100
+ break;
101
+ default:
102
+ }
103
+ this._connectionState = value;
104
+ this.inputs.connectionStateChanged(value, oldState, reason);
105
+ }
106
+ }
13
107
  /**
14
108
  * In the lifetime of a container, the connection will likely disconnect and reconnect periodically.
15
109
  * This class ensures that any ops sent by this container instance on previous connection are either
@@ -22,59 +116,49 @@ const JoinOpTimeoutMs = 45000;
22
116
  *
23
117
  * The job of this class is to encapsulate the transition period during reconnect, which is identified by
24
118
  * ConnectionState.CatchingUp. Specifically, before moving to Connected state with the new clientId, it ensures that:
25
- * (A) We process the Leave op for the previous clientId. This allows us to properly handle any acks from in-flight ops
26
- * that got sequenced with the old clientId (we'll recognize them as local ops). After the Leave op, any other
27
- * pending ops can safely be submitted with the new clientId without fear of duplication in the sequenced op stream.
28
- * (B) We process the Join op for the new clientId (identified when the underlying connection was first established),
29
- * indicating the service is ready to sequence ops sent with the new clientId.
30
- * (C) We process all ops known at the time the underlying connection was established (so we are "caught up")
31
119
  *
32
- * For (A) we give up waiting after some time (same timeout as server uses), and go ahead and transition to Connected.
33
- * For (B) we log telemetry if it takes too long, but still only transition to Connected when the Join op is processed
120
+ * a. We process the Leave op for the previous clientId. This allows us to properly handle any acks from in-flight ops
121
+ * that got sequenced with the old clientId (we'll recognize them as local ops). After the Leave op, any other
122
+ * pending ops can safely be submitted with the new clientId without fear of duplication in the sequenced op stream.
123
+ *
124
+ * b. We process the Join op for the new clientId (identified when the underlying connection was first established),
125
+ * indicating the service is ready to sequence ops sent with the new clientId.
126
+ *
127
+ * c. We process all ops known at the time the underlying connection was established (so we are "caught up")
128
+ *
129
+ * For (a) we give up waiting after some time (same timeout as server uses), and go ahead and transition to Connected.
130
+ *
131
+ * For (b) we log telemetry if it takes too long, but still only transition to Connected when the Join op is processed
34
132
  * and we are added to the Quorum.
35
- * For (C) this is optional behavior, controlled by the parameters of receivedConnectEvent
133
+ *
134
+ * For (c) this is optional behavior, controlled by the parameters of receivedConnectEvent
36
135
  */
37
136
  class ConnectionStateHandler {
38
- constructor(handler, logger, _clientId) {
137
+ constructor(handler, _clientId) {
39
138
  var _a;
40
139
  this.handler = handler;
41
- this.logger = logger;
42
140
  this._clientId = _clientId;
43
141
  this._connectionState = connectionState_1.ConnectionState.Disconnected;
44
- this.transitionToConnectedState = () => {
45
- // Defensive measure, we should always be in CatchingUp state when this is called.
46
- if (this._connectionState === connectionState_1.ConnectionState.CatchingUp) {
47
- this.setConnectionState(connectionState_1.ConnectionState.Connected);
48
- }
49
- else {
50
- this.logger.sendTelemetryEvent({
51
- eventName: "cannotTransitionToConnectedState",
52
- connectionState: connectionState_1.ConnectionState[this._connectionState],
53
- });
54
- }
55
- };
56
142
  this.prevClientLeftTimer = new common_utils_1.Timer(
57
143
  // Default is 5 min for which we are going to wait for its own "leave" message. This is same as
58
144
  // the max time on server after which leave op is sent.
59
145
  (_a = this.handler.maxClientLeaveWaitTime) !== null && _a !== void 0 ? _a : 300000, () => {
60
- (0, common_utils_1.assert)(!this.connected, 0x2ac /* "Connected when timeout waiting for leave from previous session fired!" */);
146
+ (0, common_utils_1.assert)(this.connectionState !== connectionState_1.ConnectionState.Connected, 0x2ac /* "Connected when timeout waiting for leave from previous session fired!" */);
61
147
  this.applyForConnectedState("timeout");
62
148
  });
63
149
  // Based on recent data, it looks like majority of cases where we get stuck are due to really slow or
64
150
  // timing out ops fetches. So attempt recovery infrequently. Also fetch uses 30 second timeout, so
65
151
  // if retrying fixes the problem, we should not see these events.
66
152
  this.joinOpTimer = new common_utils_1.Timer(JoinOpTimeoutMs, () => {
67
- var _a;
68
153
  // I've observed timer firing within couple ms from disconnect event, looks like
69
154
  // queued timer callback is not cancelled if timer is cancelled while callback sits in the queue.
70
155
  if (this.connectionState !== connectionState_1.ConnectionState.CatchingUp) {
71
156
  return;
72
157
  }
73
- const quorumClients = this.handler.quorumClients();
74
158
  const details = {
75
- quorumInitialized: quorumClients !== undefined,
159
+ protocolInitialized: this.protocol !== undefined,
76
160
  pendingClientId: this.pendingClientId,
77
- inQuorum: (quorumClients === null || quorumClients === void 0 ? void 0 : quorumClients.getMember((_a = this.pendingClientId) !== null && _a !== void 0 ? _a : "")) !== undefined,
161
+ clientJoined: this.hasMember(this.pendingClientId),
78
162
  waitingForLeaveOp: this.waitingForLeaveOp,
79
163
  };
80
164
  this.handler.logConnectionIssue("NoJoinOp", details);
@@ -83,9 +167,6 @@ class ConnectionStateHandler {
83
167
  get connectionState() {
84
168
  return this._connectionState;
85
169
  }
86
- get connected() {
87
- return this.connectionState === connectionState_1.ConnectionState.Connected;
88
- }
89
170
  get clientId() {
90
171
  return this._clientId;
91
172
  }
@@ -128,7 +209,7 @@ class ConnectionStateHandler {
128
209
  }
129
210
  // Start the event in case we are waiting for leave or timeout.
130
211
  if (this.waitingForLeaveOp) {
131
- this.waitEvent = telemetry_utils_1.PerformanceEvent.start(this.logger, {
212
+ this.waitEvent = telemetry_utils_1.PerformanceEvent.start(this.handler.logger, {
132
213
  eventName: "WaitBeforeClientLeave",
133
214
  details: JSON.stringify({
134
215
  waitOnClientId: this._clientId,
@@ -140,25 +221,21 @@ class ConnectionStateHandler {
140
221
  }
141
222
  }
142
223
  applyForConnectedState(source) {
143
- var _a, _b;
144
- const quorumClients = this.handler.quorumClients();
145
- (0, common_utils_1.assert)(quorumClients !== undefined, 0x236 /* "In all cases it should be already installed" */);
146
- (0, common_utils_1.assert)(this.waitingForLeaveOp === false ||
147
- (this.clientId !== undefined && quorumClients.getMember(this.clientId) !== undefined), 0x2e2 /* "Must only wait for leave message when clientId in quorum" */);
224
+ var _a;
225
+ (0, common_utils_1.assert)(this.protocol !== undefined, 0x236 /* "In all cases it should be already installed" */);
226
+ (0, common_utils_1.assert)(!this.waitingForLeaveOp || this.hasMember(this.clientId), 0x2e2 /* "Must only wait for leave message when clientId in quorum" */);
148
227
  // Move to connected state only if we are in Connecting state, we have seen our join op
149
228
  // and there is no timer running which means we are not waiting for previous client to leave
150
229
  // or timeout has occurred while doing so.
151
230
  if (this.pendingClientId !== this.clientId
152
- && this.pendingClientId !== undefined
153
- && quorumClients.getMember(this.pendingClientId) !== undefined
231
+ && this.hasMember(this.pendingClientId)
154
232
  && !this.waitingForLeaveOp) {
155
233
  (_a = this.waitEvent) === null || _a === void 0 ? void 0 : _a.end({ source });
156
- (0, common_utils_1.assert)(this.catchUpMonitor !== undefined, 0x37d /* catchUpMonitor should always be set if pendingClientId is set */);
157
- this.catchUpMonitor.on("caughtUp", this.transitionToConnectedState);
234
+ this.setConnectionState(connectionState_1.ConnectionState.Connected);
158
235
  }
159
236
  else {
160
237
  // Adding this event temporarily so that we can get help debugging if something goes wrong.
161
- this.logger.sendTelemetryEvent({
238
+ this.handler.logger.sendTelemetryEvent({
162
239
  eventName: "connectedStateRejected",
163
240
  category: source === "timeout" ? "error" : "generic",
164
241
  details: JSON.stringify({
@@ -166,7 +243,7 @@ class ConnectionStateHandler {
166
243
  pendingClientId: this.pendingClientId,
167
244
  clientId: this.clientId,
168
245
  waitingForLeaveOp: this.waitingForLeaveOp,
169
- inQuorum: (quorumClients === null || quorumClients === void 0 ? void 0 : quorumClients.getMember((_b = this.pendingClientId) !== null && _b !== void 0 ? _b : "")) !== undefined,
246
+ clientJoined: this.hasMember(this.pendingClientId),
170
247
  }),
171
248
  });
172
249
  }
@@ -190,18 +267,16 @@ class ConnectionStateHandler {
190
267
  * @param deltaManager - DeltaManager to be used for delaying Connected transition until caught up.
191
268
  * If it's undefined, then don't delay and transition to Connected as soon as Leave/Join op are accounted for
192
269
  */
193
- receivedConnectEvent(connectionMode, details, deltaManager) {
194
- var _a;
270
+ receivedConnectEvent(connectionMode, details) {
195
271
  const oldState = this._connectionState;
196
272
  this._connectionState = connectionState_1.ConnectionState.CatchingUp;
197
273
  const writeConnection = connectionMode === "write";
198
- (0, common_utils_1.assert)(!this.handler.shouldClientJoinWrite() || writeConnection, 0x30a /* shouldClientJoinWrite should imply this is a writeConnection */);
199
- (0, common_utils_1.assert)(!this.waitingForLeaveOp || writeConnection, 0x2a6 /* "waitingForLeaveOp should imply writeConnection (we need to be ready to flush pending ops)" */);
200
- // Defensive measure in case catchUpMonitor from previous connection attempt wasn't already cleared
201
- (_a = this.catchUpMonitor) === null || _a === void 0 ? void 0 : _a.dispose();
202
- // Note that this may be undefined since the connection is established proactively on load
203
- // and the quorum may still be under initialization.
204
- const quorumClients = this.handler.quorumClients();
274
+ // The following checks are wrong. They are only valid if user has write access to a file.
275
+ // If user lost such access mid-session, user will not be able to get "write" connection.
276
+ // assert(!this.handler.shouldClientJoinWrite() || writeConnection,
277
+ // 0x30a /* shouldClientJoinWrite should imply this is a writeConnection */);
278
+ // assert(!this.waitingForLeaveOp || writeConnection,
279
+ // 0x2a6 /* "waitingForLeaveOp should imply writeConnection (we need to be ready to flush pending ops)" */);
205
280
  // Stash the clientID to detect when transitioning from connecting (socket.io channel open) to connected
206
281
  // (have received the join message for the client ID)
207
282
  // This is especially important in the reconnect case. It's possible there could be outstanding
@@ -209,18 +284,14 @@ class ConnectionStateHandler {
209
284
  // join message. after we see the join message for our new connection with our new client id,
210
285
  // we know there can no longer be outstanding ops that we sent with the previous client id.
211
286
  this._pendingClientId = details.clientId;
212
- // We may want to catch up to known ops as of now before transitioning to Connected state
213
- this.catchUpMonitor = deltaManager !== undefined
214
- ? new catchUpMonitor_1.CatchUpMonitor(deltaManager)
215
- : new catchUpMonitor_1.ImmediateCatchUpMonitor();
216
287
  // IMPORTANT: Report telemetry after we set _pendingClientId, but before transitioning to Connected state
217
- this.handler.logConnectionStateChangeTelemetry(connectionState_1.ConnectionState.CatchingUp, oldState);
288
+ this.handler.connectionStateChanged(connectionState_1.ConnectionState.CatchingUp, oldState);
218
289
  // For write connections, this pending clientId could be in the quorum already (i.e. join op already processed).
219
290
  // We are fetching ops from storage in parallel to connecting to Relay Service,
220
291
  // and given async processes, it's possible that we have already processed our own join message before
221
292
  // connection was fully established.
222
- // If quorumClients itself is undefined, we expect it will process the join op after it's initialized.
223
- const waitingForJoinOp = writeConnection && (quorumClients === null || quorumClients === void 0 ? void 0 : quorumClients.getMember(this._pendingClientId)) === undefined;
293
+ // If protocol is not initialized yet, we expect it will process the join op after it's initialized.
294
+ const waitingForJoinOp = writeConnection && !this.hasMember(this._pendingClientId);
224
295
  if (waitingForJoinOp) {
225
296
  // Previous client left, and we are waiting for our own join op. When it is processed we'll join the quorum
226
297
  // and attempt to transition to Connected state via receivedAddMemberEvent.
@@ -228,33 +299,24 @@ class ConnectionStateHandler {
228
299
  }
229
300
  else if (!this.waitingForLeaveOp) {
230
301
  // We're not waiting for Join or Leave op (if read-only connection those don't even apply),
231
- // but we do need to wait until we are caught up (to now-known ops) before transitioning to Connected state.
232
- this.catchUpMonitor.on("caughtUp", this.transitionToConnectedState);
302
+ // go ahead and declare the state to be Connected!
303
+ // If we are waiting for Leave op still, do nothing for now, we will transition to Connected later.
304
+ this.setConnectionState(connectionState_1.ConnectionState.Connected);
233
305
  }
234
306
  // else - We are waiting for Leave op still, do nothing for now, we will transition to Connected later
235
307
  }
236
- /** Clear all the state used during the Connecting phase (set in receivedConnectEvent) */
237
- clearPendingConnectionState() {
238
- var _a;
239
- this._pendingClientId = undefined;
240
- (_a = this.catchUpMonitor) === null || _a === void 0 ? void 0 : _a.dispose();
241
- this.catchUpMonitor = undefined;
242
- if (this.joinOpTimer.hasTimer) {
243
- this.stopJoinOpTimer();
244
- }
245
- }
246
308
  setConnectionState(value, reason) {
309
+ var _a, _b;
247
310
  if (this.connectionState === value) {
248
311
  // Already in the desired state - exit early
249
- this.logger.sendErrorEvent({ eventName: "setConnectionStateSame", value });
312
+ this.handler.logger.sendErrorEvent({ eventName: "setConnectionStateSame", value });
250
313
  return;
251
314
  }
252
315
  const oldState = this._connectionState;
253
316
  this._connectionState = value;
254
- const quorumClients = this.handler.quorumClients();
255
317
  let client;
256
318
  if (this._clientId !== undefined) {
257
- client = quorumClients === null || quorumClients === void 0 ? void 0 : quorumClients.getMember(this._clientId);
319
+ client = (_b = (_a = this.protocol) === null || _a === void 0 ? void 0 : _a.quorum) === null || _b === void 0 ? void 0 : _b.getMember(this._clientId);
258
320
  }
259
321
  if (value === connectionState_1.ConnectionState.Connected) {
260
322
  (0, common_utils_1.assert)(oldState === connectionState_1.ConnectionState.CatchingUp, 0x1d8 /* "Should only transition from Connecting state" */);
@@ -266,23 +328,26 @@ class ConnectionStateHandler {
266
328
  }
267
329
  else if (value === connectionState_1.ConnectionState.Disconnected) {
268
330
  // Clear pending state immediately to prepare for reconnect
269
- this.clearPendingConnectionState();
270
- // Only wait for "leave" message if the connected client exists in the quorum because only the write
271
- // client will exist in the quorum and only for those clients we will receive "removeMember" event and
272
- // the client has some unacked ops.
273
- // Also server would not accept ops from read client. Also check if the timer is not already running as
331
+ this._pendingClientId = undefined;
332
+ if (this.joinOpTimer.hasTimer) {
333
+ this.stopJoinOpTimer();
334
+ }
335
+ // Only wait for "leave" message if the connected client exists in the quorum and had some non-acked ops
336
+ // Also check if the timer is not already running as
274
337
  // we could receive "Disconnected" event multiple times without getting connected and in that case we
275
338
  // don't want to reset the timer as we still want to wait on original client which started this timer.
276
339
  if (client !== undefined
277
340
  && this.handler.shouldClientJoinWrite()
278
- && this.prevClientLeftTimer.hasTimer === false) {
341
+ && !this.waitingForLeaveOp // same as !this.prevClientLeftTimer.hasTimer
342
+ ) {
279
343
  this.prevClientLeftTimer.restart();
280
344
  }
281
345
  else {
282
346
  // Adding this event temporarily so that we can get help debugging if something goes wrong.
283
- this.logger.sendTelemetryEvent({
347
+ this.handler.logger.sendTelemetryEvent({
284
348
  eventName: "noWaitOnDisconnected",
285
349
  details: JSON.stringify({
350
+ clientId: this._clientId,
286
351
  inQuorum: client !== undefined,
287
352
  waitingForLeaveOp: this.waitingForLeaveOp,
288
353
  hadOutstandingOps: this.handler.shouldClientJoinWrite(),
@@ -291,22 +356,38 @@ class ConnectionStateHandler {
291
356
  }
292
357
  }
293
358
  // Report transition before we propagate event across layers
294
- this.handler.logConnectionStateChangeTelemetry(this._connectionState, oldState, reason);
295
- // Propagate event across layers
296
- this.handler.connectionStateChanged();
359
+ this.handler.connectionStateChanged(this._connectionState, oldState, reason);
360
+ }
361
+ // Helper method to switch between quorum and audience.
362
+ // Old design was checking only quorum for "write" clients.
363
+ // Latest change checks audience for all types of connections.
364
+ get membership() {
365
+ var _a;
366
+ return (_a = this.protocol) === null || _a === void 0 ? void 0 : _a.quorum;
297
367
  }
298
368
  initProtocol(protocol) {
299
- protocol.quorum.on("addMember", (clientId, _details) => {
369
+ var _a, _b;
370
+ this.protocol = protocol;
371
+ (_a = this.membership) === null || _a === void 0 ? void 0 : _a.on("addMember", (clientId) => {
300
372
  this.receivedAddMemberEvent(clientId);
301
373
  });
302
- protocol.quorum.on("removeMember", (clientId) => {
374
+ (_b = this.membership) === null || _b === void 0 ? void 0 : _b.on("removeMember", (clientId) => {
303
375
  this.receivedRemoveMemberEvent(clientId);
304
376
  });
377
+ // Very unlikely race condition, but theoretically can happen - our new connection is already
378
+ // summarized and we are loading from such summary.
379
+ if (this.hasMember(this.pendingClientId)) {
380
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
381
+ this.receivedAddMemberEvent(this.pendingClientId);
382
+ }
305
383
  // if we have a clientId from a previous container we need to wait for its leave message
306
- if (this.clientId !== undefined && protocol.quorum.getMember(this.clientId) !== undefined) {
384
+ if (this.clientId !== undefined && this.hasMember(this.clientId)) {
307
385
  this.prevClientLeftTimer.restart();
308
386
  }
309
387
  }
388
+ hasMember(clientId) {
389
+ var _a;
390
+ return ((_a = this.membership) === null || _a === void 0 ? void 0 : _a.getMember(clientId !== null && clientId !== void 0 ? clientId : "")) !== undefined;
391
+ }
310
392
  }
311
- exports.ConnectionStateHandler = ConnectionStateHandler;
312
393
  //# sourceMappingURL=connectionStateHandler.js.map