@wireapp/core 28.4.7 → 28.5.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.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,17 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ # [28.5.0](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@28.4.7...@wireapp/core@28.5.0) (2022-07-15)
7
+
8
+
9
+ ### Features
10
+
11
+ * Decrypt incoming MLS messages ([#4315](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/issues/4315)) ([c93b0ae](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/commit/c93b0aea0a43f5eef204d5a20ca047215d6998d6))
12
+
13
+
14
+
15
+
16
+
6
17
  ## [28.4.7](https://github.com/wireapp/wire-web-packages/tree/main/packages/core/compare/@wireapp/core@28.4.6...@wireapp/core@28.4.7) (2022-07-15)
7
18
 
8
19
  **Note:** Version bump only for package @wireapp/core
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "@otak/core-crypto": "0.3.0-beta-1",
8
8
  "@types/long": "4.0.1",
9
9
  "@types/node": "~14",
10
- "@wireapp/api-client": "19.14.1",
10
+ "@wireapp/api-client": "19.15.0",
11
11
  "@wireapp/commons": "4.3.0",
12
12
  "@wireapp/cryptobox": "12.8.0",
13
13
  "@wireapp/store-engine-dexie": "1.6.10",
@@ -42,7 +42,6 @@
42
42
  "karma-spec-reporter": "0.0.32",
43
43
  "mock-socket": "9.0.3",
44
44
  "nock": "13.1.1",
45
- "nodemon": "2.0.19",
46
45
  "nyc": "15.1.0",
47
46
  "rimraf": "3.0.2",
48
47
  "typescript": "4.4.2",
@@ -76,9 +75,8 @@
76
75
  "test:browser": "webpack --mode=development && karma start",
77
76
  "test:project": "yarn dist && yarn test",
78
77
  "test:node": "nyc jasmine --config=jasmine.json",
79
- "watch": "tsc ---watch",
80
- "test:watch": "nodemon --ext ts --exec 'yarn test:node'"
78
+ "watch": "tsc ---watch"
81
79
  },
82
- "version": "28.4.7",
83
- "gitHead": "bbe98e85b48966ae66bb4439f6560d0920368063"
80
+ "version": "28.5.0",
81
+ "gitHead": "9d06dbf709b35ec4c5d0f3e2aa3b2fdd370da607"
84
82
  }
@@ -229,11 +229,11 @@ class Account extends events_1.EventEmitter {
229
229
  const connectionService = new connection_1.ConnectionService(this.apiClient);
230
230
  const giphyService = new giphy_1.GiphyService(this.apiClient);
231
231
  const linkPreviewService = new linkPreview_1.LinkPreviewService(assetService);
232
+ const notificationService = new notification_1.NotificationService(this.apiClient, cryptographyService, this.storeEngine, () => this.coreCryptoClient);
232
233
  const conversationService = new conversation_1.ConversationService(this.apiClient, cryptographyService, {
233
234
  // We can use qualified ids to send messages as long as the backend supports federated endpoints
234
235
  useQualifiedIds: this.backendFeatures.federationEndpoints,
235
- }, () => this.coreCryptoClient);
236
- const notificationService = new notification_1.NotificationService(this.apiClient, cryptographyService, this.storeEngine);
236
+ }, () => this.coreCryptoClient, notificationService);
237
237
  const selfService = new self_1.SelfService(this.apiClient);
238
238
  const teamService = new team_1.TeamService(this.apiClient);
239
239
  const broadcastService = new broadcast_1.BroadcastService(this.apiClient, cryptographyService);
@@ -9,22 +9,39 @@ import type { RemoteData } from '../conversation/content/';
9
9
  import type { CryptographyService } from '../cryptography/';
10
10
  import type { ClearConversationMessage, DeleteMessage, HideMessage, OtrMessage } from './message/OtrMessage';
11
11
  import { XOR } from '@wireapp/commons/src/main/util/TypeUtil';
