@fluidframework/container-loader 2.0.0-internal.6.0.0 → 2.0.0-internal.6.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 (59) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/connectionManager.d.ts +3 -3
  3. package/dist/connectionManager.d.ts.map +1 -1
  4. package/dist/connectionManager.js +33 -24
  5. package/dist/connectionManager.js.map +1 -1
  6. package/dist/connectionStateHandler.d.ts +14 -14
  7. package/dist/connectionStateHandler.d.ts.map +1 -1
  8. package/dist/connectionStateHandler.js +17 -12
  9. package/dist/connectionStateHandler.js.map +1 -1
  10. package/dist/container.d.ts.map +1 -1
  11. package/dist/container.js +26 -38
  12. package/dist/container.js.map +1 -1
  13. package/dist/contracts.d.ts +11 -7
  14. package/dist/contracts.d.ts.map +1 -1
  15. package/dist/contracts.js.map +1 -1
  16. package/dist/deltaManager.d.ts +4 -4
  17. package/dist/deltaManager.d.ts.map +1 -1
  18. package/dist/deltaManager.js +5 -4
  19. package/dist/deltaManager.js.map +1 -1
  20. package/dist/packageVersion.d.ts +1 -1
  21. package/dist/packageVersion.js +1 -1
  22. package/dist/packageVersion.js.map +1 -1
  23. package/dist/protocol.d.ts +4 -2
  24. package/dist/protocol.d.ts.map +1 -1
  25. package/dist/protocol.js +23 -1
  26. package/dist/protocol.js.map +1 -1
  27. package/lib/connectionManager.d.ts +3 -3
  28. package/lib/connectionManager.d.ts.map +1 -1
  29. package/lib/connectionManager.js +33 -24
  30. package/lib/connectionManager.js.map +1 -1
  31. package/lib/connectionStateHandler.d.ts +14 -14
  32. package/lib/connectionStateHandler.d.ts.map +1 -1
  33. package/lib/connectionStateHandler.js +17 -12
  34. package/lib/connectionStateHandler.js.map +1 -1
  35. package/lib/container.d.ts.map +1 -1
  36. package/lib/container.js +27 -39
  37. package/lib/container.js.map +1 -1
  38. package/lib/contracts.d.ts +11 -7
  39. package/lib/contracts.d.ts.map +1 -1
  40. package/lib/contracts.js.map +1 -1
  41. package/lib/deltaManager.d.ts +4 -4
  42. package/lib/deltaManager.d.ts.map +1 -1
  43. package/lib/deltaManager.js +5 -4
  44. package/lib/deltaManager.js.map +1 -1
  45. package/lib/packageVersion.d.ts +1 -1
  46. package/lib/packageVersion.js +1 -1
  47. package/lib/packageVersion.js.map +1 -1
  48. package/lib/protocol.d.ts +4 -2
  49. package/lib/protocol.d.ts.map +1 -1
  50. package/lib/protocol.js +23 -1
  51. package/lib/protocol.js.map +1 -1
  52. package/package.json +13 -17
  53. package/src/connectionManager.ts +46 -31
  54. package/src/connectionStateHandler.ts +28 -36
  55. package/src/container.ts +46 -51
  56. package/src/contracts.ts +12 -6
  57. package/src/deltaManager.ts +19 -13
  58. package/src/packageVersion.ts +1 -1
  59. package/src/protocol.ts +33 -1
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AACvE,OAAO,EACN,gBAAgB,IAAI,oBAAoB,EACxC,eAAe,EACf,iBAAiB,EACjB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACN,mBAAmB,EAEnB,cAAc,EACd,MAAM,sCAAsC,CAAC;AAG9C,eAAO,MAAM,kBAAkB,GAAa,CAAC;AAG7C,oBAAY,UAAU;IACrB,UAAU,SAAS;IACnB,WAAW,UAAU;IACrB,KAAK,UAAU;CACf;AAED;;GAEG;AACH,oBAAY,sBAAsB,GAAG,CACpC,UAAU,EAAE,mBAAmB,EAC/B,QAAQ,EAAE,eAAe,EACzB,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,MAAM,KAC7C,gBAAgB,CAAC;AAEtB,MAAM,WAAW,gBAAiB,SAAQ,oBAAoB;IAC7D,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,aAAa,CAAC,OAAO,EAAE,cAAc,OAAE;CACvC;AAED,qBAAa,eAAgB,SAAQ,iBAAkB,YAAW,gBAAgB;IAKhF,QAAQ,CAAC,QAAQ,EAAE,cAAc;gBAHjC,UAAU,EAAE,mBAAmB,EAC/B,cAAc,EAAE,eAAe,EAC/B,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,MAAM,EACxC,QAAQ,EAAE,cAAc;IAqB3B,aAAa,CAAC,OAAO,EAAE,cAAc;CAgC5C;AAED;;;;GAIG;AACH,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,cAAc,WAWzE"}
1
+ {"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,uCAAuC,CAAC;AAEvE,OAAO,EACN,gBAAgB,IAAI,oBAAoB,EACxC,eAAe,EACf,iBAAiB,EACjB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EACN,mBAAmB,EACnB,qBAAqB,EAErB,yBAAyB,EAEzB,cAAc,EAEd,MAAM,sCAAsC,CAAC;AAG9C,eAAO,MAAM,kBAAkB,GAAa,CAAC;AAG7C,oBAAY,UAAU;IACrB,UAAU,SAAS;IACnB,WAAW,UAAU;IACrB,KAAK,UAAU;CACf;AAED;;GAEG;AACH,oBAAY,sBAAsB,GAAG,CACpC,UAAU,EAAE,mBAAmB,EAC/B,QAAQ,EAAE,eAAe,EACzB,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,MAAM,KAC7C,gBAAgB,CAAC;AAEtB,MAAM,WAAW,gBAAiB,SAAQ,oBAAoB;IAC7D,QAAQ,CAAC,QAAQ,EAAE,cAAc,CAAC;IAClC,aAAa,CAAC,OAAO,EAAE,cAAc,OAAE;CACvC;AAED,qBAAa,eAAgB,SAAQ,iBAAkB,YAAW,gBAAgB;aAKhE,QAAQ,EAAE,cAAc;IACxC,OAAO,CAAC,QAAQ,CAAC,oBAAoB;gBAJrC,UAAU,EAAE,mBAAmB,EAC/B,cAAc,EAAE,eAAe,EAC/B,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,KAAK,MAAM,EACjC,QAAQ,EAAE,cAAc,EACvB,oBAAoB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO;IAqB9D,cAAc,CACpB,OAAO,EAAE,yBAAyB,EAClC,KAAK,EAAE,OAAO,GACZ,qBAAqB;IAuBjB,aAAa,CAAC,OAAO,EAAE,cAAc;CAgC5C;AAED;;;;GAIG;AACH,wBAAgB,kCAAkC,CAAC,OAAO,EAAE,cAAc,WAWzE"}
package/lib/protocol.js CHANGED
@@ -2,7 +2,9 @@
2
2
  * Copyright (c) Microsoft Corporation and contributors. All rights reserved.
