@wireapp/core 46.46.6-beta.14.f6fd03fe6 → 46.46.6-beta.28.9d3034481

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 (195) hide show
  1. package/lib/Account.d.ts +168 -51
  2. package/lib/Account.d.ts.map +1 -1
  3. package/lib/Account.js +517 -127
  4. package/lib/Account.test.js +158 -147
  5. package/lib/broadcast/AvailabilityType.d.ts +1 -1
  6. package/lib/broadcast/AvailabilityType.d.ts.map +1 -1
  7. package/lib/broadcast/BroadcastService.d.ts +1 -1
  8. package/lib/broadcast/BroadcastService.d.ts.map +1 -1
  9. package/lib/broadcast/BroadcastService.js +1 -1
  10. package/lib/client/ClientService.d.ts +4 -3
  11. package/lib/client/ClientService.d.ts.map +1 -1
  12. package/lib/client/ClientService.js +19 -5
  13. package/lib/conversation/AbortReason.d.ts +1 -1
  14. package/lib/conversation/AbortReason.d.ts.map +1 -1
  15. package/lib/conversation/AssetService/AssetService.d.ts +12 -30
  16. package/lib/conversation/AssetService/AssetService.d.ts.map +1 -1
  17. package/lib/conversation/AssetService/AssetService.js +1 -10
  18. package/lib/conversation/AssetService/AssetService.test.js +8 -3
  19. package/lib/conversation/ClientActionType.d.ts +1 -1
  20. package/lib/conversation/ClientActionType.d.ts.map +1 -1
  21. package/lib/conversation/ClientActionType.js +1 -1
  22. package/lib/conversation/ConversationService/ConversationService.d.ts +98 -14
  23. package/lib/conversation/ConversationService/ConversationService.d.ts.map +1 -1
  24. package/lib/conversation/ConversationService/ConversationService.js +314 -101
  25. package/lib/conversation/ConversationService/ConversationService.test.js +441 -47
  26. package/lib/conversation/ConversationService/ConversationService.types.d.ts +5 -4
  27. package/lib/conversation/ConversationService/ConversationService.types.d.ts.map +1 -1
  28. package/lib/conversation/ConversationService/Utility/getConversationQualifiedMembers.d.ts.map +1 -1
  29. package/lib/conversation/ConversationService/Utility/getConversationQualifiedMembers.js +6 -3
  30. package/lib/conversation/SubconversationService/SubconversationService.d.ts.map +1 -1
  31. package/lib/conversation/SubconversationService/SubconversationService.js +158 -11
  32. package/lib/conversation/SubconversationService/SubconversationService.test.js +8 -2
  33. package/lib/conversation/content/AssetContent.d.ts +1 -1
  34. package/lib/conversation/content/AssetContent.d.ts.map +1 -1
  35. package/lib/conversation/content/ButtonActionConfirmationContent.d.ts +1 -1
  36. package/lib/conversation/content/ButtonActionConfirmationContent.d.ts.map +1 -1
  37. package/lib/conversation/content/ButtonActionContent.d.ts +1 -1
  38. package/lib/conversation/content/ButtonActionContent.d.ts.map +1 -1
  39. package/lib/conversation/content/ClearedContent.d.ts +1 -1
  40. package/lib/conversation/content/ClearedContent.d.ts.map +1 -1
  41. package/lib/conversation/content/ClientActionContent.d.ts +1 -1
  42. package/lib/conversation/content/ClientActionContent.d.ts.map +1 -1
  43. package/lib/conversation/content/CompositeContent.d.ts +1 -1
  44. package/lib/conversation/content/CompositeContent.d.ts.map +1 -1
  45. package/lib/conversation/content/ConfirmationContent.d.ts +1 -1
  46. package/lib/conversation/content/ConfirmationContent.d.ts.map +1 -1
  47. package/lib/conversation/content/DeletedContent.d.ts +1 -1
  48. package/lib/conversation/content/DeletedContent.d.ts.map +1 -1
  49. package/lib/conversation/content/HiddenContent.d.ts +1 -1
  50. package/lib/conversation/content/HiddenContent.d.ts.map +1 -1
  51. package/lib/conversation/content/KnockContent.d.ts +1 -1
  52. package/lib/conversation/content/KnockContent.d.ts.map +1 -1
  53. package/lib/conversation/content/LinkPreviewContent.d.ts +1 -1
  54. package/lib/conversation/content/LinkPreviewContent.d.ts.map +1 -1
  55. package/lib/conversation/content/MentionContent.d.ts +1 -1
  56. package/lib/conversation/content/MentionContent.d.ts.map +1 -1
  57. package/lib/conversation/content/MultipartContent.d.ts +1 -1
  58. package/lib/conversation/content/MultipartContent.d.ts.map +1 -1
  59. package/lib/conversation/content/QuoteContent.d.ts +1 -1
  60. package/lib/conversation/content/QuoteContent.d.ts.map +1 -1
  61. package/lib/conversation/content/TweetContent.d.ts +1 -1
  62. package/lib/conversation/content/TweetContent.d.ts.map +1 -1
  63. package/lib/conversation/content/index.d.ts +1 -1
  64. package/lib/conversation/content/index.d.ts.map +1 -1
  65. package/lib/conversation/content/index.js +1 -1
  66. package/lib/conversation/message/MessageBuilder.d.ts +1 -1
  67. package/lib/conversation/message/MessageBuilder.d.ts.map +1 -1
  68. package/lib/conversation/message/MessageBuilder.js +1 -1
  69. package/lib/conversation/message/MessageService.d.ts.map +1 -1
  70. package/lib/conversation/message/MessageService.js +1 -1
  71. package/lib/conversation/message/MessageService.test.js +7 -1
  72. package/lib/conversation/message/MessageToProtoMapper.d.ts +1 -1
  73. package/lib/conversation/message/MessageToProtoMapper.d.ts.map +1 -1
  74. package/lib/conversation/message/MessageToProtoMapper.js +1 -1
  75. package/lib/conversation/message/messageSender.js +2 -2
  76. package/lib/cryptography/AssetCryptography/EncryptedAsset.d.ts +2 -2
  77. package/lib/cryptography/AssetCryptography/EncryptedAsset.d.ts.map +1 -1
  78. package/lib/messagingProtocols/common.types.d.ts +9 -0
  79. package/lib/messagingProtocols/common.types.d.ts.map +1 -1
  80. package/lib/messagingProtocols/mls/E2EIdentityService/E2EIService.types.d.ts +2 -2
  81. package/lib/messagingProtocols/mls/E2EIdentityService/E2EIService.types.d.ts.map +1 -1
  82. package/lib/messagingProtocols/mls/E2EIdentityService/E2EIService.types.js +2 -1
  83. package/lib/messagingProtocols/mls/E2EIdentityService/E2EIServiceExternal.d.ts +1 -1
  84. package/lib/messagingProtocols/mls/E2EIdentityService/E2EIServiceExternal.d.ts.map +1 -1
  85. package/lib/messagingProtocols/mls/E2EIdentityService/E2EIServiceExternal.js +13 -11
  86. package/lib/messagingProtocols/mls/E2EIdentityService/E2EIServiceExternal.test.js +21 -16
  87. package/lib/messagingProtocols/mls/E2EIdentityService/E2EIServiceInternal.d.ts +9 -3
  88. package/lib/messagingProtocols/mls/E2EIdentityService/E2EIServiceInternal.d.ts.map +1 -1
  89. package/lib/messagingProtocols/mls/E2EIdentityService/E2EIServiceInternal.js +31 -12
  90. package/lib/messagingProtocols/mls/E2EIdentityService/Helper/index.d.ts +6 -0
  91. package/lib/messagingProtocols/mls/E2EIdentityService/Helper/index.d.ts.map +1 -1
  92. package/lib/messagingProtocols/mls/E2EIdentityService/Helper/index.js +19 -1
  93. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/IncomingProposalsQueue/IncomingProposalsQueue.d.ts +7 -0
  94. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/IncomingProposalsQueue/IncomingProposalsQueue.d.ts.map +1 -0
  95. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/IncomingProposalsQueue/IncomingProposalsQueue.js +48 -0
  96. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/IncomingProposalsQueue/index.d.ts +2 -0
  97. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/{IncomingMessagesQueue → IncomingProposalsQueue}/index.d.ts.map +1 -1
  98. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/{IncomingMessagesQueue → IncomingProposalsQueue}/index.js +1 -1
  99. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/index.d.ts +0 -1
  100. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/index.d.ts.map +1 -1
  101. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/index.js +0 -1
  102. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/messageAdd.d.ts.map +1 -1
  103. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/messageAdd.js +23 -14
  104. package/lib/messagingProtocols/mls/EventHandler/events/welcomeMessage/welcomeMessage.d.ts.map +1 -1
  105. package/lib/messagingProtocols/mls/EventHandler/events/welcomeMessage/welcomeMessage.js +5 -2
  106. package/lib/messagingProtocols/mls/EventHandler/events/welcomeMessage/welcomeMessage.test.js +13 -3
  107. package/lib/messagingProtocols/mls/MLSService/CoreCryptoMLSError.d.ts +38 -2
  108. package/lib/messagingProtocols/mls/MLSService/CoreCryptoMLSError.d.ts.map +1 -1
  109. package/lib/messagingProtocols/mls/MLSService/CoreCryptoMLSError.js +41 -6
  110. package/lib/messagingProtocols/mls/MLSService/CoreCryptoMLSError.test.d.ts +2 -0
  111. package/lib/messagingProtocols/mls/MLSService/CoreCryptoMLSError.test.d.ts.map +1 -0
  112. package/lib/messagingProtocols/mls/MLSService/CoreCryptoMLSError.test.js +124 -0
  113. package/lib/messagingProtocols/mls/MLSService/MLSService.d.ts +38 -34
  114. package/lib/messagingProtocols/mls/MLSService/MLSService.d.ts.map +1 -1
  115. package/lib/messagingProtocols/mls/MLSService/MLSService.js +267 -208
  116. package/lib/messagingProtocols/mls/MLSService/MLSService.test.js +157 -160
  117. package/lib/messagingProtocols/mls/MLSService/commitBundleUtil.js +3 -3
  118. package/lib/messagingProtocols/mls/MLSService/commitBundleUtil.test.js +5 -5
  119. package/lib/messagingProtocols/mls/conversationRejoinQueue.js +2 -2
  120. package/lib/messagingProtocols/mls/recovery/MlsErrorMapper.d.ts +78 -0
  121. package/lib/messagingProtocols/mls/recovery/MlsErrorMapper.d.ts.map +1 -0
  122. package/lib/messagingProtocols/mls/recovery/MlsErrorMapper.js +173 -0
  123. package/lib/messagingProtocols/mls/recovery/MlsErrorMapper.test.d.ts +2 -0
  124. package/lib/messagingProtocols/mls/recovery/MlsErrorMapper.test.d.ts.map +1 -0
  125. package/lib/messagingProtocols/mls/recovery/MlsErrorMapper.test.js +117 -0
  126. package/lib/messagingProtocols/mls/recovery/MlsRecoveryOrchestrator.d.ts +167 -0
  127. package/lib/messagingProtocols/mls/recovery/MlsRecoveryOrchestrator.d.ts.map +1 -0
  128. package/lib/messagingProtocols/mls/recovery/MlsRecoveryOrchestrator.js +317 -0
  129. package/lib/messagingProtocols/mls/recovery/MlsRecoveryOrchestrator.test.d.ts +2 -0
  130. package/lib/messagingProtocols/mls/recovery/MlsRecoveryOrchestrator.test.d.ts.map +1 -0
  131. package/lib/messagingProtocols/mls/recovery/MlsRecoveryOrchestrator.test.js +248 -0
  132. package/lib/messagingProtocols/mls/recovery/index.d.ts +5 -0
  133. package/lib/messagingProtocols/mls/recovery/index.d.ts.map +1 -0
  134. package/lib/messagingProtocols/mls/recovery/index.js +28 -0
  135. package/lib/messagingProtocols/mls/types.d.ts +0 -8
  136. package/lib/messagingProtocols/mls/types.d.ts.map +1 -1
  137. package/lib/messagingProtocols/proteus/EventHandler/events/otrMessageAdd/otrMessageAdd.d.ts.map +1 -1
  138. package/lib/messagingProtocols/proteus/EventHandler/events/otrMessageAdd/otrMessageAdd.js +7 -1
  139. package/lib/messagingProtocols/proteus/ProteusService/CryptoClient/CoreCryptoWrapper/CoreCryptoWrapper.d.ts +8 -15
  140. package/lib/messagingProtocols/proteus/ProteusService/CryptoClient/CoreCryptoWrapper/CoreCryptoWrapper.d.ts.map +1 -1
  141. package/lib/messagingProtocols/proteus/ProteusService/CryptoClient/CoreCryptoWrapper/CoreCryptoWrapper.js +97 -62
  142. package/lib/messagingProtocols/proteus/ProteusService/CryptoClient/CryptoClient.types.d.ts +0 -6
  143. package/lib/messagingProtocols/proteus/ProteusService/CryptoClient/CryptoClient.types.d.ts.map +1 -1
  144. package/lib/messagingProtocols/proteus/ProteusService/DecryptionErrorGenerator/DecryptionErrorGenerator.d.ts +1 -6
  145. package/lib/messagingProtocols/proteus/ProteusService/DecryptionErrorGenerator/DecryptionErrorGenerator.d.ts.map +1 -1
  146. package/lib/messagingProtocols/proteus/ProteusService/DecryptionErrorGenerator/DecryptionErrorGenerator.js +19 -22
  147. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.d.ts +5 -3
  148. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.d.ts.map +1 -1
  149. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.js +11 -24
  150. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.mocks.d.ts +1 -0
  151. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.mocks.d.ts.map +1 -1
  152. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.mocks.js +11 -2
  153. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.test.js +13 -9
  154. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.types.d.ts +3 -2
  155. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.types.d.ts.map +1 -1
  156. package/lib/messagingProtocols/proteus/ProteusService/WithMockedGenerics.test.js +11 -4
  157. package/lib/messagingProtocols/proteus/ProteusService/cryptoMigrationStateStore.d.ts +0 -4
  158. package/lib/messagingProtocols/proteus/ProteusService/cryptoMigrationStateStore.d.ts.map +1 -1
  159. package/lib/messagingProtocols/proteus/ProteusService/cryptoMigrationStateStore.js +0 -5
  160. package/lib/messagingProtocols/proteus/ProteusService/identityClearer.d.ts +2 -1
  161. package/lib/messagingProtocols/proteus/ProteusService/identityClearer.d.ts.map +1 -1
  162. package/lib/messagingProtocols/proteus/ProteusService/identityClearer.js +8 -2
  163. package/lib/messagingProtocols/proteus/Utility/SessionHandler/SessionHandler.test.js +4 -0
  164. package/lib/messagingProtocols/proteus/Utility/getGenericMessageParams.d.ts +1 -1
  165. package/lib/messagingProtocols/proteus/Utility/getGenericMessageParams.d.ts.map +1 -1
  166. package/lib/messagingProtocols/proteus/Utility/getGenericMessageParams.js +1 -1
  167. package/lib/notification/NotificationService.d.ts +20 -6
  168. package/lib/notification/NotificationService.d.ts.map +1 -1
  169. package/lib/notification/NotificationService.js +23 -14
  170. package/lib/notification/NotificationService.test.js +8 -0
  171. package/lib/secretStore/secretKeyGenerator.d.ts +1 -0
  172. package/lib/secretStore/secretKeyGenerator.d.ts.map +1 -1
  173. package/lib/secretStore/secretKeyGenerator.js +3 -1
  174. package/lib/self/SelfService.d.ts +2 -2
  175. package/lib/self/SelfService.d.ts.map +1 -1
  176. package/lib/self/SelfService.test.js +5 -2
  177. package/lib/team/TeamService.d.ts +5 -2
  178. package/lib/team/TeamService.d.ts.map +1 -1
  179. package/lib/team/TeamService.js +12 -2
  180. package/lib/test/StoreHelper.d.ts +2 -0
  181. package/lib/test/StoreHelper.d.ts.map +1 -0
  182. package/lib/test/StoreHelper.js +27 -0
  183. package/lib/user/UserService.d.ts +2 -2
  184. package/lib/user/UserService.d.ts.map +1 -1
  185. package/lib/user/UserService.js +3 -3
  186. package/lib/util/TypePredicateUtil.d.ts.map +1 -1
  187. package/lib/util/TypePredicateUtil.js +2 -2
  188. package/package.json +5 -5
  189. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/IncomingMessagesQueue/IncomingMesssagesQueue.d.ts +0 -4
  190. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/IncomingMessagesQueue/IncomingMesssagesQueue.d.ts.map +0 -1
  191. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/IncomingMessagesQueue/IncomingMesssagesQueue.js +0 -69
  192. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/IncomingMessagesQueue/index.d.ts +0 -2
  193. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/messageAdd.test.d.ts +0 -2
  194. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/messageAdd.test.d.ts.map +0 -1
  195. package/lib/messagingProtocols/mls/EventHandler/events/messageAdd/messageAdd.test.js +0 -98