12
+ import type { NotificationService } from '../notification';
12
13
  export declare enum MessageTargetMode {
13
14
  NONE = 0,
14
15
  USERS = 1,
15
16
  USERS_CLIENTS = 2
16
17
  }
17
18
  interface SendCommonParams<T> {
19
+ /**
20
+ * The protocol to use to send the message (MLS or Proteus)
21
+ */
18
22
  protocol: ConversationProtocol;
23
+ /**
24
+ * The message to send to the conversation
25
+ */
19
26
  payload: T;
20
27
  onStart?: (message: GenericMessage) => void | boolean | Promise<boolean>;
21
28
  onSuccess?: (message: GenericMessage, sentTime?: string) => void;
22
29
  }
23
30
  declare type SendProteusMessageParams<T> = SendCommonParams<T> & MessageSendingOptions & {
31
+ /**
32
+ * Can be either a QualifiedId[], string[], UserClients or QualfiedUserClients. The type has some effect on the behavior of the method. (Needed only for Proteus)
33
+ * When given a QualifiedId[] or string[] the method will fetch the freshest list of devices for those users (since they are not given by the consumer). As a consequence no ClientMismatch error will trigger and we will ignore missing clients when sending
34
+ * When given a QualifiedUserClients or UserClients the method will only send to the clients listed in the userIds. This could lead to ClientMismatch (since the given list of devices might not be the freshest one and new clients could have been created)
35
+ * When given a QualifiedId[] or QualifiedUserClients the method will send the message through the federated API endpoint
36
+ * When given a string[] or UserClients the method will send the message through the old API endpoint
37
+ */
24
38
  userIds?: string[] | QualifiedId[] | UserClients | QualifiedUserClients;
25
39
  onClientMismatch?: (status: ClientMismatch | MessageSendingStatus, wasSent: boolean) => void | boolean | Promise<boolean>;
26
40
  };
27
41
  declare type SendMlsMessageParams<T> = SendCommonParams<T> & {
42
+ /**
43
+ * The groupId of the conversation to send the message to (Needed only for MLS)
44
+ */
28
45
  groupId: string;
29
46
  };
30
47
  interface MessageSendingOptions {
@@ -77,12 +94,13 @@ export declare class ConversationService {
77
94
  private readonly apiClient;
78
95
  private readonly config;
79
96
  private readonly coreCryptoClientProvider;
97
+ private readonly notificationService;
80
98
  readonly messageTimer: MessageTimer;
81
99
  private readonly messageService;
82
100
  private selfConversationId?;
83
101
  constructor(apiClient: APIClient, cryptographyService: CryptographyService, config: {
84
102
  useQualifiedIds?: boolean;
85
- }, coreCryptoClientProvider: () => CoreCrypto);
103
+ }, coreCryptoClientProvider: () => CoreCrypto, notificationService: NotificationService);
86
104
  private createEphemeral;
87
105
  private getConversationQualifiedMembers;
88
106
  /**
@@ -191,24 +209,7 @@ export declare class ConversationService {
191
209
  getUnencryptedAsset(assetId: string, assetToken?: string): Promise<ArrayBuffer>;
192
210
  addUser(conversationId: QualifiedId, userIds: string | string[] | QualifiedId | QualifiedId[]): Promise<QualifiedId[]>;
193
211
  removeUser(conversationId: string, userId: string): Promise<string>;
194
- /**
195
- * Sends a mls message to a conversation
196
- *
197
- * @param params.payload The message to send to the conversation
198
- * @param params.protocol The protocol to use to send the message (MLS or Proteus)
199
- * @param params.groupId? The groupId of the conversation to send the message to (Needed only for MLS)
200
- * @return resolves with the sent message
201
- */
202
212
  private sendMlsMessage;