3
3
  * Licensed under the MIT License.
4
4
  */
5
+ import { canBeCoalescedByService } from "@fluidframework/driver-utils";
5
6
  import { ProtocolOpHandler, } from "@fluidframework/protocol-base";
7
+ import { MessageType, } from "@fluidframework/protocol-definitions";
6
8
  // "term" was an experimental feature that is being removed. The only safe value to use is 1.
7
9
  export const OnlyValidTermValue = 1;
8
10
  // ADO: #1986: Start using enum from protocol-base.
@@ -13,9 +15,10 @@ export var SignalType;
13
15
  SignalType["Clear"] = "clear";
14
16
  })(SignalType || (SignalType = {}));
15
17
  export class ProtocolHandler extends ProtocolOpHandler {
16
- constructor(attributes, quorumSnapshot, sendProposal, audience) {
18
+ constructor(attributes, quorumSnapshot, sendProposal, audience, shouldClientHaveLeft) {
17
19
  super(attributes.minimumSequenceNumber, attributes.sequenceNumber, quorumSnapshot.members, quorumSnapshot.proposals, quorumSnapshot.values, sendProposal);
18
20
  this.audience = audience;
21
+ this.shouldClientHaveLeft = shouldClientHaveLeft;
19
22
  // Join / leave signals are ignored for "write" clients in favor of join / leave ops
20
23
  this.quorum.on("addMember", (clientId, details) => audience.addMember(clientId, details.client));
21
24
  this.quorum.on("removeMember", (clientId) => audience.removeMember(clientId));
@@ -23,6 +26,25 @@ export class ProtocolHandler extends ProtocolOpHandler {
23
26
  this.audience.addMember(clientId, details.client);
24
27
  }
25
28
  }
29
+ processMessage(message, local) {
30
+ const client = this.quorum.getMember(message.clientId);
31
+ // Check and report if we're getting messages from a clientId that we previously
32
+ // flagged as shouldHaveLeft, or from a client that's not in the quorum but should be
33
+ if (message.clientId != null) {
34
+ if (client === undefined && message.type !== MessageType.ClientJoin) {
35
+ // pre-0.58 error message: messageClientIdMissingFromQuorum
36
+ throw new Error("Remote message's clientId is missing from the quorum");
37
+ }
38
+ // Here checking canBeCoalescedByService is used as an approximation of "is benign to process despite being unexpected".
39
+ // It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the
40
+ // document we don't need to blow up aggressively.
41
+ if (this.shouldClientHaveLeft(message.clientId) && !canBeCoalescedByService(message)) {
42
+ // pre-0.58 error message: messageClientIdShouldHaveLeft
43
+ throw new Error("Remote message's clientId already should have left");
44
+ }
45
+ }
46
+ return super.processMessage(message, local);
47
+ }
26
48
  processSignal(message) {
27
49
  const innerContent = message.content;
28
50
  switch (innerContent.type) {
@@ -1 +1 @@
1
- {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IAudienceOwner } from \"@fluidframework/container-definitions\";\nimport {\n\tIProtocolHandler as IBaseProtocolHandler,\n\tIQuorumSnapshot,\n\tProtocolOpHandler,\n} from \"@fluidframework/protocol-base\";\nimport {\n\tIDocumentAttributes,\n\tISignalClient,\n\tISignalMessage,\n} from \"@fluidframework/protocol-definitions\";\n\n// \"term\" was an experimental feature that is being removed. The only safe value to use is 1.\nexport const OnlyValidTermValue = 1 as const;\n\n// ADO: #1986: Start using enum from protocol-base.\nexport enum SignalType {\n\tClientJoin = \"join\", // same value as MessageType.ClientJoin,\n\tClientLeave = \"leave\", // same value as MessageType.ClientLeave,\n\tClear = \"clear\", // used only by client for synthetic signals\n}\n\n/**\n * Function to be used for creating a protocol handler.\n */\nexport type ProtocolHandlerBuilder = (\n\tattributes: IDocumentAttributes,\n\tsnapshot: IQuorumSnapshot,\n\tsendProposal: (key: string, value: any) => number,\n) => IProtocolHandler;\n\nexport interface IProtocolHandler extends IBaseProtocolHandler {\n\treadonly audience: IAudienceOwner;\n\tprocessSignal(message: ISignalMessage);\n}\n\nexport class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandler {\n\tconstructor(\n\t\tattributes: IDocumentAttributes,\n\t\tquorumSnapshot: IQuorumSnapshot,\n\t\tsendProposal: (key: string, value: any) => number,\n\t\treadonly audience: IAudienceOwner,\n\t) {\n\t\tsuper(\n\t\t\tattributes.minimumSequenceNumber,\n\t\t\tattributes.sequenceNumber,\n\t\t\tquorumSnapshot.members,\n\t\t\tquorumSnapshot.proposals,\n\t\t\tquorumSnapshot.values,\n\t\t\tsendProposal,\n\t\t);\n\n\t\t// Join / leave signals are ignored for \"write\" clients in favor of join / leave ops\n\t\tthis.quorum.on(\"addMember\", (clientId, details) =>\n\t\t\taudience.addMember(clientId, details.client),\n\t\t);\n\t\tthis.quorum.on(\"removeMember\", (clientId) => audience.removeMember(clientId));\n\t\tfor (const [clientId, details] of this.quorum.getMembers()) {\n\t\t\tthis.audience.addMember(clientId, details.client);\n\t\t}\n\t}\n\n\tpublic processSignal(message: ISignalMessage) {\n\t\tconst innerContent = message.content as { content: any; type: string };\n\t\tswitch (innerContent.type) {\n\t\t\tcase SignalType.Clear: {\n\t\t\t\tconst members = this.audience.getMembers();\n\t\t\t\tfor (const [clientId, client] of members) {\n\t\t\t\t\tif (client.mode === \"read\") {\n\t\t\t\t\t\tthis.audience.removeMember(clientId);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SignalType.ClientJoin: {\n\t\t\t\tconst newClient = innerContent.content as ISignalClient;\n\t\t\t\t// Ignore write clients - quorum will control such clients.\n\t\t\t\tif (newClient.client.mode === \"read\") {\n\t\t\t\t\tthis.audience.addMember(newClient.clientId, newClient.client);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SignalType.ClientLeave: {\n\t\t\t\tconst leftClientId = innerContent.content as string;\n\t\t\t\t// Ignore write clients - quorum will control such clients.\n\t\t\t\tif (this.audience.getMember(leftClientId)?.mode === \"read\") {\n\t\t\t\t\tthis.audience.removeMember(leftClientId);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/**\n * Function to check whether the protocol handler should process the Signal.\n * The protocol handler should strictly handle only ClientJoin, ClientLeave\n * and Clear signal types.\n */\nexport function protocolHandlerShouldProcessSignal(message: ISignalMessage) {\n\t// Signal originates from server\n\tif (message.clientId === null) {\n\t\tconst innerContent = message.content as { content: unknown; type: string };\n\t\treturn (\n\t\t\tinnerContent.type === SignalType.Clear ||\n\t\t\tinnerContent.type === SignalType.ClientJoin ||\n\t\t\tinnerContent.type === SignalType.ClientLeave\n\t\t);\n\t}\n\treturn false;\n}\n"]}
1
+ {"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAGN,iBAAiB,GACjB,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAON,WAAW,GACX,MAAM,sCAAsC,CAAC;AAE9C,8FAA8F;AAC9F,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAU,CAAC;AAE7C,mDAAmD;AACnD,MAAM,CAAN,IAAY,UAIX;AAJD,WAAY,UAAU;IACrB,iCAAmB,CAAA;IACnB,mCAAqB,CAAA;IACrB,6BAAe,CAAA;AAChB,CAAC,EAJW,UAAU,KAAV,UAAU,QAIrB;AAgBD,MAAM,OAAO,eAAgB,SAAQ,iBAAiB;IACrD,YACC,UAA+B,EAC/B,cAA+B,EAC/B,YAAiD,EACjC,QAAwB,EACvB,oBAAmD;QAEpE,KAAK,CACJ,UAAU,CAAC,qBAAqB,EAChC,UAAU,CAAC,cAAc,EACzB,cAAc,CAAC,OAAO,EACtB,cAAc,CAAC,SAAS,EACxB,cAAc,CAAC,MAAM,EACrB,YAAY,CACZ,CAAC;QAVc,aAAQ,GAAR,QAAQ,CAAgB;QACvB,yBAAoB,GAApB,oBAAoB,CAA+B;QAWpE,oFAAoF;QACpF,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,CACjD,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAC5C,CAAC;QACF,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,cAAc,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC9E,KAAK,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE;YAC3D,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;SAClD;IACF,CAAC;IAEM,cAAc,CACpB,OAAkC,EAClC,KAAc;QAEd,MAAM,MAAM,GAAiC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAErF,gFAAgF;QAChF,qFAAqF;QACrF,IAAI,OAAO,CAAC,QAAQ,IAAI,IAAI,EAAE;YAC7B,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,CAAC,UAAU,EAAE;gBACpE,2DAA2D;gBAC3D,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;aACxE;YAED,wHAAwH;YACxH,sHAAsH;YACtH,kDAAkD;YAClD,IAAI,IAAI,CAAC,oBAAoB,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,uBAAuB,CAAC,OAAO,CAAC,EAAE;gBACrF,wDAAwD;gBACxD,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;aACtE;SACD;QAED,OAAO,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;IAC7C,CAAC;IAEM,aAAa,CAAC,OAAuB;QAC3C,MAAM,YAAY,GAAG,OAAO,CAAC,OAAyC,CAAC;QACvE,QAAQ,YAAY,CAAC,IAAI,EAAE;YAC1B,KAAK,UAAU,CAAC,KAAK,CAAC,CAAC;gBACtB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,UAAU,EAAE,CAAC;gBAC3C,KAAK,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,OAAO,EAAE;oBACzC,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE;wBAC3B,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC;qBACrC;iBACD;gBACD,MAAM;aACN;YACD,KAAK,UAAU,CAAC,UAAU,CAAC,CAAC;gBAC3B,MAAM,SAAS,GAAG,YAAY,CAAC,OAAwB,CAAC;gBACxD,2DAA2D;gBAC3D,IAAI,SAAS,CAAC,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE;oBACrC,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,SAAS,CAAC,QAAQ,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;iBAC9D;gBACD,MAAM;aACN;YACD,KAAK,UAAU,CAAC,WAAW,CAAC,CAAC;gBAC5B,MAAM,YAAY,GAAG,YAAY,CAAC,OAAiB,CAAC;gBACpD,2DAA2D;gBAC3D,IAAI,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,YAAY,CAAC,EAAE,IAAI,KAAK,MAAM,EAAE;oBAC3D,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;iBACzC;gBACD,MAAM;aACN;YACD;gBACC,MAAM;SACP;IACF,CAAC;CACD;AAED;;;;GAIG;AACH,MAAM,UAAU,kCAAkC,CAAC,OAAuB;IACzE,gCAAgC;IAChC,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,EAAE;QAC9B,MAAM,YAAY,GAAG,OAAO,CAAC,OAA6C,CAAC;QAC3E,OAAO,CACN,YAAY,CAAC,IAAI,KAAK,UAAU,CAAC,KAAK;YACtC,YAAY,CAAC,IAAI,KAAK,UAAU,CAAC,UAAU;YAC3C,YAAY,CAAC,IAAI,KAAK,UAAU,CAAC,WAAW,CAC5C,CAAC;KACF;IACD,OAAO,KAAK,CAAC;AACd,CAAC","sourcesContent":["/*!\n * Copyright (c) Microsoft Corporation and contributors. All rights reserved.\n * Licensed under the MIT License.\n */\n\nimport { IAudienceOwner } from \"@fluidframework/container-definitions\";\nimport { canBeCoalescedByService } from \"@fluidframework/driver-utils\";\nimport {\n\tIProtocolHandler as IBaseProtocolHandler,\n\tIQuorumSnapshot,\n\tProtocolOpHandler,\n} from \"@fluidframework/protocol-base\";\nimport {\n\tIDocumentAttributes,\n\tIProcessMessageResult,\n\tISequencedClient,\n\tISequencedDocumentMessage,\n\tISignalClient,\n\tISignalMessage,\n\tMessageType,\n} from \"@fluidframework/protocol-definitions\";\n\n// \"term\" was an experimental feature that is being removed. The only safe value to use is 1.\nexport const OnlyValidTermValue = 1 as const;\n\n// ADO: #1986: Start using enum from protocol-base.\nexport enum SignalType {\n\tClientJoin = \"join\", // same value as MessageType.ClientJoin,\n\tClientLeave = \"leave\", // same value as MessageType.ClientLeave,\n\tClear = \"clear\", // used only by client for synthetic signals\n}\n\n/**\n * Function to be used for creating a protocol handler.\n */\nexport type ProtocolHandlerBuilder = (\n\tattributes: IDocumentAttributes,\n\tsnapshot: IQuorumSnapshot,\n\tsendProposal: (key: string, value: any) => number,\n) => IProtocolHandler;\n\nexport interface IProtocolHandler extends IBaseProtocolHandler {\n\treadonly audience: IAudienceOwner;\n\tprocessSignal(message: ISignalMessage);\n}\n\nexport class ProtocolHandler extends ProtocolOpHandler implements IProtocolHandler {\n\tconstructor(\n\t\tattributes: IDocumentAttributes,\n\t\tquorumSnapshot: IQuorumSnapshot,\n\t\tsendProposal: (key: string, value: any) => number,\n\t\tpublic readonly audience: IAudienceOwner,\n\t\tprivate readonly shouldClientHaveLeft: (clientId: string) => boolean,\n\t) {\n\t\tsuper(\n\t\t\tattributes.minimumSequenceNumber,\n\t\t\tattributes.sequenceNumber,\n\t\t\tquorumSnapshot.members,\n\t\t\tquorumSnapshot.proposals,\n\t\t\tquorumSnapshot.values,\n\t\t\tsendProposal,\n\t\t);\n\n\t\t// Join / leave signals are ignored for \"write\" clients in favor of join / leave ops\n\t\tthis.quorum.on(\"addMember\", (clientId, details) =>\n\t\t\taudience.addMember(clientId, details.client),\n\t\t);\n\t\tthis.quorum.on(\"removeMember\", (clientId) => audience.removeMember(clientId));\n\t\tfor (const [clientId, details] of this.quorum.getMembers()) {\n\t\t\tthis.audience.addMember(clientId, details.client);\n\t\t}\n\t}\n\n\tpublic processMessage(\n\t\tmessage: ISequencedDocumentMessage,\n\t\tlocal: boolean,\n\t): IProcessMessageResult {\n\t\tconst client: ISequencedClient | undefined = this.quorum.getMember(message.clientId);\n\n\t\t// Check and report if we're getting messages from a clientId that we previously\n\t\t// flagged as shouldHaveLeft, or from a client that's not in the quorum but should be\n\t\tif (message.clientId != null) {\n\t\t\tif (client === undefined && message.type !== MessageType.ClientJoin) {\n\t\t\t\t// pre-0.58 error message: messageClientIdMissingFromQuorum\n\t\t\t\tthrow new Error(\"Remote message's clientId is missing from the quorum\");\n\t\t\t}\n\n\t\t\t// Here checking canBeCoalescedByService is used as an approximation of \"is benign to process despite being unexpected\".\n\t\t\t// It's still not good to see these messages from unexpected clientIds, but since they don't harm the integrity of the\n\t\t\t// document we don't need to blow up aggressively.\n\t\t\tif (this.shouldClientHaveLeft(message.clientId) && !canBeCoalescedByService(message)) {\n\t\t\t\t// pre-0.58 error message: messageClientIdShouldHaveLeft\n\t\t\t\tthrow new Error(\"Remote message's clientId already should have left\");\n\t\t\t}\n\t\t}\n\n\t\treturn super.processMessage(message, local);\n\t}\n\n\tpublic processSignal(message: ISignalMessage) {\n\t\tconst innerContent = message.content as { content: any; type: string };\n\t\tswitch (innerContent.type) {\n\t\t\tcase SignalType.Clear: {\n\t\t\t\tconst members = this.audience.getMembers();\n\t\t\t\tfor (const [clientId, client] of members) {\n\t\t\t\t\tif (client.mode === \"read\") {\n\t\t\t\t\t\tthis.audience.removeMember(clientId);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SignalType.ClientJoin: {\n\t\t\t\tconst newClient = innerContent.content as ISignalClient;\n\t\t\t\t// Ignore write clients - quorum will control such clients.\n\t\t\t\tif (newClient.client.mode === \"read\") {\n\t\t\t\t\tthis.audience.addMember(newClient.clientId, newClient.client);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase SignalType.ClientLeave: {\n\t\t\t\tconst leftClientId = innerContent.content as string;\n\t\t\t\t// Ignore write clients - quorum will control such clients.\n\t\t\t\tif (this.audience.getMember(leftClientId)?.mode === \"read\") {\n\t\t\t\t\tthis.audience.removeMember(leftClientId);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\n/**\n * Function to check whether the protocol handler should process the Signal.\n * The protocol handler should strictly handle only ClientJoin, ClientLeave\n * and Clear signal types.\n */\nexport function protocolHandlerShouldProcessSignal(message: ISignalMessage) {\n\t// Signal originates from server\n\tif (message.clientId === null) {\n\t\tconst innerContent = message.content as { content: unknown; type: string };\n\t\treturn (\n\t\t\tinnerContent.type === SignalType.Clear ||\n\t\t\tinnerContent.type === SignalType.ClientJoin ||\n\t\t\tinnerContent.type === SignalType.ClientLeave\n\t\t);\n\t}\n\treturn false;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluidframework/container-loader",
3
- "version": "2.0.0-internal.6.0.0",
3
+ "version": "2.0.0-internal.6.1.0",
4
4
  "description": "Fluid container loader",
5
5
  "homepage": "https://fluidframework.com",
6
6
  "repository": {
@@ -37,14 +37,14 @@
37
37
  "dependencies": {
38
38
  "@fluidframework/common-definitions": "^0.20.1",
39
39
  "@fluidframework/common-utils": "^1.1.1",
40
- "@fluidframework/container-definitions": ">=2.0.0-internal.6.0.0 <2.0.0-internal.6.1.0",
41
- "@fluidframework/container-utils": ">=2.0.0-internal.6.0.0 <2.0.0-internal.6.1.0",
42
- "@fluidframework/core-interfaces": ">=2.0.0-internal.6.0.0 <2.0.0-internal.6.1.0",
43
- "@fluidframework/driver-definitions": ">=2.0.0-internal.6.0.0 <2.0.0-internal.6.1.0",
44
- "@fluidframework/driver-utils": ">=2.0.0-internal.6.0.0 <2.0.0-internal.6.1.0",
40
+ "@fluidframework/container-definitions": ">=2.0.0-internal.6.1.0 <2.0.0-internal.6.2.0",
41
+ "@fluidframework/container-utils": ">=2.0.0-internal.6.1.0 <2.0.0-internal.6.2.0",
42
+ "@fluidframework/core-interfaces": ">=2.0.0-internal.6.1.0 <2.0.0-internal.6.2.0",
43
+ "@fluidframework/driver-definitions": ">=2.0.0-internal.6.1.0 <2.0.0-internal.6.2.0",
44
+ "@fluidframework/driver-utils": ">=2.0.0-internal.6.1.0 <2.0.0-internal.6.2.0",
45
45
  "@fluidframework/protocol-base": "^1.0.0",
46
46
  "@fluidframework/protocol-definitions": "^1.1.0",
47
- "@fluidframework/telemetry-utils": ">=2.0.0-internal.6.0.0 <2.0.0-internal.6.1.0",
47
+ "@fluidframework/telemetry-utils": ">=2.0.0-internal.6.1.0 <2.0.0-internal.6.2.0",
48
48
  "debug": "^4.1.1",
49
49
  "double-ended-queue": "^2.1.0-0",
50
50
  "events": "^3.1.0",
@@ -53,13 +53,13 @@
53
53
  "uuid": "^8.3.1"
54
54
  },
55
55
  "devDependencies": {
56
- "@fluid-internal/test-loader-utils": ">=2.0.0-internal.6.0.0 <2.0.0-internal.6.1.0",
57
- "@fluid-tools/build-cli": "^0.21.0",
56
+ "@fluid-internal/test-loader-utils": ">=2.0.0-internal.6.1.0 <2.0.0-internal.6.2.0",
57
+ "@fluid-tools/build-cli": "^0.22.0",
58
58
  "@fluidframework/build-common": "^2.0.0",
59
- "@fluidframework/build-tools": "^0.21.0",
60
- "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.0.0-internal.5.2.0",
59
+ "@fluidframework/build-tools": "^0.22.0",
60
+ "@fluidframework/container-loader-previous": "npm:@fluidframework/container-loader@2.0.0-internal.6.0.0",
61
61
  "@fluidframework/eslint-config-fluid": "^2.0.0",
62
- "@fluidframework/mocha-test-setup": ">=2.0.0-internal.6.0.0 <2.0.0-internal.6.1.0",
62
+ "@fluidframework/mocha-test-setup": ">=2.0.0-internal.6.1.0 <2.0.0-internal.6.2.0",
63
63
  "@microsoft/api-extractor": "^7.34.4",
64
64
  "@types/double-ended-queue": "^2.1.0",
65
65
  "@types/events": "^3.0.0",
@@ -82,11 +82,7 @@
82
82
  "typescript": "~4.5.5"
83
83
  },
84
84
  "typeValidation": {
85
- "broken": {
86
- "InterfaceDeclaration_IContainerExperimental": {
87
- "backCompat": false
88
- }
89
- }
85
+ "broken": {}
90
86
  },
91
87
  "scripts": {
92
88
  "build": "fluid-build . --task build",
@@ -48,6 +48,7 @@ import {
48
48
  IConnectionManager,
49
49
  IConnectionManagerFactoryArgs,
50
50
  IConnectionDetailsInternal,
51
+ IConnectionStateChangeReason,
51
52
  } from "./contracts";
52
53
  import { DeltaQueue } from "./deltaQueue";
53
54
  import { SignalType } from "./protocol";
@@ -336,7 +337,7 @@ export class ConnectionManager implements IConnectionManager {
336
337
 
337
338
  private static detailsFromConnection(
338
339
  connection: IDocumentDeltaConnection,
339
- reason: string,
340
+ reason: IConnectionStateChangeReason,
340
341
  ): IConnectionDetailsInternal {
341
342
  return {
342
343
  claims: connection.claims,
@@ -389,7 +390,10 @@ export class ConnectionManager implements IConnectionManager {
389
390
 
390
391
  this._outbound.clear();
391
392
 
392
- const disconnectReason = "Closing DeltaManager";
393
+ const disconnectReason: IConnectionStateChangeReason = {
394
+ text: "Closing DeltaManager",
395
+ error,
396
+ };
393
397
 
394
398
  // This raises "disconnect" event if we have active connection.
395
399
  this.disconnectFromDeltaStream(disconnectReason);
@@ -406,7 +410,7 @@ export class ConnectionManager implements IConnectionManager {
406
410
  * Enables or disables automatic reconnecting.
407
411
  * Will throw an error if reconnectMode set to Never.
408
412
  */
409
- public setAutoReconnect(mode: ReconnectMode): void {
413
+ public setAutoReconnect(mode: ReconnectMode, reason: IConnectionStateChangeReason): void {
410
414
  assert(
411
415
  mode !== ReconnectMode.Never && this._reconnectMode !== ReconnectMode.Never,
412
416
  0x278 /* "API is not supported for non-connecting or closed container" */,
@@ -416,7 +420,7 @@ export class ConnectionManager implements IConnectionManager {
416
420
 
417
421
  if (mode !== ReconnectMode.Enabled) {
418
422
  // immediately disconnect - do not rely on service eventually dropping connection.
419
- this.disconnectFromDeltaStream("setAutoReconnect");
423
+ this.disconnectFromDeltaStream(reason);
420
424
  }
421
425
  }
422
426
 
@@ -463,12 +467,12 @@ export class ConnectionManager implements IConnectionManager {
463
467
  this.logger.sendErrorEvent({ eventName: "ForceReadonlyPendingChanged" });
464
468
  }
465
469
 
466
- reconnect = this.disconnectFromDeltaStream("Force readonly");
470
+ reconnect = this.disconnectFromDeltaStream({ text: "Force readonly" });
467
471
  }
468
472
  this.props.readonlyChangeHandler(this.readonly);
469
473
  if (reconnect) {
470
474
  // reconnect if we disconnected from before.
471
- this.triggerConnect("Force Readonly", "read");
475
+ this.triggerConnect({ text: "Force Readonly" }, "read");
472
476
  }
473
477
  }
474
478
  }
@@ -481,14 +485,17 @@ export class ConnectionManager implements IConnectionManager {
481
485
  }
482
486
  }
483
487
 
484
- public connect(reason: string, connectionMode?: ConnectionMode) {
485
- this.connectCore(reason, connectionMode).catch((error) => {
486
- const normalizedError = normalizeError(error, { props: fatalConnectErrorProp });
488
+ public connect(reason: IConnectionStateChangeReason, connectionMode?: ConnectionMode) {
489
+ this.connectCore(reason, connectionMode).catch((e) => {
490
+ const normalizedError = normalizeError(e, { props: fatalConnectErrorProp });
487
491
  this.props.closeHandler(normalizedError);
488
492
  });
489
493
  }
490
494
 
491
- private async connectCore(reason: string, connectionMode?: ConnectionMode): Promise<void> {
495
+ private async connectCore(
496
+ reason: IConnectionStateChangeReason,
497
+ connectionMode?: ConnectionMode,
498
+ ): Promise<void> {
492
499
  assert(!this._disposed, 0x26a /* "not closed" */);
493
500
 
494
501
  if (this.connection !== undefined) {
@@ -664,7 +671,7 @@ export class ConnectionManager implements IConnectionManager {
664
671
  * And report the error if it escapes for any reason.
665
672
  * @param args - The connection arguments
666
673
  */
667
- private triggerConnect(reason: string, connectionMode: ConnectionMode) {
674
+ private triggerConnect(reason: IConnectionStateChangeReason, connectionMode: ConnectionMode) {
668
675
  // reconnect() includes async awaits, and that causes potential race conditions
669
676
  // where we might already have a connection. If it were to happen, it's possible that we will connect
670
677
  // with different mode to `connectionMode`. Glancing through the caller chains, it looks like code should be
@@ -684,7 +691,7 @@ export class ConnectionManager implements IConnectionManager {
684
691
  * @param error - Error causing the disconnect if any.
685
692
  * @returns A boolean that indicates if there was an existing connection (or pending connection) to disconnect
686
693
  */
687
- private disconnectFromDeltaStream(reason: string, error?: IAnyDriverError): boolean {
694
+ private disconnectFromDeltaStream(reason: IConnectionStateChangeReason): boolean {
688
695
  this.pendingReconnect = false;
689
696
 
690
697
  if (this.connection === undefined) {
@@ -717,7 +724,7 @@ export class ConnectionManager implements IConnectionManager {
717
724
  this._outbound.clear();
718
725
  connection.dispose();
719
726
 
720
- this.props.disconnectHandler(reason, error);
727
+ this.props.disconnectHandler(reason);
721
728
 
722
729
  this._connectionVerboseProps = {};
723
730
 
@@ -727,7 +734,7 @@ export class ConnectionManager implements IConnectionManager {
727
734
  /**
728
735
  * Cancel in-progress connection attempt.
729
736
  */
730
- private cancelConnection(reason: string) {
737
+ private cancelConnection(reason: IConnectionStateChangeReason) {
731
738
  assert(
732
739
  this.pendingConnection !== undefined,
733
740
  0x345 /* this.pendingConnection is undefined when trying to cancel */,
@@ -735,7 +742,10 @@ export class ConnectionManager implements IConnectionManager {
735
742
  this.pendingConnection.abort();
736
743
  this.pendingConnection = undefined;
737
744
  this.logger.sendTelemetryEvent({ eventName: "ConnectionCancelReceived" });
738
- this.props.cancelConnectionHandler(`Cancel Pending Connection due to ${reason}`);
745
+ this.props.cancelConnectionHandler({
746
+ text: `Cancel Pending Connection due to ${reason.text}`,
747
+ error: reason.error,
748
+ });
739
749
  }
740
750
 
741
751
  /**
@@ -746,7 +756,7 @@ export class ConnectionManager implements IConnectionManager {
746
756
  private setupNewSuccessfulConnection(
747
757
  connection: IDocumentDeltaConnection,
748
758
  requestedMode: ConnectionMode,
749
- reason: string,
759
+ reason: IConnectionStateChangeReason,
750
760
  ) {
751
761
  // Old connection should have been cleaned up before establishing a new one
752
762
  assert(
@@ -789,7 +799,7 @@ export class ConnectionManager implements IConnectionManager {
789
799
 
790
800
  if (this._disposed) {
791
801
  // Raise proper events, Log telemetry event and close connection.
792
- this.disconnectFromDeltaStream("ConnectionManager already closed");
802
+ this.disconnectFromDeltaStream({ text: "ConnectionManager already closed" });
793
803
  return;
794
804
  }
795
805
 
@@ -894,7 +904,9 @@ export class ConnectionManager implements IConnectionManager {
894
904
  * @returns A promise that resolves when the connection is reestablished or we stop trying
895
905
  */
896
906
  private reconnectOnError(requestedMode: ConnectionMode, error: IAnyDriverError) {
897
- this.reconnect(requestedMode, error.message, error).catch(this.props.closeHandler);
907
+ this.reconnect(requestedMode, { text: error.message, error }).catch(
908
+ this.props.closeHandler,
909
+ );
898
910
  }
899
911
 
900
912
  /**
@@ -906,26 +918,25 @@ export class ConnectionManager implements IConnectionManager {
906
918
  */
907
919
  private async reconnect(
908
920
  requestedMode: ConnectionMode,
909
- disconnectMessage: string,
910
- error?: IAnyDriverError,
921
+ reason: IConnectionStateChangeReason<IAnyDriverError>,
911
922
  ) {
912
923
  // We quite often get protocol errors before / after observing nack/disconnect
913
924
  // we do not want to run through same sequence twice.
914
925
  // If we're already disconnected/disconnecting it's not appropriate to call this again.
915
926
  assert(this.connection !== undefined, 0x0eb /* "Missing connection for reconnect" */);
916
927
 
917
- this.disconnectFromDeltaStream(disconnectMessage, error);
928
+ this.disconnectFromDeltaStream(reason);
918
929
 
919
930
  // We will always trigger reconnect, even if canRetry is false.
920
931
  // Any truly fatal error state will result in container close upon attempted reconnect,
921
932
  // which is a preferable to closing abruptly when a live connection fails.
922
- if (error !== undefined && !error.canRetry) {
933
+ if (reason.error?.canRetry === false) {
923
934
  this.logger.sendTelemetryEvent(
924
935
  {
925
936
  eventName: "reconnectingDespiteFatalError",
926
937
  reconnectMode: this.reconnectMode,
927
938
  },
928
- error,
939
+ reason.error,
929
940
  );
930
941
  }
931
942
 
@@ -942,9 +953,9 @@ export class ConnectionManager implements IConnectionManager {
942
953
  }
943
954
 
944
955
  // If the error tells us to wait before retrying, then do so.
945
- const delayMs = getRetryDelayFromError(error);
946
- if (error !== undefined && delayMs !== undefined) {
947
- this.props.reconnectionDelayHandler(delayMs, error);
956
+ const delayMs = getRetryDelayFromError(reason.error);
957
+ if (reason.error !== undefined && delayMs !== undefined) {
958
+ this.props.reconnectionDelayHandler(delayMs, reason.error);
948
959
  await new Promise<void>((resolve) => {
949
960
  setTimeout(resolve, delayMs);
950
961
  });
@@ -956,9 +967,13 @@ export class ConnectionManager implements IConnectionManager {
956
967
  await waitForOnline();
957
968
 
958
969
  this.triggerConnect(
959
- error !== undefined
960
- ? "Reconnecting due to Error"
961
- : `Reconnecting due to: ${disconnectMessage}`,
970
+ {
971
+ text:
972
+ reason.error !== undefined
973
+ ? "Reconnecting due to Error"
974
+ : `Reconnecting due to: ${reason.text}`,
975
+ error: reason.error,
976
+ },
962
977
  requestedMode,
963
978
  );
964
979
  }
@@ -1029,7 +1044,7 @@ export class ConnectionManager implements IConnectionManager {
1029
1044
  // still valid?
1030
1045
  await this.reconnect(
1031
1046
  "write", // connectionMode
1032
- "Switch to write", // message
1047
+ { text: "Switch to write" }, // message
1033
1048
  );
1034
1049
  }
1035
1050
  })
@@ -1083,7 +1098,7 @@ export class ConnectionManager implements IConnectionManager {
1083
1098
  // Note - this may close container!
1084
1099
  this.reconnect(
1085
1100
  "read", // connectionMode
1086
- "Switch to read", // message
1101
+ { text: "Switch to read" }, // message
1087
1102
  ).catch((error) => {
1088
1103
  this.logger.sendErrorEvent({ eventName: "SwitchToReadConnection" }, error);
1089
1104
  });
@@ -6,16 +6,16 @@
6
6
  import { ITelemetryProperties, TelemetryEventCategory } from "@fluidframework/core-interfaces";
7
7
  import { assert, Timer } from "@fluidframework/common-utils";
8
8
  import { IDeltaManager } from "@fluidframework/container-definitions";
9
- import { IAnyDriverError } from "@fluidframework/driver-definitions";
10
9
  import { ISequencedClient, IClient } from "@fluidframework/protocol-definitions";
11
10
  import {
12
11
  ITelemetryLoggerExt,
13
12
  PerformanceEvent,
14
13
  loggerToMonitoringContext,
15
14
  } from "@fluidframework/telemetry-utils";
15
+ import { IAnyDriverError } from "@fluidframework/driver-definitions";
16
16
  import { CatchUpMonitor, ICatchUpMonitor } from "./catchUpMonitor";
17
17
  import { ConnectionState } from "./connectionState";
18
- import { IConnectionDetailsInternal } from "./contracts";
18
+ import { IConnectionDetailsInternal, IConnectionStateChangeReason } from "./contracts";
19
19
  import { IProtocolHandler } from "./protocol";
20
20
 
21
21
  // Based on recent data, it looks like majority of cases where we get stuck are due to really slow or
@@ -33,8 +33,7 @@ export interface IConnectionStateHandlerInputs {
33
33
  connectionStateChanged: (
34
34
  value: ConnectionState,
35
35
  oldState: ConnectionState,
36
- reason?: string | undefined,
37
- error?: IAnyDriverError,
36
+ reason?: IConnectionStateChangeReason,
38
37
  ) => void;
39
38
  /** Whether to expect the client to join in write mode on next connection */
40
39
  shouldClientJoinWrite: () => boolean;
@@ -61,14 +60,14 @@ export interface IConnectionStateHandler {
61
60
  dispose(): void;
62
61
  initProtocol(protocol: IProtocolHandler): void;
63
62
  receivedConnectEvent(details: IConnectionDetailsInternal): void;
64
- receivedDisconnectEvent(reason: string, error?: IAnyDriverError): void;
65
- establishingConnection(reason: string): void;
63
+ receivedDisconnectEvent(reason: IConnectionStateChangeReason): void;
64
+ establishingConnection(reason: IConnectionStateChangeReason): void;
66
65
  /**
67
66
  * Switches state to disconnected when we are still establishing connection during container.load(),
68
67
  * container connect() or reconnect and the container gets closed or disposed or disconnect happens.
69
68
  * @param reason - reason for cancelling the connection.
70
69
  */
71
- cancelEstablishingConnection(reason: string): void;
70
+ cancelEstablishingConnection(reason: IConnectionStateChangeReason): void;
72
71
  }
73
72
 
74
73
  export function createConnectionStateHandler(
@@ -150,15 +149,15 @@ class ConnectionStateHandlerPassThrough
150
149
  public initProtocol(protocol: IProtocolHandler) {
151
150
  return this.pimpl.initProtocol(protocol);
152
151
  }
153
- public receivedDisconnectEvent(reason: string, error?: IAnyDriverError) {
154
- return this.pimpl.receivedDisconnectEvent(reason, error);
152
+ public receivedDisconnectEvent(reason: IConnectionStateChangeReason<IAnyDriverError>) {
153
+ return this.pimpl.receivedDisconnectEvent(reason);
155
154
  }
156
155
 
157
- public establishingConnection(reason: string) {
156
+ public establishingConnection(reason: IConnectionStateChangeReason) {
158
157
  return this.pimpl.establishingConnection(reason);
159
158
  }
160
159
 
161
- public cancelEstablishingConnection(reason: string) {
160
+ public cancelEstablishingConnection(reason: IConnectionStateChangeReason) {
162
161
  return this.pimpl.cancelEstablishingConnection(reason);
163
162
  }
164
163
 
@@ -176,10 +175,9 @@ class ConnectionStateHandlerPassThrough
176
175
  public connectionStateChanged(
177
176
  value: ConnectionState,
178
177
  oldState: ConnectionState,
179
- reason?: string | undefined,
180
- error?: IAnyDriverError,
178
+ reason?: IConnectionStateChangeReason,
181
179
  ) {
182
- return this.inputs.connectionStateChanged(value, oldState, reason, error);
180
+ return this.inputs.connectionStateChanged(value, oldState, reason);
183
181
  }
184
182
  public shouldClientJoinWrite() {
185
183
  return this.inputs.shouldClientJoinWrite();
@@ -223,8 +221,7 @@ class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
223
221
  public connectionStateChanged(
224
222
  value: ConnectionState,
225
223
  oldState: ConnectionState,
226
- reason?: string | undefined,
227
- error?: IAnyDriverError,
224
+ reason?: IConnectionStateChangeReason<IAnyDriverError>,
228
225
  ) {
229
226
  switch (value) {
230
227
  case ConnectionState.Connected:
@@ -268,7 +265,7 @@ class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
268
265
  default:
269
266
  }
270
267
  this._connectionState = value;
271
- this.inputs.connectionStateChanged(value, oldState, reason, error);
268
+ this.inputs.connectionStateChanged(value, oldState, reason);
272
269
  }
273
270
 
274
271
  private readonly transitionToConnectedState = () => {
@@ -277,11 +274,9 @@ class ConnectionStateCatchup extends ConnectionStateHandlerPassThrough {
277
274
  assert(state === ConnectionState.Connected, 0x3e5 /* invariant broken */);
278
275
  assert(this._connectionState === ConnectionState.CatchingUp, 0x3e6 /* invariant broken */);
279
276
  this._connectionState = ConnectionState.Connected;
280
- this.inputs.connectionStateChanged(
281
- ConnectionState.Connected,
282
- ConnectionState.CatchingUp,
283
- "caught up",
284
- );
277
+ this.inputs.connectionStateChanged(ConnectionState.Connected, ConnectionState.CatchingUp, {
278
+ text: "caught up",
279
+ });
285
280
  };
286
281
  }
287
282
 
@@ -495,12 +490,12 @@ class ConnectionStateHandler implements IConnectionStateHandler {
495
490
  }
496
491
  }
497
492
 
498
- public receivedDisconnectEvent(reason: string, error?: IAnyDriverError) {
493
+ public receivedDisconnectEvent(reason: IConnectionStateChangeReason<IAnyDriverError>) {
499
494
  this.connection = undefined;
500
- this.setConnectionState(ConnectionState.Disconnected, reason, error);
495
+ this.setConnectionState(ConnectionState.Disconnected, reason);
501
496
  }
502
497
 
503
- public cancelEstablishingConnection(reason: string) {
498
+ public cancelEstablishingConnection(reason: IConnectionStateChangeReason) {
504
499
  assert(
505
500
  this._connectionState === ConnectionState.EstablishingConnection,
506
501
  0x6d3 /* Connection state should be EstablishingConnection */,
@@ -511,14 +506,13 @@ class ConnectionStateHandler implements IConnectionStateHandler {
511
506
  this.handler.connectionStateChanged(ConnectionState.Disconnected, oldState, reason);
512
507
  }
513
508
 
514
- public establishingConnection(reason: string) {
509
+ public establishingConnection(reason: IConnectionStateChangeReason) {
515
510
  const oldState = this._connectionState;
516
511
  this._connectionState = ConnectionState.EstablishingConnection;
517
- this.handler.connectionStateChanged(
518
- ConnectionState.EstablishingConnection,
519
- oldState,
520
- `Establishing Connection due to ${reason}`,
521
- );
512
+ this.handler.connectionStateChanged(ConnectionState.EstablishingConnection, oldState, {
513
+ text: `Establishing Connection due to ${reason.text}`,
514
+ error: reason.error,
515
+ });
522
516
  }
523
517
 
524
518
  private shouldWaitForJoinSignal() {
@@ -583,14 +577,12 @@ class ConnectionStateHandler implements IConnectionStateHandler {
583
577
 
584
578
  private setConnectionState(
585
579
  value: ConnectionState.Disconnected,
586
- reason: string,
587
- error?: IAnyDriverError,
580
+ reason: IConnectionStateChangeReason,
588
581
  ): void;
589
582
  private setConnectionState(value: ConnectionState.Connected): void;
590
583
  private setConnectionState(
591
584
  value: ConnectionState.Disconnected | ConnectionState.Connected,
592
- reason?: string,
593
- error?: IAnyDriverError,
585
+ reason?: IConnectionStateChangeReason,
594
586
  ): void {
595
587
  if (this.connectionState === value) {
596
588
  // Already in the desired state - exit early
@@ -651,7 +643,7 @@ class ConnectionStateHandler implements IConnectionStateHandler {
651
643
  }
652
644
 
653
645
  // Report transition before we propagate event across layers
654
- this.handler.connectionStateChanged(this._connectionState, oldState, reason, error);
646
+ this.handler.connectionStateChanged(this._connectionState, oldState, reason);
655
647
  }
656
648
 
657
649
  // Helper method to switch between quorum and audience.