package/lib/Account.js CHANGED
@@ -41,16 +41,18 @@ var __importStar = (this && this.__importStar) || function (mod) {
41
41
  return result;
42
42
  };
43
43
  Object.defineProperty(exports, "__esModule", { value: true });
44
- exports.Account = exports.ConnectionState = exports.EVENTS = void 0;
44
+ exports.Account = exports.AccountLocalStorageStore = exports.EVENTS = exports.ConnectionState = void 0;
45
45
  const auth_1 = require("@wireapp/api-client/lib/auth");
46
46
  const client_1 = require("@wireapp/api-client/lib/client/");
47
47
  const event_1 = require("@wireapp/api-client/lib/event");
48
+ const ConsumableNotification_1 = require("@wireapp/api-client/lib/notification/ConsumableNotification");
48
49
  const tcp_1 = require("@wireapp/api-client/lib/tcp/");
49
50
  const ReconnectingWebsocket_1 = require("@wireapp/api-client/lib/tcp/ReconnectingWebsocket");
50
51
  const team_1 = require("@wireapp/api-client/lib/team");
51
52
  const TimeUtil_1 = require("@wireapp/commons/lib/util/TimeUtil");
52
53
  const api_client_1 = require("@wireapp/api-client");
53
54
  const commons_1 = require("@wireapp/commons");