203
- /**
204
- * Sends a proteus message to a conversation
205
- * @param params.userIds? Can be either a QualifiedId[], string[], UserClients or QualfiedUserClients. The type has some effect on the behavior of the method. (Needed only for Proteus)
206
- * When given a QualifiedId[] or string[] the method will fetch the freshest list of devices for those users (since they are not given by the consumer). As a consequence no ClientMismatch error will trigger and we will ignore missing clients when sending
207
- * When given a QualifiedUserClients or UserClients the method will only send to the clients listed in the userIds. This could lead to ClientMismatch (since the given list of devices might not be the freshest one and new clients could have been created)
208
- * When given a QualifiedId[] or QualifiedUserClients the method will send the message through the federated API endpoint
209
- * When given a string[] or UserClients the method will send the message through the old API endpoint
210
- * @return resolves with the sent message
211
- */
212
213
  private sendProteusMessage;
213
214
  /**
214
215
  * Sends a message to a conversation
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /*
3
3
  * Wire
4
- * Copyright (C) 2018 Wire Swiss GmbH
4
+ * Copyright (C) 2022 Wire Swiss GmbH
5
5
  *
6
6
  * This program is free software: you can redistribute it and/or modify
7
7
  * it under the terms of the GNU General Public License as published by
@@ -39,10 +39,11 @@ function isMLS(params) {
39
39
  return params.protocol === conversation_1.ConversationProtocol.MLS;
40
40
  }
41
41
  class ConversationService {
42
- constructor(apiClient, cryptographyService, config, coreCryptoClientProvider) {
42
+ constructor(apiClient, cryptographyService, config, coreCryptoClientProvider, notificationService) {
43
43
  this.apiClient = apiClient;
44
44
  this.config = config;
45
45
  this.coreCryptoClientProvider = coreCryptoClientProvider;
46
+ this.notificationService = notificationService;
46
47
  this.messageTimer = new conversation_2.MessageTimer();
47
48
  this.messageService = new MessageService_1.MessageService(this.apiClient, cryptographyService);
48
49
  }
@@ -685,6 +686,7 @@ class ConversationService {
685
686
  sendingPromises.push(this.apiClient.api.conversation.postMlsMessage(Uint8Array.from(memberAddedMessages.message)));
686
687
  }
687
688
  await Promise.all(sendingPromises);
689
+ await this.notificationService.saveConversationGroupId(newConversation);
688
690
  // We fetch the fresh version of the conversation created on backend with the newly added users
689
691
  return this.getConversations(newConversation.id);
690
692
  }
@@ -720,14 +722,6 @@ class ConversationService {
720
722
  await this.apiClient.api.conversation.deleteMember(conversationId, userId);
721
723
  return userId;
722
724
  }
723
- /**
724
- * Sends a mls message to a conversation
725
- *
726
- * @param params.payload The message to send to the conversation
727
- * @param params.protocol The protocol to use to send the message (MLS or Proteus)
728
- * @param params.groupId? The groupId of the conversation to send the message to (Needed only for MLS)
729
- * @return resolves with the sent message
730
- */
731
725
  async sendMlsMessage(params, genericMessage, content) {
732
726
  var _a, _b;
733
727
  const { groupId, onSuccess, payload } = params;
@@ -743,15 +737,6 @@ class ConversationService {
743
737
  return Object.assign(Object.assign({}, payload), { content, messageTimer: ((_b = genericMessage.ephemeral) === null || _b === void 0 ? void 0 : _b.expireAfterMillis) || 0, state: conversation_2.PayloadBundleState.CANCELLED });
744
738
  }
745
739
  }
746
- /**
747
- * Sends a proteus message to a conversation
748
- * @param params.userIds? Can be either a QualifiedId[], string[], UserClients or QualfiedUserClients. The type has some effect on the behavior of the method. (Needed only for Proteus)
749
- * When given a QualifiedId[] or string[] the method will fetch the freshest list of devices for those users (since they are not given by the consumer). As a consequence no ClientMismatch error will trigger and we will ignore missing clients when sending
750
- * When given a QualifiedUserClients or UserClients the method will only send to the clients listed in the userIds. This could lead to ClientMismatch (since the given list of devices might not be the freshest one and new clients could have been created)
751
- * When given a QualifiedId[] or QualifiedUserClients the method will send the message through the federated API endpoint
752
- * When given a string[] or UserClients the method will send the message through the old API endpoint
753
- * @return resolves with the sent message
754
- */
755
740
  async sendProteusMessage(params, genericMessage, content) {
756
741
  var _a;
757
742
  const { userIds, sendAsProtobuf, conversationDomain, nativePush, targetMode, payload, onClientMismatch, onSuccess } = params;
@@ -50,6 +50,7 @@ export declare enum PayloadBundleType {
50
50
  MESSAGE_DELETE = "PayloadBundleType.MESSAGE_DELETE",
51
51
  MESSAGE_EDIT = "PayloadBundleType.MESSAGE_EDIT",
52
52
  MESSAGE_HIDE = "PayloadBundleType.MESSAGE_HIDE",
53
+ MLS_WELCOME_MESSAGE = "PayloadBundleType.MLS_WELCOME",
53
54
  PING = "PayloadBundleType.PING",
54
55
  REACTION = "PayloadBundleType.REACTION",
55
56
  TEAM_CONVERSATION_CREATE = "PayloadBundleType.TEAM_CONVERSATION_CREATE",
@@ -54,6 +54,7 @@ var PayloadBundleType;
54
54
  PayloadBundleType["MESSAGE_DELETE"] = "PayloadBundleType.MESSAGE_DELETE";
55
55
  PayloadBundleType["MESSAGE_EDIT"] = "PayloadBundleType.MESSAGE_EDIT";
56
56
  PayloadBundleType["MESSAGE_HIDE"] = "PayloadBundleType.MESSAGE_HIDE";
57
+ PayloadBundleType["MLS_WELCOME_MESSAGE"] = "PayloadBundleType.MLS_WELCOME";
57
58
  PayloadBundleType["PING"] = "PayloadBundleType.PING";
58
59
  PayloadBundleType["REACTION"] = "PayloadBundleType.REACTION";
59
60
  PayloadBundleType["TEAM_CONVERSATION_CREATE"] = "PayloadBundleType.TEAM_CONVERSATION_CREATE";
@@ -4,7 +4,8 @@ export declare enum DatabaseStores {
4
4
  CLIENTS = "clients",
5
5
  KEYS = "keys",
6
6
  PRE_KEYS = "prekeys",
7
- SESSIONS = "sessions"
7
+ SESSIONS = "sessions",
8
+ GROUP_IDS = "group_ids"
8
9
  }
9
10
  export declare class CryptographyDatabaseRepository {
10
11
  private readonly storeEngine;
@@ -26,6 +26,7 @@ var DatabaseStores;
26
26
  DatabaseStores["KEYS"] = "keys";
27
27
  DatabaseStores["PRE_KEYS"] = "prekeys";
28
28
  DatabaseStores["SESSIONS"] = "sessions";
29
+ DatabaseStores["GROUP_IDS"] = "group_ids";
29
30
  })(DatabaseStores = exports.DatabaseStores || (exports.DatabaseStores = {}));
30
31
  class CryptographyDatabaseRepository {
31
32
  constructor(storeEngine) {
@@ -38,6 +39,7 @@ class CryptographyDatabaseRepository {
38
39
  this.storeEngine.deleteAll(CryptographyDatabaseRepository.STORES.KEYS),
39
40
  this.storeEngine.deleteAll(CryptographyDatabaseRepository.STORES.SESSIONS),
40
41
  this.storeEngine.deleteAll(CryptographyDatabaseRepository.STORES.PRE_KEYS),
42
+ this.storeEngine.deleteAll(CryptographyDatabaseRepository.STORES.GROUP_IDS),
41
43
  ]);
42
44
  }
43
45
  }
@@ -1,6 +1,11 @@
1
1
  import type { BackendEvent } from '@wireapp/api-client/src/event';