55
+ const promise_queue_1 = require("@wireapp/promise-queue");
54
56
  const store_engine_1 = require("@wireapp/store-engine");
55
57
  const account_1 = require("./account/");
56
58
  const auth_2 = require("./auth/");
@@ -65,8 +67,11 @@ const linkPreview_1 = require("./linkPreview");
65
67
  const mls_1 = require("./messagingProtocols/mls");
66
68
  const conversationRejoinQueue_1 = require("./messagingProtocols/mls/conversationRejoinQueue");
67
69
  const E2EIdentityService_1 = require("./messagingProtocols/mls/E2EIdentityService");
70
+ const IncomingProposalsQueue_1 = require("./messagingProtocols/mls/EventHandler/events/messageAdd/IncomingProposalsQueue");
68
71
  const proteus_1 = require("./messagingProtocols/proteus");
69
72
  const CryptoClient_1 = require("./messagingProtocols/proteus/ProteusService/CryptoClient");
73
+ const CoreCryptoWrapper_1 = require("./messagingProtocols/proteus/ProteusService/CryptoClient/CoreCryptoWrapper");
74
+ const identityClearer_1 = require("./messagingProtocols/proteus/ProteusService/identityClearer");
70
75
  const notification_1 = require("./notification/");
71
76
  const encryptedStore_1 = require("./secretStore/encryptedStore");
72
77
  const secretKeyGenerator_1 = require("./secretStore/secretKeyGenerator");
@@ -74,24 +79,17 @@ const self_1 = require("./self/");
74
79
  const CoreDB_1 = require("./storage/CoreDB");
75
80
  const team_2 = require("./team/");
76
81
  const user_1 = require("./user/");
82
+ const LocalStorageStore_1 = require("./util/LocalStorageStore");
77
83
  const RecurringTaskScheduler_1 = require("./util/RecurringTaskScheduler");
78
- var EVENTS;
79
- (function (EVENTS) {
80
- /**
81
- * event triggered when a message from an unknown client is received.
82
- * An unknown client is a client we don't yet have a session with
83
- */
84
- EVENTS["NEW_SESSION"] = "new_session";
85
- })(EVENTS || (exports.EVENTS = EVENTS = {}));
86
84
  var ConnectionState;
87
85
  (function (ConnectionState) {
88
- /** The websocket is closed and notifications stream is not being processed */
86
+ /** The WebSocket is closed and no notifications are being processed */
89
87
  ConnectionState["CLOSED"] = "closed";
90
- /** The websocket is being opened */
88
+ /** The WebSocket is being opened or reconnected */
91
89
  ConnectionState["CONNECTING"] = "connecting";
92
90
  /** The websocket is open but locked and notifications stream is being processed */
93
91
  ConnectionState["PROCESSING_NOTIFICATIONS"] = "processing_notifications";
94
- /** The websocket is open and message will go through and notifications stream is fully processed */
92
+ /** The WebSocket is open and new messages are processed live in real time */
95
93
  ConnectionState["LIVE"] = "live";
96
94
  })(ConnectionState || (exports.ConnectionState = ConnectionState = {}));