2
2
  import type { Notification } from '@wireapp/api-client/src/notification/';
3
3
  import type { CRUDEngine } from '@wireapp/store-engine';
4
+ declare type CompoundGroupIdParams = {
5
+ groupId: string;
6
+ conversationId: string;
7
+ conversationDomain: string;
8
+ };
4
9
  export declare enum DatabaseStores {
5
10
  EVENTS = "events"
6
11
  }
@@ -17,4 +22,8 @@ export declare class NotificationDatabaseRepository {
17
22
  createLastEventDate(eventDate: Date): Promise<Date>;
18
23
  getLastNotificationId(): Promise<string>;
19
24
  updateLastNotificationId(lastNotification: Notification): Promise<string>;
25
+ private generateCompoundGroupIdPrimaryKey;
26
+ addCompoundGroupId(params: CompoundGroupIdParams): Promise<void>;
27
+ getCompoundGroupId(params: Omit<CompoundGroupIdParams, 'groupId'>): Promise<string>;
20
28
  }
29
+ export {};
@@ -30,6 +30,7 @@ var DatabaseKeys;
30
30
  DatabaseKeys["PRIMARY_KEY_LAST_NOTIFICATION"] = "z.storage.StorageKey.NOTIFICATION.LAST_ID";
31
31
  })(DatabaseKeys = exports.DatabaseKeys || (exports.DatabaseKeys = {}));
32
32
  const STORE_AMPLIFY = CryptographyDatabaseRepository_1.CryptographyDatabaseRepository.STORES.AMPLIFY;
33
+ const STORE_GROUPIDS = CryptographyDatabaseRepository_1.CryptographyDatabaseRepository.STORES.GROUP_IDS;
33
34
  class NotificationDatabaseRepository {
34
35
  constructor(storeEngine) {
35
36
  this.storeEngine = storeEngine;
@@ -59,6 +60,15 @@ class NotificationDatabaseRepository {
59
60
  });
60
61
  return lastNotification.id;
61
62
  }
63
+ generateCompoundGroupIdPrimaryKey({ conversationId, conversationDomain, }) {
64
+ return `${conversationId}@${conversationDomain}`;
65
+ }
66
+ async addCompoundGroupId(params) {
67
+ await this.storeEngine.updateOrCreate(STORE_GROUPIDS, this.generateCompoundGroupIdPrimaryKey(params), params.groupId);
68
+ }
69
+ async getCompoundGroupId(params) {
70
+ return this.storeEngine.read(STORE_GROUPIDS, this.generateCompoundGroupIdPrimaryKey(params));
71
+ }
62
72
  }
63
73
  exports.NotificationDatabaseRepository = NotificationDatabaseRepository;
64
74
  //# sourceMappingURL=NotificationDatabaseRepository.js.map
@@ -9,6 +9,9 @@ import { NotificationError } from '../CoreError';
9
9
  import type { CryptographyService } from '../cryptography';
10
10
  import { GenericMessage } from '@wireapp/protocol-messaging';
11
11
  import { AbortHandler } from '@wireapp/api-client/src/tcp';
12
+ import type { CoreCrypto } from '@otak/core-crypto/platforms/web/corecrypto';
13
+ import { QualifiedId } from '@wireapp/api-client/src/user';
14
+ import { Conversation } from '@wireapp/api-client/src/conversation';
12
15
  export declare type HandledEventPayload = {
13
16
  event: Events.BackendEvent;
14
17
  mappedEvent?: PayloadBundle;
@@ -29,13 +32,14 @@ export interface NotificationService {
29
32
  on(event: TOPIC.NOTIFICATION_ERROR, listener: (payload: NotificationError) => void): this;
30
33
  }
31
34
  export declare class NotificationService extends EventEmitter {
35
+ private readonly coreCryptoClientProvider;
32
36
  private readonly apiClient;
33
37
  private readonly backend;
34
38
  private readonly cryptographyService;
35
39
  private readonly database;
36
40
  private readonly logger;
37
41
  static readonly TOPIC: typeof TOPIC;
38
- constructor(apiClient: APIClient, cryptographyService: CryptographyService, storeEngine: CRUDEngine);
42
+ constructor(apiClient: APIClient, cryptographyService: CryptographyService, storeEngine: CRUDEngine, coreCryptoClientProvider: () => CoreCrypto | undefined);
39
43
  private getAllNotifications;
40
44
  /** Should only be called with a completely new client. */
41
45
  initializeNotificationStream(): Promise<string>;
@@ -56,5 +60,20 @@ export declare class NotificationService extends EventEmitter {
56
60
  handleNotification(notification: Notification, source: PayloadBundleSource, dryRun?: boolean): AsyncGenerator<HandledEventPayload>;
57
61
  private cleanupPayloadBundle;
58
62
  private handleEvent;
63
+ /**
64
+ * If there is a groupId in the conversation, we need to store the conversationId => groupId pair
65
+ * in order to find the groupId when decrypting messages
66
+ * This is a bit hacky but since mls messages do not embed the groupId we need to keep a mapping of those
67
+ *
68
+ * @param conversation conversation with group_id
69
+ */
70
+ saveConversationGroupId(conversation: Conversation): Promise<void>;
71
+ /**
72
+ * If there is a matching conversationId => groupId pair in the database,
73
+ * we can find the groupId and return it as a Uint8Array
74
+ *
75
+ * @param conversationQualifiedId
76
+ */
77
+ getUint8ArrayFromConversationGroupId(conversationQualifiedId: QualifiedId): Promise<Uint8Array>;
59
78
  }
60
79
  export {};
@@ -63,13 +63,16 @@ const CoreError_1 = require("../CoreError");
63
63
  const UserMapper_1 = require("../user/UserMapper");
64
64
  const NotificationBackendRepository_1 = require("./NotificationBackendRepository");
65
65
  const NotificationDatabaseRepository_1 = require("./NotificationDatabaseRepository");
66
+ const protocol_messaging_1 = require("@wireapp/protocol-messaging");
67
+ const bazinga64_1 = require("bazinga64");
66
68
  var TOPIC;
67
69
  (function (TOPIC) {
68
70
  TOPIC["NOTIFICATION_ERROR"] = "NotificationService.TOPIC.NOTIFICATION_ERROR";
69
71
  })(TOPIC || (TOPIC = {}));
70
72
  class NotificationService extends events_1.EventEmitter {
71
- constructor(apiClient, cryptographyService, storeEngine) {
73
+ constructor(apiClient, cryptographyService, storeEngine, coreCryptoClientProvider) {
72
74
  super();
75
+ this.coreCryptoClientProvider = coreCryptoClientProvider;
73
76
  this.logger = (0, logdown_1.default)('@wireapp/core/notification/NotificationService', {
74
77
  logger: console,
75
78
  markdown: false,
@@ -206,8 +209,38 @@ class NotificationService extends events_1.EventEmitter {
206
209
  }
207
210
  }
208
211
  async handleEvent(event, source, dryRun = false) {
212
+ var _a;
213
+ const coreCryptoClient = this.coreCryptoClientProvider();
209
214
  switch (event.type) {
210
- // Encrypted events
215
+ case Events.CONVERSATION_EVENT.MLS_WELCOME_MESSAGE:
216
+ if (!coreCryptoClient) {
217
+ // TODO throw proper error
218
+ throw new Error('TODO');
219
+ }
220
+ const data = bazinga64_1.Decoder.fromBase64(event.data).asBytes;
221
+ // We extract the groupId from the welcome message and let coreCrypto store this group
222
+ const newGroupId = await coreCryptoClient.processWelcomeMessage(data);
223
+ const groupIdStr = bazinga64_1.Encoder.toBase64(newGroupId).asString;
224
+ // The groupId can then be sent back to the consumer
225
+ return {
226
+ event,
227
+ mappedEvent: ConversationMapper_1.ConversationMapper.mapConversationEvent(Object.assign(Object.assign({}, event), { data: groupIdStr }), source),
228
+ };
229
+ case Events.CONVERSATION_EVENT.MLS_MESSAGE_ADD:
230
+ if (!coreCryptoClient) {
231
+ // TODO throw proper error
232
+ throw new Error('TODO');
233
+ }
234
+ const encryptedData = bazinga64_1.Decoder.fromBase64(event.data).asBytes;
235
+ const groupId = await this.getUint8ArrayFromConversationGroupId(event.qualified_conversation || { id: event.conversation, domain: '' });
236
+ const rawData = await coreCryptoClient.decryptMessage(groupId, encryptedData);
237
+ if (!rawData) {
238
+ // TODO
239
+ throw new Error('empty message');
240
+ }
241
+ const decryptedData = protocol_messaging_1.GenericMessage.decode(rawData);
242
+ return { event, decryptedData };
243
+ // Encrypted Proteus events
211
244
  case Events.CONVERSATION_EVENT.OTR_MESSAGE_ADD: {
212
245
  if (dryRun) {
213
246
  // In case of a dry run, we do not want to decrypt messages
@@ -228,6 +261,13 @@ class NotificationService extends events_1.EventEmitter {
228
261
  }
229
262
  // Meta events
230
263
  case Events.CONVERSATION_EVENT.MEMBER_JOIN:
264
+ // As of today (07/07/2022) the backend sends `WELCOME` message to the user's own conversation (not the actual conversation that the welcome should be part of)
265
+ // So in order to map conversation Ids and groupId together, we need to first fetch the conversation and get the groupId linked to it.
266
+ const conversation = await this.apiClient.api.conversation.getConversation((_a = event.qualified_conversation) !== null && _a !== void 0 ? _a : { id: event.conversation, domain: '' });
267
+ if (!conversation) {
268
+ throw new Error('no conv');
269
+ }
270
+ await this.saveConversationGroupId(conversation);
231
271
  case Events.CONVERSATION_EVENT.MESSAGE_TIMER_UPDATE:
232
272
  case Events.CONVERSATION_EVENT.RENAME:
233
273
  case Events.CONVERSATION_EVENT.TYPING: {
@@ -245,6 +285,36 @@ class NotificationService extends events_1.EventEmitter {
245
285
  }
246
286
  return { event };
247
287
  }
288
+ /**
289
+ * If there is a groupId in the conversation, we need to store the conversationId => groupId pair
290
+ * in order to find the groupId when decrypting messages
291
+ * This is a bit hacky but since mls messages do not embed the groupId we need to keep a mapping of those
292
+ *
293
+ * @param conversation conversation with group_id
294
+ */
295
+ async saveConversationGroupId(conversation) {
296
+ if (conversation.group_id) {
297
+ const { group_id: groupId, qualified_id: { id: conversationId, domain: conversationDomain }, } = conversation;
298
+ await this.database.addCompoundGroupId({ conversationDomain, conversationId, groupId });
299
+ }
300
+ }
301
+ /**
302
+ * If there is a matching conversationId => groupId pair in the database,
303
+ * we can find the groupId and return it as a Uint8Array
304
+ *
305
+ * @param conversationQualifiedId
306
+ */
307
+ async getUint8ArrayFromConversationGroupId(conversationQualifiedId) {
308
+ const { id: conversationId, domain: conversationDomain } = conversationQualifiedId;
309
+ const groupId = await this.database.getCompoundGroupId({
310
+ conversationId,
311
+ conversationDomain,
312
+ });
313
+ if (!groupId) {
314
+ throw new Error(`Could not find a group_id for conversation ${conversationId}@${conversationDomain}`);
315
+ }
316
+ return bazinga64_1.Decoder.fromBase64(groupId).asBytes;
317
+ }
248
318
  }
249
319
  exports.NotificationService = NotificationService;
250
320
  NotificationService.TOPIC = TOPIC;