97
95
  const coreDefaultClient = {
@@ -99,17 +97,31 @@ const coreDefaultClient = {
99
97
  cookieLabel: 'default',
100
98
  model: '@wireapp/core',
101
99
  };
100
+ var EVENTS;
101
+ (function (EVENTS) {
102
+ /**
103
+ * event triggered when a message from an unknown client is received.
104
+ * An unknown client is a client we don't yet have a session with
105
+ */
106
+ EVENTS["NEW_SESSION"] = "new_session";
107
+ })(EVENTS || (exports.EVENTS = EVENTS = {}));
108
+ exports.AccountLocalStorageStore = (0, LocalStorageStore_1.LocalStorageStore)('core_account');
102
109
  class Account extends commons_1.TypedEventEmitter {
103
110
  options;
104
111
  apiClient;
105
112
  logger;
106
- coreCryptoConfig;
107
113
  /** this is the client the consumer is currently using. Will be set as soon as `initClient` is called and will be rest upon logout */
108
114
  currentClient;
109
115
  storeEngine;
110
116
  db;
111
117
  encryptedDb;
112
118
  coreCallbacks;
119
+ connectionState = ConnectionState.CLOSED;
120
+ notificationProcessingQueue = new promise_queue_1.PromiseQueue({
121
+ name: 'notification-processing-queue',
122
+ paused: true,
123
+ });
124
+ setMaxCoreCryptoLogLevel = () => undefined;
113
125
  service;
114
126
  backendFeatures;
115
127
  recurringTaskScheduler;
@@ -117,12 +129,14 @@ class Account extends commons_1.TypedEventEmitter {
117
129
  * @param apiClient The apiClient instance to use in the core (will create a new new one if undefined)
118
130
  * @param accountOptions
119
131
  */
120
- constructor(apiClient = new api_client_1.APIClient(), options = { nbPrekeys: 100 }) {
132
+ constructor(apiClient = new api_client_1.APIClient(), options = {
133
+ nbPrekeys: 100,
134
+ coreCryptoConfig: { wasmFilePath: '', enabled: false },
135
+ }) {
121
136
  super();
122
137
  this.options = options;
123
138
  this.apiClient = apiClient;
124
139
  this.backendFeatures = this.apiClient.backendFeatures;
125
- this.coreCryptoConfig = options.coreCryptoConfig;
126
140
  this.recurringTaskScheduler = new RecurringTaskScheduler_1.RecurringTaskScheduler({
127
141
  get: async (key) => {
128
142
  const task = await this.db?.get('recurringTasks', key);
@@ -141,7 +155,7 @@ class Account extends commons_1.TypedEventEmitter {
141
155
  await this.persistCookie(this.storeEngine, cookie);
142
156
  }
143
157
  catch (error) {
144
- this.logger.error(`Failed to save cookie: ${error.message}`, error);
158
+ this.logger.error('Failed to save cookie:', error);
145
159
  }
146
160
  }
147
161
  });
@@ -159,16 +173,16 @@ class Account extends commons_1.TypedEventEmitter {
159
173
  * - useVersion(0, 1, true) > version 1 is used
160
174
  * @return The highest version that is both supported by client and backend
161
175
  */
162
- async useAPIVersion(min, max, allowDev) {
176
+ useAPIVersion = async (min, max, allowDev) => {
163
177
  const features = await this.apiClient.useVersion(min, max, allowDev);
164
178
  this.backendFeatures = features;
165
179
  return features;
166
- }
167
- persistCookie(storeEngine, cookie) {
180
+ };
181
+ persistCookie = (storeEngine, cookie) => {
168
182
  const entity = { expiration: cookie.expiration, zuid: cookie.zuid };
169
183
  return storeEngine.updateOrCreate(auth_1.AUTH_TABLE_NAME, auth_1.AUTH_COOKIE_KEY, entity);
170
- }
171
- async enrollE2EI({ displayName, handle, teamId, discoveryUrl, getOAuthToken, certificateTtl = 90 * (TimeUtil_1.TimeInMillis.DAY / 1000), }) {
184
+ };
185
+ enrollE2EI = async ({ displayName, handle, teamId, discoveryUrl, getOAuthToken, getAllConversations, certificateTtl = 90 * (TimeUtil_1.TimeInMillis.DAY / 1000), }) => {
172
186
  const context = this.apiClient.context;
173
187
  const domain = context?.domain ?? '';
174
188
  if (!this.currentClient) {
@@ -184,8 +198,8 @@ class Account extends commons_1.TypedEventEmitter {
184
198
  teamId,
185
199
  id: this.userId,
186
200
  };
187
- return this.service.mls.enrollE2EI(discoveryUrl, user, this.currentClient, this.options.nbPrekeys, certificateTtl, getOAuthToken);
188
- }
201
+ return this.service.mls.enrollE2EI(discoveryUrl, user, this.currentClient, this.options.nbPrekeys, certificateTtl, getOAuthToken, getAllConversations);
202
+ };
189
203
  get clientId() {
190
204
  return this.apiClient.validatedClientId;
191
205
  }
@@ -198,52 +212,57 @@ class Account extends commons_1.TypedEventEmitter {
198
212
  * @param registration The user's data
199
213
  * @param clientType Type of client to create (temporary or permanent)
200
214
  */
201
- async register(registration, clientType) {
215
+ register = async (registration, clientType) => {
202
216
  const context = await this.apiClient.register(registration, clientType);
203
217
  await this.initServices(context);
204
218
  return context;
205
- }
219
+ };
206
220
  /**
207
221
  * Will init the core with an already logged in user
208
222
  *
209
223
  * @param clientType The type of client the user is using (temporary or permanent)
210
224
  */
211
- async init(clientType, { cookie } = {}) {
225
+ init = async (clientType, { cookie } = {}) => {
212
226
  const context = await this.apiClient.init(clientType, cookie);
213
227
  await this.initServices(context);
214
228
  return context;
215
- }
229
+ };
216
230
  /**
217
231
  * Will log the user in with the given credential.
218
232
  *
219
233
  * @param loginData The credentials of the user
220
234
  * @param clientInfo Info about the client to create (name, type...)
221
235
  */
222
- async login(loginData) {
236
+ login = async (loginData) => {
223
237
  this.resetContext();
224
238
  auth_2.LoginSanitizer.removeNonPrintableCharacters(loginData);
225
239
  const context = await this.apiClient.login(loginData);
226
240
  await this.initServices(context);
227
241
  return context;
228
- }
242
+ };
229
243
  /**
230
244
  * Will register a new client for the current user
231
245
  */
232
- async registerClient(loginData, clientInfo = coreDefaultClient,
246
+ registerClient = async (loginData, useLegacyNotificationStream,
233
247
  /** will add extra manual entropy to the client's identity being created */
234
- entropyData) {
248
+ entropyData, clientInfo = coreDefaultClient) => {
235
249
  if (!this.service || !this.apiClient.context || !this.storeEngine) {
236
250
  throw new Error('Services are not set or context not initialized.');
237
251
  }
252
+ if (typeof useLegacyNotificationStream !== 'boolean') {
253
+ throw new Error('use of legacy notifications must be explicitly set to true or false');
254
+ }
238
255
  // we reset the services to re-instantiate a new CryptoClient instance
239
256
  await this.initServices(this.apiClient.context);
240
257
  const initialPreKeys = await this.service.proteus.createClient(entropyData);
241
- const client = await this.service.client.register(loginData, clientInfo, initialPreKeys);
258
+ const client = await this.service.client.register(loginData, clientInfo, initialPreKeys, useLegacyNotificationStream);
242
259
  const clientId = client.id;
243
- await this.service.notification.initializeNotificationStream(clientId);
260
+ if (useLegacyNotificationStream) {
261
+ await this.service.notification.legacyInitializeNotificationStream(clientId);
262
+ }
244
263
  await this.service.client.synchronizeClients(clientId);
245
264
  return client;
246
- }
265
+ };
247
266
  getLocalClient() {
248
267
  return this.service?.client.loadClient();
249
268
  }
@@ -252,14 +271,14 @@ class Account extends commons_1.TypedEventEmitter {
252
271
  *
253
272
  * @returns The local existing client or undefined if the client does not exist or is not valid (non existing on backend)
254
273
  */
255
- async initClient(client, mlsConfig) {
274
+ initClient = async (client, mlsConfig) => {
256
275
  if (!this.service || !this.apiClient.context || !this.storeEngine) {
257
276
  throw new Error('Services are not set.');
258
277
  }
259
278
  this.apiClient.context.clientId = client.id;
260
279
  // Call /access endpoint with client_id after client initialisation
261
280
  await this.apiClient.transport.http.associateClientWithSession(client.id);
262
- await this.service.proteus.initClient(this.storeEngine, this.apiClient.context);
281
+ await this.service.proteus.initClient(this.apiClient.context);
263
282
  if ((await this.isMLSActiveForClient()) && this.service.mls && mlsConfig) {
264
283
  const { userId, domain = '' } = this.apiClient.context;
265
284
  await this.service.mls.initClient({ id: userId, domain }, client, mlsConfig);
@@ -272,8 +291,8 @@ class Account extends commons_1.TypedEventEmitter {
272
291
  }
273
292
  this.currentClient = client;
274
293
  return client;
275
- }
276
- async buildCryptoClient(context, storeEngine, encryptedStore) {
294
+ };
295
+ buildCryptoClient = async (context, storeEngine, encryptedStore) => {
277
296
  const baseConfig = {
278
297
  nbPrekeys: this.options.nbPrekeys,
279
298
  onNewPrekeys: async (prekeys) => {
@@ -282,20 +301,19 @@ class Account extends commons_1.TypedEventEmitter {
282
301
  this.logger.debug(`Successfully uploaded '${prekeys.length}' PreKeys.`);
283
302
  },
284
303
  };
285
- const coreCryptoConfig = this.coreCryptoConfig;
286
- if (coreCryptoConfig) {
304
+ if (this.options.coreCryptoConfig?.enabled) {
287
305
  const { buildClient } = await Promise.resolve().then(() => __importStar(require('./messagingProtocols/proteus/ProteusService/CryptoClient/CoreCryptoWrapper')));
288
306
  const client = await buildClient(storeEngine, {
289
307
  ...baseConfig,
290
- ...coreCryptoConfig,
291
- generateSecretKey: keyId => (0, secretKeyGenerator_1.generateSecretKey)({ keyId, keySize: 16, secretsDb: encryptedStore }),
292
- });
308
+ generateSecretKey: (keyId, keySize) => (0, secretKeyGenerator_1.generateSecretKey)({ keyId, keySize, secretsDb: encryptedStore }),
309
+ }, this.options.coreCryptoConfig);
310
+ this.setMaxCoreCryptoLogLevel = client.setMaxLogLevel;
293
311
  return [CryptoClient_1.CryptoClientType.CORE_CRYPTO, client];
294
312
  }
295
313
  const { buildClient } = await Promise.resolve().then(() => __importStar(require('./messagingProtocols/proteus/ProteusService/CryptoClient/CryptoboxWrapper')));
296
314
  const client = buildClient(storeEngine, baseConfig);
297
315
  return [CryptoClient_1.CryptoClientType.CRYPTOBOX, client];
298
- }
316
+ };
299
317
  /**
300
318
  * In order to be able to send MLS messages, the core needs a few information from the consumer.
301
319
  * Namely:
@@ -303,10 +321,10 @@ class Account extends commons_1.TypedEventEmitter {
303
321
  * - what is the groupId of a conversation
304
322
  * @param coreCallbacks
305
323
  */
306
- configureCoreCallbacks(coreCallbacks) {
324
+ configureCoreCallbacks = (coreCallbacks) => {
307
325
  this.coreCallbacks = coreCallbacks;
308
- }
309
- async initServices(context) {
326
+ };
327
+ initServices = async (context) => {
310
328
  const encryptedStoreName = this.generateEncryptedDbName(context);
311
329
  this.encryptedDb = this.options.systemCrypto
312
330
  ? await (0, encryptedStore_1.createCustomEncryptedStore)(encryptedStoreName, this.options.systemCrypto)
@@ -322,7 +340,7 @@ class Account extends commons_1.TypedEventEmitter {
322
340
  const proteusService = new proteus_1.ProteusService(this.apiClient, cryptoClient, {
323
341
  onNewClient: payload => this.emit(EVENTS.NEW_SESSION, payload),
324
342
  nbPrekeys: this.options.nbPrekeys,
325
- });
343
+ }, this.storeEngine);
326
344
  const clientService = new client_2.ClientService(this.apiClient, proteusService, this.storeEngine);
327
345
  if (clientType === CryptoClient_1.CryptoClientType.CORE_CRYPTO && (await this.apiClient.supportsMLS())) {
328
346
  mlsService = new mls_1.MLSService(this.apiClient, cryptoClient.getNativeClient(), this.db, this.recurringTaskScheduler);
@@ -332,7 +350,7 @@ class Account extends commons_1.TypedEventEmitter {
332
350
  const giphyService = new giphy_1.GiphyService(this.apiClient);
333
351
  const linkPreviewService = new linkPreview_1.LinkPreviewService(assetService);
334
352
  const subconversationService = new SubconversationService_1.SubconversationService(this.apiClient, this.db, mlsService);
335
- const conversationService = new conversation_1.ConversationService(this.apiClient, proteusService, this.db, this.groupIdFromConversationId, subconversationService, mlsService);
353
+ const conversationService = new conversation_1.ConversationService(this.apiClient, proteusService, this.db, this.groupIdFromConversationId, subconversationService, this.isMLSConversationRecoveryEnabled, mlsService);
336
354
  const notificationService = new notification_1.NotificationService(this.apiClient, this.storeEngine, conversationService);
337
355
  const selfService = new self_1.SelfService(this.apiClient);
338
356
  const teamService = new team_2.TeamService(this.apiClient);
@@ -356,35 +374,88 @@ class Account extends commons_1.TypedEventEmitter {
356
374
  team: teamService,
357
375
  user: userService,
358
376
  };
359
- }
360
- resetContext() {
377
+ };
378
+ resetContext = () => {
361
379
  this.currentClient = undefined;
362
380
  delete this.apiClient.context;
363
381
  delete this.service;
364
- }
382
+ };
365
383
  /**
366
384
  * Will logout the current user
367
385
  * @param clearData if set to `true` will completely wipe any database that was created by the Account
368
386
  */
369
- async logout(clearData = false) {
387
+ logout = async (data) => {
370
388
  this.db?.close();
371
389
  this.encryptedDb?.close();
372
- if (clearData) {
373
- await this.wipe();
390
+ if (data?.clearAllData) {
391
+ await this.wipeAllData();
392
+ }
393
+ else if (data?.clearCryptoData) {
394
+ await this.wipeCryptoData();
374
395
  }
375
396
  await this.apiClient.logout();
376
397
  this.resetContext();
377
- }
398
+ };
399
+ wipeCommonData = async () => {
400
+ try {
401
+ await this.service?.client.deleteLocalClient();
402
+ }
403
+ catch (error) {
404
+ this.logger.error('Failed to delete local client during logout cleanup:', error);
405
+ }
406
+ try {
407
+ if (this.storeEngine) {
408
+ await (0, CoreCryptoWrapper_1.wipeCoreCryptoDb)(this.storeEngine);
409
+ }
410
+ }
411
+ catch (error) {
412
+ this.logger.error('Failed to wipe crypto database during logout cleanup:', error);
413
+ }
414
+ try {
415
+ // needs to be wiped last
416
+ await this.encryptedDb?.wipe();
417
+ }
418
+ catch (error) {
419
+ this.logger.error('Failed to delete encrypted database during logout cleanup:', error);
420
+ }
421
+ };
378
422
  /**
379
- * Will delete the identity of the current user
423
+ * Will delete the identity and history of the current user
380
424
  */
381
- async wipe() {
382
- await this.service?.proteus.wipe(this.storeEngine);
383
- if (this.db) {
384
- await (0, CoreDB_1.deleteDB)(this.db);
425
+ wipeAllData = async () => {
426
+ try {
427
+ if (this.storeEngine) {
428
+ await (0, identityClearer_1.deleteIdentity)(this.storeEngine, false);
429
+ }
385
430
  }
386
- await this.encryptedDb?.wipe();
387
- }
431
+ catch (error) {
432
+ this.logger.error('Failed to delete identity during logout cleanup:', error);
433
+ }
434
+ try {
435
+ if (this.db) {
436
+ await (0, CoreDB_1.deleteDB)(this.db);
437
+ }
438
+ }
439
+ catch (error) {
440
+ this.logger.error('Failed to delete database during logout cleanup:', error);
441
+ }
442
+ await this.wipeCommonData();
443
+ };
444
+ /**
445
+ * Will delete the cryptography and client of the current user
446
+ * Will keep the history intact
447
+ */
448
+ wipeCryptoData = async () => {
449
+ try {
450
+ if (this.storeEngine) {
451
+ await (0, identityClearer_1.deleteIdentity)(this.storeEngine, true);
452
+ }
453
+ }
454
+ catch (error) {
455
+ this.logger.error('Failed to delete identity during logout cleanup:', error);
456
+ }
457
+ await this.wipeCommonData();
458
+ };
388
459
  /**
389
460
  * return true if the current user has a MLS device that is initialized and ready to use
390
461
  */
@@ -398,11 +469,105 @@ class Account extends commons_1.TypedEventEmitter {
398
469
  * @param callbacks callbacks that will be called to handle different events
399
470
  * @returns close a function that will disconnect from the websocket
400
471
  */
401
- listen({ onEvent = () => { }, onConnectionStateChanged = () => { }, onNotificationStreamProgress = () => { }, onMissedNotifications = () => { }, dryRun = false, } = {}) {
472
+ listen = async ({ useLegacy, onEvent = async () => { }, onConnectionStateChanged: onConnectionStateChangedCallBack = () => { }, onNotificationStreamProgress = () => { }, onMissedNotifications = () => { }, } = {}) => {
402
473
  if (!this.currentClient) {
403
474
  throw new Error('Client has not been initialized - please login first');
404
475
  }
405
- const handleEvent = async (payload, source) => {
476
+ if (typeof useLegacy !== 'boolean') {
477
+ throw new Error('use of legacy notifications must be explicitly set to true or false');
478
+ }
479
+ const onConnectionStateChanged = this.createConnectionStateChangedHandler(onConnectionStateChangedCallBack);
480
+ const handleEvent = this.createEventHandler(onEvent);
481
+ const handleLegacyNotification = this.createLegacyNotificationHandler(handleEvent, onNotificationStreamProgress);
482
+ const handleNotification = this.createNotificationHandler(handleEvent, onNotificationStreamProgress, onConnectionStateChanged);
483
+ const handleMissedNotifications = this.createLegacyMissedNotificationsHandler(onMissedNotifications);
484
+ const legacyProcessNotificationStream = this.createLegacyNotificationStreamProcessor({
485
+ handleLegacyNotification,
486
+ handleMissedNotifications,
487
+ onConnectionStateChanged,
488
+ });
489
+ this.setupWebSocketListeners(onConnectionStateChanged, handleNotification, handleLegacyNotification, useLegacy);
490
+ const isClientCapableOfConsumableNotifications = this.getClientCapabilities().includes(client_1.ClientCapability.CONSUMABLE_NOTIFICATIONS);
491
+ const capabilities = [client_1.ClientCapability.LEGAL_HOLD_IMPLICIT_CONSENT];
492
+ if (!useLegacy) {
493
+ // let the backend now client is capable of consumable notifications
494
+ capabilities.push(client_1.ClientCapability.CONSUMABLE_NOTIFICATIONS);
495
+ this.apiClient.transport.ws.useAsyncNotificationsSocket();
496
+ }
497
+ this.logger.info(`Client is using the ${useLegacy ? 'legacy' : 'async'} notification stream`);
498
+ await this.service?.client.putClientCapabilities(this.currentClient.id, { capabilities });
499
+ /*
500
+ * When enabling async notifications, be aware that the backend maintains a separate queue
501
+ * for new async notifications (/events weboscket endpoint), which only starts populating *after* the client declares support
502
+ * for async notifications.
503
+ *
504
+ * Therefore, after declaring support, it's necessary to perform one final fetch from the legacy
505
+ * system to ensure no notifications are missed—since older notifications won't
506
+ * appear in the new queue.
507
+ *
508
+ * These two systems are separate, and the transition timing
509
+ * is important to avoid missing any messages during the switch.
510
+ *
511
+ * @todo This can be removed when all clients are capable of consumable notifications.
512
+ */
513
+ if (!isClientCapableOfConsumableNotifications && !useLegacy) {
514
+ // do the last legacy sync without connecting to any websockets
515
+ await legacyProcessNotificationStream();
516
+ this.logger.info('Completed final legacy notification stream processing after enabling async notifications');
517
+ }
518
+ if (useLegacy) {
519
+ /**
520
+ * immediately lock the websocket to prevent any new messages from being received
521
+ * before legacy notifications endpoint is fetched otherwise it'll update the last notification ID
522
+ * and fetching legacy notifications will return an empty list
523
+ */
524
+ this.apiClient.transport.ws.lock();
525
+ }
526
+ this.apiClient.connect(async (abortController) => {
527
+ // this call back is called every single time the websocket connection is (re)established
528
+ this.logger.info('Connection established with websocket, starting notification stream processing');
529
+ /**
530
+ * This is to avoid passing proposals too early to core crypto
531
+ * @See WPB-18995
532
+ */
533
+ (0, IncomingProposalsQueue_1.pauseProposalProcessing)();
534
+ (0, messageSender_1.pauseMessageSending)(); // pause message sending while processing notifications, it will be resumed once the processing is done and we have the marker token
535
+ (0, conversationRejoinQueue_1.pauseRejoiningMLSConversations)(); // We want to avoid triggering rejoins of out-of-sync MLS conversations while we are processing the notification stream
536
+ /**
537
+ * resume the notification processing queue
538
+ * it will start processing notifications immediately and pause if web socket connection drops
539
+ * we should start decryption and therefore acknowledging the notifications in order for the backend to
540
+ * send us the next batch of notifications, currently total size of notifications coming from web socket is limited to 500
541
+ * so we need to acknowledge the notifications to let the backend know we are ready for the next batch
542
+ */
543
+ this.notificationProcessingQueue.resume();
544
+ if (useLegacy) {
545
+ await legacyProcessNotificationStream(abortController);
546
+ }
547
+ });
548
+ return () => {
549
+ this.logger.info('Disconnecting from backend as requested by consumer');
550
+ (0, IncomingProposalsQueue_1.flushProposalsQueue)();
551
+ this.pauseAndFlushNotificationQueue();
552
+ this.apiClient.disconnect();
553
+ onConnectionStateChanged(ConnectionState.CLOSED);
554
+ this.apiClient.transport.ws.removeAllListeners();
555
+ };
556
+ };
557
+ createConnectionStateChangedHandler = (onConnectionStateChanged) => {
558
+ return (state) => {
559
+ this.connectionState = state;
560
+ onConnectionStateChanged(state);
561
+ this.logger.info(`Connection state changed to: ${state}`);
562
+ };
563
+ };
564
+ /**
565
+ * Creates the event handler that is invoked for each decrypted event from the backend.
566
+ * Responsible for handling specific event types like `MESSAGE_TIMER_UPDATE`, and then
567
+ * forwarding the event to the consumer via the `onEvent` callback.
568
+ */
569
+ createEventHandler = (onEvent) => {
570
+ return async (payload, source) => {
406
571
  const { event } = payload;
407
572
  switch (event?.type) {
408
573
  case event_1.CONVERSATION_EVENT.MESSAGE_TIMER_UPDATE: {
@@ -412,80 +577,290 @@ class Account extends commons_1.TypedEventEmitter {
412
577
  break;
413
578
  }
414
579
  }
580
+ // Always forward the event to the consumer
415
581
  await onEvent(payload, source);
416
582
  };
417
- const handleNotification = async (notification, source) => {
583
+ };
584
+ /**
585
+ * @deprecated This method is used to handle legacy notifications from the backend.
586
+ * It processes notifications from the legacy system, decrypts them, and emits events.
587
+ * It can be replaced with the new notification handling system using `ConsumableNotification`
588
+ * when all clients are capable of handling consumable notifications.
589
+ */
590
+ createLegacyNotificationHandler = (handleEvent, onNotificationStreamProgress) => {
591
+ return async (notification, source) => {
592
+ void this.notificationProcessingQueue
593
+ .push(async () => {
594
+ try {
595
+ const start = Date.now();
596
+ const notificationTime = this.getNotificationEventTime(notification.payload[0]);
597
+ this.logger.info(`Processing legacy notification "${notification.id}" at ${notificationTime}`, {
598
+ notification,
599
+ });
600
+ this.logger.info(`Total notifications queue length: ${this.notificationProcessingQueue.getLength()}`);
601
+ this.logger.info(`Total pending proposals queue length: ${(0, IncomingProposalsQueue_1.getProposalQueueLength)()}`);
602
+ if (notificationTime) {
603
+ onNotificationStreamProgress(notificationTime);
604
+ }
605
+ const messages = this.service.notification.handleNotification(notification, source);
606
+ for await (const message of messages) {
607
+ await handleEvent(message, source);
608
+ }
609
+ this.logger.info(`Finished processing legacy notification "${notification.id}" in ${Date.now() - start}ms`);
610
+ }
611
+ catch (error) {
612
+ this.logger.error(`Failed to handle legacy notification "${notification.id}": ${error.message}`, error);
613
+ }
614
+ })
615
+ .catch(this.handleNotificationQueueError);
616
+ };
617
+ };
618
+ createNotificationHandler = (handleEvent, onNotificationStreamProgress, onConnectionStateChanged) => {
619
+ return async (notification, source) => {
620
+ this.logger.info(`Received consumable notification of type "${notification.type}"`, { notification });
418
621
  try {
419
- const messages = this.service.notification.handleNotification(notification, source, dryRun);
420
- for await (const message of messages) {
421
- await handleEvent(message, source);
622
+ if (notification.type === ConsumableNotification_1.ConsumableEvent.MISSED) {
623
+ this.reactToMissedNotification();
624
+ return;
625
+ }
626
+ if (notification.type === ConsumableNotification_1.ConsumableEvent.SYNCHRONIZATION) {
627
+ this.notificationProcessingQueue
628
+ .push(() => this.handleSynchronizationNotification(notification, onConnectionStateChanged))
629
+ .catch(this.handleNotificationQueueError);
630
+ return;
422
631
  }
632
+ this.notificationProcessingQueue
633
+ .push(() => this.decryptAckEmitNotification(notification, handleEvent, source, onNotificationStreamProgress))
634
+ .catch(this.handleNotificationQueueError);
423
635
  }
424
636
  catch (error) {
425
- this.logger.error(`Failed to handle notification ID "${notification.id}": ${error.message}`, error);
637
+ this.logger.error(`Failed to handle notification "${notification.type}": ${error.message}`, error);
426
638
  }
427
639
  };
428
- this.apiClient.transport.ws.removeAllListeners(tcp_1.WebSocketClient.TOPIC.ON_MESSAGE);
429
- this.apiClient.transport.ws.on(tcp_1.WebSocketClient.TOPIC.ON_MESSAGE, notification => handleNotification(notification, notification_1.NotificationSource.WEBSOCKET));
430
- this.apiClient.transport.ws.on(tcp_1.WebSocketClient.TOPIC.ON_STATE_CHANGE, wsState => {
431
- const mapping = {
432
- [ReconnectingWebsocket_1.WEBSOCKET_STATE.CLOSED]: ConnectionState.CLOSED,
433
- [ReconnectingWebsocket_1.WEBSOCKET_STATE.CONNECTING]: ConnectionState.CONNECTING,
434
- };
435
- const connectionState = mapping[wsState];
436
- if (connectionState) {
437
- onConnectionStateChanged(connectionState);
640
+ };
641
+ handleNotificationQueueError = (error) => {
642
+ if (!(error instanceof Error)) {
643
+ throw error;
644
+ }
645
+ switch (error.cause) {
646
+ case promise_queue_1.PromiseQueue.ERROR_CAUSES.TIMEOUT:
647
+ this.logger.warn('Notification decryption task timed out', error);
648
+ break;
649
+ case promise_queue_1.PromiseQueue.ERROR_CAUSES.FLUSHED:
650
+ this.logger.info('Notification processing queue was flushed, ignoring error', error);
651
+ break;
652
+ }
653
+ };
654
+ acknowledgeSynchronizationNotification = (notification) => {
655
+ this.apiClient.transport.ws.acknowledgeConsumableNotificationSynchronization(notification);
656
+ };
657
+ handleSynchronizationNotification = async (notification, onConnectionStateChanged) => {
658
+ this.logger.info('acknowledging synchronization notification', { notification });
659
+ this.acknowledgeSynchronizationNotification(notification);
660
+ const markerId = notification.data.marker_id;
661
+ const currentMarkerId = this.apiClient.transport.http.accessTokenStore.markerToken;
662
+ this.logger.info(`Handling synchronization notification with marker ID: ${markerId} current marker ID: ${currentMarkerId}`);
663
+ /**
664
+ * There is a chance that there might be multiple synchronization notifications (markers)
665
+ * in the queue in case websocket connection drops a few times
666
+ * Hence we only want to resume message sending and set the connection state to LIVE
667
+ * if the marker ID matches the current marker ID.
668
+ */
669
+ if (markerId === currentMarkerId) {
670
+ (0, IncomingProposalsQueue_1.resumeProposalProcessing)();
671
+ (0, messageSender_1.resumeMessageSending)();
672
+ (0, conversationRejoinQueue_1.resumeRejoiningMLSConversations)();
673
+ onConnectionStateChanged(ConnectionState.LIVE);
674
+ }
675
+ };
676
+ decryptAckEmitNotification = async (notification, handleEvent, source, onNotificationStreamProgress) => {
677
+ try {
678
+ this.logger.info(`Sending consumable notification for decryption`, notification.data.event.id);
679
+ const payloads = this.service.notification.handleNotification(notification.data.event, source);
680
+ const notificationTime = this.getNotificationEventTime(notification.data.event.payload[0]);
681
+ if (this.connectionState !== ConnectionState.LIVE && notificationTime) {
682
+ onNotificationStreamProgress(notificationTime);
438
683
  }
439
- });
440
- const handleMissedNotifications = async (notificationId) => {
684
+ for await (const payload of payloads ?? []) {
685
+ await handleEvent(payload, source);
686
+ }
687
+ this.logger.info(`Acknowledging consumable notification on the backend "${notification.data.delivery_tag}"`);
688
+ this.apiClient.transport.ws.acknowledgeNotification(notification);
689
+ }
690
+ catch (err) {
691
+ this.logger.error(`Failed to process notification ${notification.data.delivery_tag}`, err);
692
+ }
693
+ };
694
+ getNotificationEventTime = (backendEvent) => {
695
+ if ('time' in backendEvent && typeof backendEvent.time === 'string') {
696
+ return backendEvent.time;
697
+ }
698
+ return null;
699
+ };
700
+ /**
701
+ * Returns a function to handle missed notifications — i.e., when the backend indicates
702
+ * that some notifications were lost due to age (typically >28 days).
703
+ * Also handles MLS-specific epoch mismatch recovery by triggering a conversation rejoin.
704
+ *
705
+ * @deprecated This is used to handle legacy missed notifications.
706
+ * It should be replaced with the new notification handling system using `ConsumableNotification`.
707
+ * when all clients are capable of handling consumable notifications.
708
+ */
709
+ createLegacyMissedNotificationsHandler = (onMissedNotifications) => {
710
+ return async (notificationId) => {
441
711
  if (this.hasMLSDevice) {
442
- (0, conversationRejoinQueue_1.queueConversationRejoin)('all-conversations', () => this.service.conversation.handleConversationsEpochMismatch());
712
+ void (0, conversationRejoinQueue_1.queueConversationRejoin)('all-conversations', () => this.service.conversation.handleConversationsEpochMismatch());
443
713
  }
444
714
  return onMissedNotifications(notificationId);
445
715
  };
446
- const processNotificationStream = async (abortHandler) => {
447
- // Lock websocket in order to buffer any message that arrives while we handle the notification stream
716
+ };
717
+ /**
718
+ * Returns a processor function for the notification stream (legacy sync).
719
+ * It pauses message sending and MLS rejoining during stream handling to prevent race conditions,
720
+ * then resumes normal operations after sync is complete.
721
+ *
722
+ * @deprecated This is used to do a final sync of the legacy notification stream
723
+ * before switching to the new notification handling system using `ConsumableNotification`.
724
+ * It should be replaced with the new notification handling system when all clients are capable of handling consumable notifications.
725
+ *
726
+ * @param handlers Various logic handlers wired to notification callbacks
727
+ */
728
+ createLegacyNotificationStreamProcessor = ({ handleLegacyNotification, handleMissedNotifications, onConnectionStateChanged, }) => {
729
+ return async (abortController) => {
448
730
  this.apiClient.transport.ws.lock();
731
+ (0, IncomingProposalsQueue_1.pauseProposalProcessing)();
449
732
  (0, messageSender_1.pauseMessageSending)();
450
- // We want to avoid triggering rejoins of out-of-sync MLS conversations while we are processing the notification stream
451
733
  (0, conversationRejoinQueue_1.pauseRejoiningMLSConversations)();
452
734
  onConnectionStateChanged(ConnectionState.PROCESSING_NOTIFICATIONS);
453
- const results = await this.service.notification.processNotificationStream(async (notification, source, progress) => {
454
- await handleNotification(notification, source);
455
- onNotificationStreamProgress(progress);
456
- }, handleMissedNotifications, abortHandler);
457
- this.logger.info('Finished processing notifications', results);
458
- if (abortHandler.signal.aborted) {
459
- this.logger.warn('Ending connection process as websocket was closed');
460
- return;
461
- }
462
- onConnectionStateChanged(ConnectionState.LIVE);
463
- // We can now unlock the websocket and let the new messages being handled and decrypted
464
- this.apiClient.transport.ws.unlock();
735
+ const results = await this.service.notification.legacyProcessNotificationStream(async (notification, source) => {
736
+ await handleLegacyNotification(notification, source);
737
+ }, handleMissedNotifications, abortController);
738
+ this.logger.info('Finished inserting notifications for decryption from the legacy endpoint to the process queue', results);
465
739
  // We need to wait for the notification stream to be fully handled before releasing the message sending queue.
466
740
  // This is due to the nature of how message are encrypted, any change in mls epoch needs to happen before we start encrypting any kind of messages
467
- this.logger.info(`Resuming message sending. ${(0, messageSender_1.getQueueLength)()} messages to be sent`);
468
- (0, messageSender_1.resumeMessageSending)();
469
- (0, conversationRejoinQueue_1.resumeRejoiningMLSConversations)();
470
- };
471
- this.apiClient.connect(processNotificationStream);
472
- return () => {
473
- this.apiClient.disconnect();
474
- onConnectionStateChanged(ConnectionState.CLOSED);
475
- this.apiClient.transport.ws.removeAllListeners();
741
+ void this.notificationProcessingQueue
742
+ .push(async () => {
743
+ this.logger.info(`Resuming message sending. ${(0, messageSender_1.getQueueLength)()} messages to be sent`);
744
+ (0, IncomingProposalsQueue_1.resumeProposalProcessing)();
745
+ (0, messageSender_1.resumeMessageSending)();
746
+ (0, conversationRejoinQueue_1.resumeRejoiningMLSConversations)();
747
+ onConnectionStateChanged(ConnectionState.LIVE);
748
+ this.apiClient.transport.ws.unlock();
749
+ })
750
+ .catch(this.handleNotificationQueueError);
476
751
  };
477
- }
478
- generateDbName(context) {
752
+ };
753
+ /**
754
+ * In case of a closed connection, we flush the notification processing queue.
755
+ * As we are not acknowledging them before decryption is done
756
+ * they will be resent next time the connection is opened
757
+ * this is to avoid duplicate decryption of notifications
758
+ */
759
+ pauseAndFlushNotificationQueue = () => {
760
+ this.notificationProcessingQueue.pause();
761
+ this.notificationProcessingQueue.flush();
762
+ this.logger.info('Notification processing queue paused and flushed');
763
+ };
764
+ pauseNotificationQueue = () => {
765
+ this.notificationProcessingQueue.pause();
766
+ this.logger.info('Notification processing queue paused');
767
+ };
768
+ resumeNotificationQueue = () => {
769
+ this.notificationProcessingQueue.resume();
770
+ this.logger.info('Notification processing queue resumed');
771
+ };
772
+ /**
773
+ * Sets up WebSocket event listeners for:
774
+ * - Incoming backend messages
775
+ * - WebSocket state changes
776
+ * On each new backend message, we pass it to the notification handler.
777
+ * On state changes, we map raw socket states to public connection states and emit them.
778
+ */
779
+ setupWebSocketListeners = (onConnectionStateChanged, handleNotification, handleLegacyNotification, useLegacy) => {
780
+ this.logger.info('Setting up WebSocket listeners');
781
+ this.apiClient.transport.ws.removeAllListeners(tcp_1.WebSocketClient.TOPIC.ON_MESSAGE);
782
+ this.apiClient.transport.ws.on(tcp_1.WebSocketClient.TOPIC.ON_MESSAGE, notification => {
783
+ this.logger.info('Received new notification from backend', { notification });
784
+ if (Account.checkIsConsumable(notification)) {
785
+ void handleNotification(notification, notification_1.NotificationSource.WEBSOCKET);
786
+ return;
787
+ }
788
+ void handleLegacyNotification(notification, notification_1.NotificationSource.WEBSOCKET);
789
+ });
790
+ this.apiClient.transport.ws.on(tcp_1.WebSocketClient.TOPIC.ON_STATE_CHANGE, wsState => {
791
+ const mapping = {
792
+ [ReconnectingWebsocket_1.WEBSOCKET_STATE.CLOSED]: ConnectionState.CLOSED,
793
+ [ReconnectingWebsocket_1.WEBSOCKET_STATE.CONNECTING]: ConnectionState.CONNECTING,
794
+ };
795
+ const connectionState = mapping[wsState];
796
+ if (connectionState === ConnectionState.CLOSED) {
797
+ (0, IncomingProposalsQueue_1.flushProposalsQueue)();
798
+ this.pauseAndFlushNotificationQueue();
799
+ if (useLegacy) {
800
+ this.apiClient.transport.ws.lock();
801
+ }
802
+ }
803
+ if (connectionState) {
804
+ onConnectionStateChanged(connectionState);
805
+ }
806
+ });
807
+ };
808
+ /**
809
+ * Handles logic for reacting to a missed notification event.
810
+ *
811
+ * The backend sends a special "missed notification" signal if it detects
812
+ * that the client has missed one or more notifications. Once this signal is sent,
813
+ * the backend will **stop sending all further notifications** until the client
814
+ * acknowledges the missed state.
815
+ *
816
+ * Because our app currently lacks functionality to perform a full real-time sync
817
+ * while running, we must reload the application to re-fetch the entire state.
818
+ *
819
+ * On first detection of the missed notification:
820
+ * - We set a local storage flag (`has_missing_notification`) to mark that we've
821
+ * entered this state.
822
+ * - We reload the application so the state can be re-fetched from scratch.
823
+ *
824
+ * On the next load:
825
+ * - If the flag is already present, we acknowledge the missed notification via
826
+ * the WebSocket transport, unblocking the backend so it resumes sending updates
827
+ * then we remove the flag.
828
+ */
829
+ reactToMissedNotification = () => {
830
+ this.logger.info('Reacting to missed notification from consumable async websocket');
831
+ const localStorageKey = 'has_missing_notification';
832
+ // First-time handling: set flag and reload to trigger full re-fetch of state.
833
+ if (!exports.AccountLocalStorageStore.has(localStorageKey)) {
834
+ this.logger.info('First missed notification detected, reloading to recover state');
835
+ exports.AccountLocalStorageStore.add(localStorageKey, 'true');
836
+ window.location.reload();
837
+ return;
838
+ }
839
+ this.logger.info('Missed notification previously detected, acknowledging to resume updates');
840
+ // After reload: acknowledge the missed notification so backend resumes notifications.
841
+ this.apiClient.transport.ws.acknowledgeMissedNotification();
842
+ exports.AccountLocalStorageStore.remove(localStorageKey);
843
+ };
844
+ getClientCapabilities = () => {
845
+ return this.currentClient?.capabilities || [];
846
+ };
847
+ static checkIsConsumable = (notification) => {
848
+ return 'type' in notification;
849
+ };
850
+ static checkIsLegacyNotification = (notification) => {
851
+ return !Account.checkIsConsumable(notification);
852
+ };
853
+ generateDbName = (context) => {
479
854
  const clientType = context.clientType === client_1.ClientType.NONE ? '' : `@${context.clientType}`;
480
855
  return `wire@${this.apiClient.config.urls.name}@${context.userId}${clientType}`;
481
- }
482
- generateCoreDbName(context) {
856
+ };
857
+ generateCoreDbName = (context) => {
483
858
  return `core-${this.generateDbName(context)}`;
484
- }
485
- generateEncryptedDbName(context) {
859
+ };
860
+ generateEncryptedDbName = (context) => {
486
861
  return `secrets-${this.generateDbName(context)}`;
487
- }
488
- async initEngine(context, encryptedStore) {
862
+ };
863
+ initEngine = async (context, encryptedStore) => {
489
864
  const dbName = this.generateDbName(context);
490
865
  this.logger.debug(`Initialising store with name "${dbName}"...`);
491
866
  const openDb = async () => {
@@ -506,28 +881,43 @@ class Account extends commons_1.TypedEventEmitter {
506
881
  await this.persistCookie(storeEngine, cookie);
507
882
  }
508
883
  return storeEngine;
509
- }
884
+ };
510
885
  groupIdFromConversationId = async (conversationId, subconversationId) => {
511
886
  if (!subconversationId) {
512
887
  return this.coreCallbacks?.groupIdFromConversationId(conversationId);
513
888
  }
514
889
  return this.service?.subconversation.getSubconversationGroupId(conversationId, subconversationId);
515
890
  };
516
- async isMLSActiveForClient() {
517
- // MLS service is initialized
518
- const isMLSServiceInitialized = this.service?.mls !== undefined;
519
- if (!isMLSServiceInitialized) {
891
+ isMLSActiveForClient = async () => {
892
+ // Check for CoreCrypto library, it is required for MLS
893
+ if (!this.options.coreCryptoConfig?.enabled) {
520
894
  return false;
521
895
  }
522
- // Backend Supports MLS trough removal keys
523
- const isMLSSupported = await this.apiClient.supportsMLS();
524
- if (!isMLSSupported) {
896
+ // Check if the backend supports MLS trough removal keys
897
+ if (!(await this.apiClient.supportsMLS())) {
525
898
  return false;
526
899
  }
527
- // MLS is enabled for the public via feature flag
900
+ // Check if MLS is enabled for the public via backend feature flag
528
901
  const commonConfig = (await this.service?.team.getCommonFeatureConfig()) ?? {};
529
- const isMLSForTeamEnabled = commonConfig[team_1.FEATURE_KEY.MLS]?.status === team_1.FeatureStatus.ENABLED;
530
- return isMLSSupported && isMLSForTeamEnabled && isMLSServiceInitialized;
531
- }
902
+ return commonConfig[team_1.FEATURE_KEY.MLS]?.status === team_1.FEATURE_STATUS.ENABLED;
903
+ };
904
+ isMLSConversationRecoveryEnabled = async () => {
905
+ const commonConfig = (await this.service?.team.getCommonFeatureConfig()) ?? {};
906
+ return commonConfig[team_1.FEATURE_KEY.ALLOWED_GLOBAL_OPERATIONS]?.config?.mlsConversationReset === true;
907
+ };
908
+ /**
909
+ * Checks the health of the WebSocket connection by sending a ping and waiting for a pong response.
910
+ * This is a non-disruptive check that does not close or reconnect the socket.
911
+ * @returns A promise that resolves to true if the socket responds with a pong within the timeout, false otherwise.
912
+ *
913
+ * @example
914
+ * const healthy = await account.isWebsocketHealthy();
915
+ * if (!healthy) {
916
+ * // handle unhealthy websocket
917
+ * }
918
+ */
919
+ isWebsocketHealthy = async () => {
920
+ return this.apiClient.transport.ws.checkHealth();
921
+ };
532
922
  }
533
923
  exports.Account = Account;