@wireapp/core 38.15.2 → 39.0.1

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 (47) hide show
  1. package/lib/Account.d.ts.map +1 -1
  2. package/lib/Account.js +10 -4
  3. package/lib/broadcast/BroadcastService.d.ts +2 -2
  4. package/lib/broadcast/BroadcastService.d.ts.map +1 -1
  5. package/lib/broadcast/BroadcastService.js +5 -12
  6. package/lib/client/ClientDatabaseRepository.d.ts +5 -5
  7. package/lib/client/ClientDatabaseRepository.d.ts.map +1 -1
  8. package/lib/client/ClientDatabaseRepository.js +9 -16
  9. package/lib/client/ClientService.d.ts.map +1 -1
  10. package/lib/client/ClientService.js +2 -2
  11. package/lib/connection/ConnectionService.d.ts +2 -2
  12. package/lib/connection/ConnectionService.d.ts.map +1 -1
  13. package/lib/connection/ConnectionService.js +2 -2
  14. package/lib/conversation/ConversationService/ConversationService.d.ts +1 -1
  15. package/lib/conversation/ConversationService/ConversationService.d.ts.map +1 -1
  16. package/lib/conversation/ConversationService/ConversationService.test.js +1 -1
  17. package/lib/conversation/ConversationService/ConversationService.types.d.ts +2 -6
  18. package/lib/conversation/ConversationService/ConversationService.types.d.ts.map +1 -1
  19. package/lib/conversation/ConversationService/Utility/getConversationQualifiedMembers.d.ts +1 -1
  20. package/lib/conversation/ConversationService/Utility/getConversationQualifiedMembers.d.ts.map +1 -1
  21. package/lib/conversation/message/MessageService.d.ts +5 -31
  22. package/lib/conversation/message/MessageService.d.ts.map +1 -1
  23. package/lib/conversation/message/MessageService.js +9 -151
  24. package/lib/conversation/message/MessageService.test.js +107 -156
  25. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.d.ts +7 -11
  26. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.d.ts.map +1 -1
  27. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.js +16 -35
  28. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.mocks.d.ts +1 -1
  29. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.mocks.d.ts.map +1 -1
  30. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.mocks.js +1 -2
  31. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.test.js +55 -94
  32. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.types.d.ts +5 -8
  33. package/lib/messagingProtocols/proteus/ProteusService/ProteusService.types.d.ts.map +1 -1
  34. package/lib/messagingProtocols/proteus/Utility/Recipients.d.ts +4 -10
  35. package/lib/messagingProtocols/proteus/Utility/Recipients.d.ts.map +1 -1
  36. package/lib/messagingProtocols/proteus/Utility/Recipients.js +2 -13
  37. package/lib/messagingProtocols/proteus/Utility/SessionHandler/SessionHandler.d.ts +8 -11
  38. package/lib/messagingProtocols/proteus/Utility/SessionHandler/SessionHandler.d.ts.map +1 -1
  39. package/lib/messagingProtocols/proteus/Utility/SessionHandler/SessionHandler.js +54 -75
  40. package/lib/messagingProtocols/proteus/Utility/SessionHandler/SessionHandler.test.js +26 -33
  41. package/lib/messagingProtocols/proteus/Utility/getGenericMessageParams.d.ts +5 -14
  42. package/lib/messagingProtocols/proteus/Utility/getGenericMessageParams.d.ts.map +1 -1
  43. package/lib/messagingProtocols/proteus/Utility/getGenericMessageParams.js +3 -35
  44. package/lib/user/UserService.d.ts +1 -16
  45. package/lib/user/UserService.d.ts.map +1 -1
  46. package/lib/user/UserService.js +1 -43
  47. package/package.json +3 -3
@@ -24,64 +24,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
24
24
  exports.MessageService = void 0;
25
25
  const StringUtil_1 = require("@wireapp/commons/lib/util/StringUtil");
26
26
  const otr_1 = require("@wireapp/protocol-messaging/web/otr");
27
- const bazinga64_1 = require("bazinga64");
28
27
  const http_status_codes_1 = require("http-status-codes");
29
28
  const long_1 = __importDefault(require("long"));
30
- const protocol_messaging_1 = require("@wireapp/protocol-messaging");
31
- const MessageBuilder_1 = require("./MessageBuilder");
32
- const RecipientsHelper_1 = require("./RecipientsHelper");
33
29
  const UserClientsUtil_1 = require("./UserClientsUtil");
34
- const __1 = require("..");
35
- const AssetCryptography_1 = require("../../cryptography/AssetCryptography");
36
30
  const util_1 = require("../../util");
37
31
  class MessageService {
38
32
  constructor(apiClient, proteusService) {
39
33
  this.apiClient = apiClient;
40
34
  this.proteusService = proteusService;
41
35
  }
42
- /**
43
- * Sends a message to a non-federated backend.
44
- *
45
- * @param sendingClientId The clientId of the current user
46
- * @param recipients The list of recipients to send the message to
47
- * @param plainText The plainText data to send
48
- * @param options.conversationId? the conversation to send the message to. Will broadcast if not set
49
- * @param options.reportMissing? trigger a mismatch error when there are missing recipients in the payload
50
- * @param options.sendAsProtobuf?
51
- * @param options.onClientMismatch? Called when a mismatch happens on the server
52
- * @return the ClientMismatch status returned by the backend
53
- */
54
- async sendMessage(sendingClientId, recipients, plainText, options = {}) {
55
- let plainTextPayload = plainText;
56
- let cipherText;
57
- if (this.shouldSendAsExternal(plainText, recipients)) {
58
- const externalPayload = await this.generateExternalPayload(plainText);
59
- plainTextPayload = externalPayload.text;
60
- cipherText = externalPayload.cipherText;
61
- }
62
- const { payloads: encryptedPayload, unknowns } = await this.proteusService.encrypt(plainTextPayload, recipients);
63
- const send = async (payload) => {
64
- const result = options.sendAsProtobuf
65
- ? await this.sendOTRProtobufMessage(sendingClientId, payload, Object.assign(Object.assign({}, options), { assetData: cipherText }))
66
- : await this.sendOTRMessage(sendingClientId, payload, Object.assign(Object.assign({}, options), { assetData: cipherText }));
67
- return unknowns ? Object.assign(Object.assign({}, result), { deleted: Object.assign(Object.assign({}, result.deleted), unknowns) }) : result;
68
- };
69
- try {
70
- return await send(encryptedPayload);
71
- }
72
- catch (error) {
73
- if (!this.isClientMismatchError(error)) {
74
- throw error;
75
- }
76
- const mismatch = error.response.data;
77
- const shouldStopSending = options.onClientMismatch && (await options.onClientMismatch(mismatch)) === false;
78
- if (shouldStopSending) {
79
- return Object.assign(Object.assign({}, mismatch), { canceled: true });
80
- }
81
- const reEncryptedMessage = await this.reencryptAfterMismatch(mismatch, encryptedPayload, plainText);
82
- return send(reEncryptedMessage);
83
- }
84
- }
85
36
  /**
86
37
  * Sends a message to a federated backend.
87
38
  *
@@ -94,10 +45,10 @@ class MessageService {
94
45
  * @param options.onClientMismatch? Called when a mismatch happens on the server
95
46
  * @return the MessageSendingStatus returned by the backend
96
47
  */
97
- async sendFederatedMessage(sendingClientId, recipients, plainText, options) {
98
- const { payloads: encryptedPayload, unknowns: unknows } = await this.proteusService.encryptQualified(plainText, recipients);
48
+ async sendMessage(sendingClientId, recipients, plainText, options = {}) {
49
+ const { payloads: encryptedPayload, unknowns: unknows } = await this.proteusService.encrypt(plainText, recipients);
99
50
  const send = async (payload) => {
100
- const result = await this.sendFederatedOtrMessage(sendingClientId, payload, options);
51
+ const result = await this.sendOtrMessage(sendingClientId, payload, options);
101
52
  return unknows ? Object.assign(Object.assign({}, result), { deleted: Object.assign(Object.assign({}, result.deleted), unknows) }) : result;
102
53
  };
103
54
  try {
@@ -112,11 +63,11 @@ class MessageService {
112
63
  if (shouldStopSending) {
113
64
  return Object.assign(Object.assign({}, mismatch), { canceled: true });
114
65
  }
115
- const reEncryptedPayload = await this.reencryptAfterFederatedMismatch(mismatch, encryptedPayload, plainText);
66
+ const reEncryptedPayload = await this.reencryptAfterMismatch(mismatch, encryptedPayload, plainText);
116
67
  return send(reEncryptedPayload);
117
68
  }
118
69
  }
119
- async sendFederatedOtrMessage(sendingClientId, recipients, options) {
70
+ async sendOtrMessage(sendingClientId, recipients, options) {
120
71
  const qualifiedUserEntries = Object.entries(recipients).map(([domain, otrRecipients]) => {
121
72
  const userEntries = Object.entries(otrRecipients).map(([userId, otrClientMap]) => {
122
73
  const clientEntries = Object.entries(otrClientMap).map(([clientId, payload]) => {
@@ -158,69 +109,15 @@ class MessageService {
158
109
  protoMessage.ignoreAll = true;
159
110
  }
160
111
  if (!options.conversationId) {
161
- return this.apiClient.api.broadcast.postBroadcastFederatedMessage(sendingClientId, protoMessage);
112
+ return this.apiClient.api.broadcast.postBroadcastMessage(sendingClientId, protoMessage);
162
113
  }
163
114
  const { id, domain } = options.conversationId;
164
- return this.apiClient.api.conversation.postOTRMessageV2(id, domain, protoMessage);
165
- }
166
- async sendOTRMessage(sendingClientId, recipients, options) {
167
- const message = {
168
- data: options.assetData ? bazinga64_1.Encoder.toBase64(options.assetData).asString : undefined,
169
- recipients: (0, RecipientsHelper_1.recipientsToBase64)(recipients),
170
- sender: sendingClientId,
171
- native_push: options.nativePush,
172
- };
173
- let ignoreMissing;
174
- if ((0, util_1.isStringArray)(options.reportMissing)) {
175
- message.report_missing = options.reportMissing;
176
- }
177
- else {
178
- // By default we want ignore missing to be false (and have mismatch errors in case some clients are missing)
179
- ignoreMissing = typeof options.reportMissing === 'boolean' ? !options.reportMissing : false;
180
- }
181
- return !options.conversationId
182
- ? this.apiClient.api.broadcast.postBroadcastMessage(sendingClientId, message, ignoreMissing)
183
- : this.apiClient.api.conversation.postOTRMessage(sendingClientId, options.conversationId.id, message, ignoreMissing);
184
- }
185
- async generateExternalPayload(plainText) {
186
- const asset = await (0, AssetCryptography_1.encryptAsset)({ plainText });
187
- const { cipherText, keyBytes, sha256 } = asset;
188
- const externalMessage = {
189
- otrKey: new Uint8Array(keyBytes),
190
- sha256: new Uint8Array(sha256),
191
- };
192
- const genericMessage = protocol_messaging_1.GenericMessage.create({
193
- [__1.GenericMessageType.EXTERNAL]: externalMessage,
194
- messageId: (0, MessageBuilder_1.createId)(),
195
- });
196
- return { text: protocol_messaging_1.GenericMessage.encode(genericMessage).finish(), cipherText };
197
- }
198
- shouldSendAsExternal(plainText, preKeyBundles) {
199
- const EXTERNAL_MESSAGE_THRESHOLD_BYTES = 200 * 1024;
200
- let clientCount = 0;
201
- for (const user in preKeyBundles) {
202
- clientCount += Object.keys(preKeyBundles[user]).length;
203
- }
204
- const messageInBytes = new Uint8Array(plainText).length;
205
- const estimatedPayloadInBytes = clientCount * messageInBytes;
206
- return estimatedPayloadInBytes > EXTERNAL_MESSAGE_THRESHOLD_BYTES;
115
+ return this.apiClient.api.conversation.postOTRMessage(id, domain, protoMessage);
207
116
  }
208
117
  isClientMismatchError(error) {
209
118
  var _a;
210
119
  return ((_a = error.response) === null || _a === void 0 ? void 0 : _a.status) === http_status_codes_1.StatusCodes.PRECONDITION_FAILED;
211
120
  }
212
- async reencryptAfterMismatch(mismatch, recipients, plainText) {
213
- const deleted = (0, UserClientsUtil_1.flattenUserClients)(mismatch.deleted);
214
- // remove deleted clients to the recipients
215
- deleted.forEach(({ userId, data }) => data.forEach(clientId => delete recipients[userId.id][clientId]));
216
- if (Object.keys(mismatch.missing).length) {
217
- const { payloads } = await this.proteusService.encrypt(plainText, mismatch.missing);
218
- const reEncryptedPayloads = (0, UserClientsUtil_1.flattenUserClients)(payloads);
219
- // add missing clients to the recipients
220
- reEncryptedPayloads.forEach(({ data, userId }) => (recipients[userId.id] = Object.assign(Object.assign({}, recipients[userId.id]), data)));
221
- }
222
- return recipients;
223
- }
224
121
  /**
225
122
  * Will re-encrypt a message when there were some missing clients in the initial send (typically when the server replies with a client mismatch error)
226
123
  *
@@ -229,55 +126,16 @@ class MessageService {
229
126
  * @param {Uint8Array} plainText The text that should be encrypted for the missing clients
230
127
  * @return resolves with a new message payload that can be sent
231
128
  */
232
- async reencryptAfterFederatedMismatch(mismatch, recipients, plainText) {
129
+ async reencryptAfterMismatch(mismatch, recipients, plainText) {
233
130
  const deleted = (0, UserClientsUtil_1.flattenQualifiedUserClients)(mismatch.deleted);
234
131
  // remove deleted clients to the recipients
235
132
  deleted.forEach(({ userId, data }) => data.forEach(clientId => delete recipients[userId.domain][userId.id][clientId]));
236
133
  if (Object.keys(mismatch.missing).length) {
237
- const { payloads } = await this.proteusService.encryptQualified(plainText, mismatch.missing);
134
+ const { payloads } = await this.proteusService.encrypt(plainText, mismatch.missing);
238
135
  const reEncryptedPayloads = (0, UserClientsUtil_1.flattenQualifiedUserClients)(payloads);
239
136
  reEncryptedPayloads.forEach(({ data, userId }) => (recipients[userId.domain][userId.id] = Object.assign(Object.assign({}, recipients[userId.domain][userId.id]), data)));
240
137
  }
241
138
  return recipients;
242
139
  }
243
- async sendOTRProtobufMessage(sendingClientId, recipients, options) {
244
- const userEntries = Object.entries(recipients).map(([userId, otrClientMap]) => {
245
- const clients = Object.entries(otrClientMap).map(([clientId, payload]) => {
246
- return {
247
- client: {
248
- client: long_1.default.fromString(clientId, 16),
249
- },
250
- text: payload,
251
- };
252
- });
253
- return {
254
- clients,
255
- user: {
256
- uuid: (0, StringUtil_1.uuidToBytes)(userId),
257
- },
258
- };
259
- });
260
- const protoMessage = otr_1.proteus.NewOtrMessage.create({
261
- recipients: userEntries,
262
- sender: {
263
- client: long_1.default.fromString(sendingClientId, 16),
264
- },
265
- });
266
- let ignoreMissing;
267
- if ((0, util_1.isStringArray)(options.reportMissing)) {
268
- const encoder = new TextEncoder();
269
- protoMessage.reportMissing = options.reportMissing.map(userId => ({ uuid: encoder.encode(userId) }));
270
- }
271
- else {
272
- // By default we want ignore missing to be false (and have mismatch errors in case some clients are missing)
273
- ignoreMissing = typeof options.reportMissing === 'boolean' ? !options.reportMissing : false;
274
- }
275
- if (options.assetData) {
276
- protoMessage.blob = options.assetData;
277
- }
278
- return !options.conversationId
279
- ? this.apiClient.api.broadcast.postBroadcastProtobufMessage(sendingClientId, protoMessage, ignoreMissing)
280
- : this.apiClient.api.conversation.postOTRProtobufMessage(sendingClientId, options.conversationId.id, protoMessage, ignoreMissing);
281
- }
282
140
  }
283
141
  exports.MessageService = MessageService;
@@ -51,12 +51,14 @@ function generateQualifiedRecipients(users) {
51
51
  return payload;
52
52
  }
53
53
  function generateRecipients(users) {
54
- return users.reduce((acc, { id, clients }) => {
55
- acc[id] = clients;
54
+ return users.reduce((acc, { id, domain, clients }) => {
55
+ const domainUsers = acc[domain] || {};
56
+ domainUsers[id] = clients;
57
+ acc[domain] = domainUsers;
56
58
  return acc;
57
59
  }, {});
58
60
  }
59
- function fakeEncryptQualified(_, recipients) {
61
+ function fakeEncrypt(_, recipients) {
60
62
  const encryptedPayload = Object.entries(recipients).reduce((acc, [domain, users]) => {
61
63
  acc[domain] = Object.entries(users).reduce((userClients, [userId, clients]) => {
62
64
  userClients[userId] = clients.reduce((payloads, client) => {
@@ -69,42 +71,74 @@ function fakeEncryptQualified(_, recipients) {
69
71
  }, {});
70
72
  return Promise.resolve({ payloads: encryptedPayload });
71
73
  }
72
- function fakeEncrypt(_, recipients) {
73
- const encryptedPayload = Object.entries(recipients).reduce((userClients, [userId, clients]) => {
74
- userClients[userId] || (userClients[userId] = clients.reduce((acc, clientId) => {
75
- acc[clientId] = new Uint8Array();
76
- return acc;
77
- }, {}));
78
- return userClients;
79
- }, {});
80
- return Promise.resolve({ payloads: encryptedPayload });
81
- }
82
74
  const buildMessageService = async () => {
83
75
  const apiClient = new api_client_1.APIClient();
84
- const [proteusService] = await (0, ProteusService_mocks_1.buildProteusService)(true);
76
+ const [proteusService] = await (0, ProteusService_mocks_1.buildProteusService)();
85
77
  const messageService = new MessageService_1.MessageService(apiClient, proteusService);
86
- jest.spyOn(proteusService, 'encryptQualified').mockImplementation(fakeEncryptQualified);
87
78
  jest.spyOn(proteusService, 'encrypt').mockImplementation(fakeEncrypt);
88
79
  return [messageService, { apiClient, proteusService }];
89
80
  };
90
81
  describe('MessageService', () => {
91
- describe('sendFederatedMessage', () => {
82
+ describe('sendMessage', () => {
83
+ const generateUsers = (userCount, clientsPerUser) => {
84
+ return Array.from(Array(userCount)).map((_, i) => ({
85
+ id: `user${i}`,
86
+ domain: `${i}.domain`,
87
+ clients: Array.from(Array(clientsPerUser)).map((_, j) => `client${i}${j}`),
88
+ }));
89
+ };
90
+ const clientId = 'sendingClient';
91
+ const conversationId = { id: 'conv1', domain: '' };
92
+ const createMessage = (content) => {
93
+ const customTextMessage = protocol_messaging_1.GenericMessage.create({
94
+ messageId: (0, PayloadHelper_1.getUUID)(),
95
+ text: protocol_messaging_1.Text.create({ content }),
96
+ });
97
+ return protocol_messaging_1.GenericMessage.encode(customTextMessage).finish();
98
+ };
92
99
  it('sends a message and forwards backend response', async () => {
93
100
  const [messageService, { apiClient }] = await buildMessageService();
94
- jest.spyOn(apiClient.api.conversation, 'postOTRMessageV2').mockResolvedValue(baseMessageSendingStatus);
101
+ jest.spyOn(apiClient.api.conversation, 'postOTRMessage').mockResolvedValue(baseMessageSendingStatus);
95
102
  const recipients = generateQualifiedRecipients([user1, user2]);
96
- const result = await messageService.sendFederatedMessage('senderclientid', recipients, new Uint8Array(), {
97
- conversationId: { id: 'convid', domain: '' },
103
+ const result = await messageService.sendMessage('senderclientid', recipients, new Uint8Array(), {
104
+ conversationId: { id: 'convid', domain: 'domain' },
98
105
  });
99
- expect(apiClient.api.conversation.postOTRMessageV2).toHaveBeenCalled();
106
+ expect(apiClient.api.conversation.postOTRMessage).toHaveBeenCalled();
100
107
  expect(result).toEqual(baseMessageSendingStatus);
101
108
  });
109
+ it('should send regular to conversation', async () => {
110
+ const [messageService, { apiClient }] = await buildMessageService();
111
+ const message = 'Lorem ipsum dolor sit amet';
112
+ jest
113
+ .spyOn(apiClient.api.conversation, 'postOTRMessage')
114
+ .mockReturnValue(Promise.resolve({}));
115
+ await messageService.sendMessage(clientId, generateRecipients(generateUsers(3, 3)), createMessage(message), {
116
+ conversationId,
117
+ });
118
+ expect(apiClient.api.conversation.postOTRMessage).toHaveBeenCalledWith(conversationId.id, conversationId.domain, expect.any(Object));
119
+ });
120
+ it('should broadcast regular message if no conversationId is given', async () => {
121
+ const [messageService, { apiClient }] = await buildMessageService();
122
+ const message = 'Lorem ipsum dolor sit amet';
123
+ jest
124
+ .spyOn(apiClient.api.broadcast, 'postBroadcastMessage')
125
+ .mockReturnValue(Promise.resolve({}));
126
+ await messageService.sendMessage(clientId, generateRecipients(generateUsers(3, 3)), createMessage(message));
127
+ expect(apiClient.api.broadcast.postBroadcastMessage).toHaveBeenCalledWith(clientId, expect.any(Object));
128
+ });
102
129
  describe('client mismatch', () => {
130
+ const baseClientMismatch = {
131
+ deleted: {},
132
+ missing: {},
133
+ redundant: {},
134
+ failed_to_send: {},
135
+ time: new Date().toISOString(),
136
+ };
103
137
  it('handles client mismatch internally if no onClientMismatch is given', async () => {
104
138
  const [messageService, { apiClient }] = await buildMessageService();
105
139
  let spyCounter = 0;
106
- const clientMismatch = Object.assign(Object.assign({}, baseMessageSendingStatus), { deleted: { [user1.domain]: { [user1.id]: [user1.clients[0]] } }, missing: { '2.wire.test': { [user2.id]: ['client22'] } } });
107
- jest.spyOn(apiClient.api.conversation, 'postOTRMessageV2').mockImplementation(() => {
140
+ const clientMismatch = Object.assign(Object.assign({}, baseClientMismatch), { deleted: { [user1.domain]: { [user1.id]: [user1.clients[0]] } }, missing: { [user2.domain]: { [user2.id]: ['client22'] } } });
141
+ jest.spyOn(apiClient.api.conversation, 'postOTRMessage').mockImplementation(() => {
108
142
  spyCounter++;
109
143
  if (spyCounter === 1) {
110
144
  const error = new Error();
@@ -114,22 +148,22 @@ describe('MessageService', () => {
114
148
  };
115
149
  return Promise.reject(error);
116
150
  }
117
- return Promise.resolve(baseMessageSendingStatus);
151
+ return Promise.resolve(baseClientMismatch);
118
152
  });
119
- jest.spyOn(apiClient.api.user, 'postQualifiedMultiPreKeyBundles').mockReturnValue(Promise.resolve({}));
120
- const recipients = generateQualifiedRecipients([user1, user2]);
121
- await messageService.sendFederatedMessage('senderclientid', recipients, new Uint8Array(), {
153
+ jest.spyOn(apiClient.api.user, 'postMultiPreKeyBundles').mockReturnValue(Promise.resolve({}));
154
+ const recipients = generateRecipients([user1, user2]);
155
+ await messageService.sendMessage('senderclientid', recipients, new Uint8Array(), {
122
156
  reportMissing: true,
123
157
  conversationId: { id: 'convid', domain: '' },
124
158
  });
125
- expect(apiClient.api.conversation.postOTRMessageV2).toHaveBeenCalledTimes(2);
159
+ expect(apiClient.api.conversation.postOTRMessage).toHaveBeenCalledTimes(2);
126
160
  });
127
161
  it('continues message sending if onClientMismatch returns true', async () => {
128
162
  const [messageService, { apiClient }] = await buildMessageService();
129
- const onClientMismatch = jest.fn().mockReturnValue(true);
130
- const clientMismatch = Object.assign(Object.assign({}, baseMessageSendingStatus), { missing: { '2.wire.test': { [user2.id]: ['client22'] } } });
163
+ const onClientMismatch = jest.fn().mockReturnValue(Promise.resolve(true));
164
+ const clientMismatch = Object.assign(Object.assign({}, baseClientMismatch), { missing: { [user2.domain]: { [user2.id]: ['client22'] } } });
131
165
  let spyCounter = 0;
132
- jest.spyOn(apiClient.api.conversation, 'postOTRMessageV2').mockImplementation(() => {
166
+ jest.spyOn(apiClient.api.conversation, 'postOTRMessage').mockImplementation(() => {
133
167
  spyCounter++;
134
168
  if (spyCounter === 1) {
135
169
  const error = new Error();
@@ -139,44 +173,23 @@ describe('MessageService', () => {
139
173
  };
140
174
  return Promise.reject(error);
141
175
  }
142
- return Promise.resolve(baseMessageSendingStatus);
176
+ return Promise.resolve(baseClientMismatch);
143
177
  });
144
- jest.spyOn(apiClient.api.user, 'postQualifiedMultiPreKeyBundles').mockReturnValue(Promise.resolve({}));
145
- const recipients = generateQualifiedRecipients([user1, user2]);
146
- await messageService.sendFederatedMessage('senderclientid', recipients, new Uint8Array(), {
178
+ jest.spyOn(apiClient.api.user, 'postMultiPreKeyBundles').mockReturnValue(Promise.resolve({}));
179
+ const recipients = generateRecipients([user1, user2]);
180
+ await messageService.sendMessage('senderclientid', recipients, new Uint8Array(), {
147
181
  reportMissing: true,
148
182
  onClientMismatch,
149
183
  conversationId: { id: 'convid', domain: '' },
150
184
  });
151
- expect(apiClient.api.conversation.postOTRMessageV2).toHaveBeenCalledTimes(2);
185
+ expect(apiClient.api.conversation.postOTRMessage).toHaveBeenCalledTimes(2);
152
186
  expect(onClientMismatch).toHaveBeenCalledWith(clientMismatch);
153
187
  });
154
- it('warns the consumer if they try to send a message to a deleted client', async () => {
155
- const [messageService, { apiClient, proteusService }] = await buildMessageService();
156
- const onClientMismatch = jest.fn().mockReturnValue(true);
157
- const recipients = generateQualifiedRecipients([user1, user2]);
158
- const unknowns = {
159
- [user1.domain]: {
160
- [user1.id]: [user1.clients[0]],
161
- },
162
- };
163
- jest.spyOn(proteusService, 'encryptQualified').mockResolvedValue({
164
- payloads: {},
165
- unknowns,
166
- });
167
- jest.spyOn(apiClient.api.conversation, 'postOTRMessageV2').mockResolvedValue(baseMessageSendingStatus);
168
- const result = await messageService.sendFederatedMessage('senderclientid', recipients, new Uint8Array(), {
169
- reportMissing: true,
170
- onClientMismatch,
171
- conversationId: { id: 'convid', domain: '' },
172
- });
173
- expect(result.deleted).toEqual(unknowns);
174
- });
175
188
  it('stops message sending if onClientMismatch returns false', async () => {
176
189
  const [messageService, { apiClient }] = await buildMessageService();
177
- const onClientMismatch = jest.fn().mockReturnValue(false);
178
- const clientMismatch = Object.assign(Object.assign({}, baseMessageSendingStatus), { missing: { '2.wire.test': { [user2.id]: ['client22'] } } });
179
- jest.spyOn(apiClient.api.conversation, 'postOTRMessageV2').mockImplementation(() => {
190
+ const onClientMismatch = jest.fn().mockReturnValue(Promise.resolve(false));
191
+ const clientMismatch = Object.assign(Object.assign({}, baseMessageSendingStatus), { missing: { [user2.id]: ['client22'] } });
192
+ jest.spyOn(apiClient.api.conversation, 'postOTRMessage').mockImplementation(() => {
180
193
  const error = new Error();
181
194
  error.response = {
182
195
  status: http_status_codes_1.StatusCodes.PRECONDITION_FAILED,
@@ -184,105 +197,22 @@ describe('MessageService', () => {
184
197
  };
185
198
  return Promise.reject(error);
186
199
  });
187
- jest.spyOn(apiClient.api.user, 'postQualifiedMultiPreKeyBundles').mockReturnValue(Promise.resolve({}));
188
- const recipients = generateQualifiedRecipients([user1, user2]);
189
- await messageService.sendFederatedMessage('senderclientid', recipients, new Uint8Array(), {
200
+ jest.spyOn(apiClient.api.user, 'postMultiPreKeyBundles').mockReturnValue(Promise.resolve({}));
201
+ const recipients = generateRecipients([user1, user2]);
202
+ await messageService.sendMessage('senderclientid', recipients, new Uint8Array(), {
190
203
  reportMissing: true,
191
204
  onClientMismatch,
192
205
  conversationId: { id: 'convid', domain: '' },
193
206
  });
194
- expect(apiClient.api.conversation.postOTRMessageV2).toHaveBeenCalledTimes(1);
207
+ expect(apiClient.api.conversation.postOTRMessage).toHaveBeenCalledTimes(1);
195
208
  expect(onClientMismatch).toHaveBeenCalledWith(clientMismatch);
196
209
  });
197
210
  });
198
- });
199
- describe('sendMessage', () => {
200
- const generateUsers = (userCount, clientsPerUser) => {
201
- return Array.from(Array(userCount)).map((_, i) => ({
202
- id: `user${i}`,
203
- domain: `${i}.domain`,
204
- clients: Array.from(Array(clientsPerUser)).map((_, j) => `client${i}${j}`),
205
- }));
206
- };
207
- const clientId = 'sendingClient';
208
- const conversationId = { id: 'conv1', domain: '' };
209
- const createMessage = (content) => {
210
- const customTextMessage = protocol_messaging_1.GenericMessage.create({
211
- messageId: (0, PayloadHelper_1.getUUID)(),
212
- text: protocol_messaging_1.Text.create({ content }),
213
- });
214
- return protocol_messaging_1.GenericMessage.encode(customTextMessage).finish();
215
- };
216
- it('should send regular to conversation', async () => {
217
- const [messageService, { apiClient }] = await buildMessageService();
218
- const message = 'Lorem ipsum dolor sit amet';
219
- jest.spyOn(apiClient.api.conversation, 'postOTRMessage').mockReturnValue(Promise.resolve({}));
220
- await messageService.sendMessage(clientId, generateRecipients(generateUsers(3, 3)), createMessage(message), {
221
- conversationId,
222
- });
223
- expect(apiClient.api.conversation.postOTRMessage).toHaveBeenCalledWith(clientId, conversationId.id, expect.any(Object), false);
224
- });
225
- it('should send protobuf message to conversation', async () => {
226
- const [messageService, { apiClient }] = await buildMessageService();
227
- const message = 'Lorem ipsum dolor sit amet';
228
- jest
229
- .spyOn(apiClient.api.conversation, 'postOTRProtobufMessage')
230
- .mockReturnValue(Promise.resolve({}));
231
- await messageService.sendMessage(clientId, generateRecipients(generateUsers(3, 3)), createMessage(message), {
232
- conversationId,
233
- sendAsProtobuf: true,
234
- });
235
- expect(apiClient.api.conversation.postOTRProtobufMessage).toHaveBeenCalledWith(clientId, conversationId.id, expect.any(Object), false);
236
- });
237
- it('should broadcast regular message if no conversationId is given', async () => {
238
- const [messageService, { apiClient }] = await buildMessageService();
239
- const message = 'Lorem ipsum dolor sit amet';
240
- jest
241
- .spyOn(apiClient.api.broadcast, 'postBroadcastMessage')
242
- .mockReturnValue(Promise.resolve({}));
243
- await messageService.sendMessage(clientId, generateRecipients(generateUsers(3, 3)), createMessage(message));
244
- expect(apiClient.api.broadcast.postBroadcastMessage).toHaveBeenCalledWith(clientId, expect.any(Object), false);
245
- });
246
- it('should broadcast protobuf message if no conversationId is given', async () => {
247
- const [messageService, { apiClient }] = await buildMessageService();
248
- const message = 'Lorem ipsum dolor sit amet';
249
- jest
250
- .spyOn(apiClient.api.broadcast, 'postBroadcastProtobufMessage')
251
- .mockReturnValue(Promise.resolve({}));
252
- await messageService.sendMessage(clientId, generateRecipients(generateUsers(3, 3)), createMessage(message), {
253
- sendAsProtobuf: true,
254
- });
255
- expect(apiClient.api.broadcast.postBroadcastProtobufMessage).toHaveBeenCalledWith(clientId, expect.any(Object), false);
256
- });
257
- it('should not send as external if payload small', async () => {
258
- const [messageService, { apiClient }] = await buildMessageService();
259
- const message = 'Lorem ipsum dolor sit amet';
260
- jest.spyOn(apiClient.api.conversation, 'postOTRMessage').mockReturnValue(Promise.resolve({}));
261
- await messageService.sendMessage(clientId, generateRecipients(generateUsers(3, 3)), createMessage(message), {
262
- conversationId,
263
- });
264
- expect(apiClient.api.conversation.postOTRMessage).toHaveBeenCalledWith(clientId, conversationId.id, expect.objectContaining({ data: undefined }), false);
265
- });
266
- it('should send as external if payload is big', async () => {
267
- const [messageService, { apiClient }] = await buildMessageService();
268
- const longMessage = 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Duis autem Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Duis autem';
269
- jest.spyOn(apiClient.api.conversation, 'postOTRMessage').mockReturnValue(Promise.resolve({}));
270
- await messageService.sendMessage(clientId, generateRecipients(generateUsers(30, 10)), createMessage(longMessage), {
271
- conversationId,
272
- });
273
- expect(apiClient.api.conversation.postOTRMessage).toHaveBeenCalledWith(clientId, conversationId.id, expect.objectContaining({ data: expect.any(String) }), false);
274
- });
275
211
  describe('client mismatch', () => {
276
- const baseClientMismatch = {
277
- deleted: {},
278
- missing: {},
279
- redundant: {},
280
- time: new Date().toISOString(),
281
- };
282
212
  it('handles client mismatch internally if no onClientMismatch is given', async () => {
283
213
  const [messageService, { apiClient }] = await buildMessageService();
284
214
  let spyCounter = 0;
285
- const clientMismatch = Object.assign(Object.assign({}, baseClientMismatch), { deleted: { [user1.id]: [user1.clients[0]] }, missing: { [user2.id]: ['client22'] } });
215
+ const clientMismatch = Object.assign(Object.assign({}, baseMessageSendingStatus), { deleted: { [user1.domain]: { [user1.id]: [user1.clients[0]] } }, missing: { '2.wire.test': { [user2.id]: ['client22'] } } });
286
216
  jest.spyOn(apiClient.api.conversation, 'postOTRMessage').mockImplementation(() => {
287
217
  spyCounter++;
288
218
  if (spyCounter === 1) {
@@ -293,10 +223,10 @@ describe('MessageService', () => {
293
223
  };
294
224
  return Promise.reject(error);
295
225
  }
296
- return Promise.resolve(baseClientMismatch);
226
+ return Promise.resolve(baseMessageSendingStatus);
297
227
  });
298
228
  jest.spyOn(apiClient.api.user, 'postMultiPreKeyBundles').mockReturnValue(Promise.resolve({}));
299
- const recipients = generateRecipients([user1, user2]);
229
+ const recipients = generateQualifiedRecipients([user1, user2]);
300
230
  await messageService.sendMessage('senderclientid', recipients, new Uint8Array(), {
301
231
  reportMissing: true,
302
232
  conversationId: { id: 'convid', domain: '' },
@@ -305,8 +235,8 @@ describe('MessageService', () => {
305
235
  });
306
236
  it('continues message sending if onClientMismatch returns true', async () => {
307
237
  const [messageService, { apiClient }] = await buildMessageService();
308
- const onClientMismatch = jest.fn().mockReturnValue(Promise.resolve(true));
309
- const clientMismatch = Object.assign(Object.assign({}, baseClientMismatch), { missing: { [user2.id]: ['client22'] } });
238
+ const onClientMismatch = jest.fn().mockReturnValue(true);
239
+ const clientMismatch = Object.assign(Object.assign({}, baseMessageSendingStatus), { missing: { '2.wire.test': { [user2.id]: ['client22'] } } });
310
240
  let spyCounter = 0;
311
241
  jest.spyOn(apiClient.api.conversation, 'postOTRMessage').mockImplementation(() => {
312
242
  spyCounter++;
@@ -318,10 +248,10 @@ describe('MessageService', () => {
318
248
  };
319
249
  return Promise.reject(error);
320
250
  }
321
- return Promise.resolve(baseClientMismatch);
251
+ return Promise.resolve(baseMessageSendingStatus);
322
252
  });
323
253
  jest.spyOn(apiClient.api.user, 'postMultiPreKeyBundles').mockReturnValue(Promise.resolve({}));
324
- const recipients = generateRecipients([user1, user2]);
254
+ const recipients = generateQualifiedRecipients([user1, user2]);
325
255
  await messageService.sendMessage('senderclientid', recipients, new Uint8Array(), {
326
256
  reportMissing: true,
327
257
  onClientMismatch,
@@ -330,10 +260,31 @@ describe('MessageService', () => {
330
260
  expect(apiClient.api.conversation.postOTRMessage).toHaveBeenCalledTimes(2);
331
261
  expect(onClientMismatch).toHaveBeenCalledWith(clientMismatch);
332
262
  });
263
+ it('warns the consumer if they try to send a message to a deleted client', async () => {
264
+ const [messageService, { apiClient, proteusService }] = await buildMessageService();
265
+ const onClientMismatch = jest.fn().mockReturnValue(true);
266
+ const recipients = generateQualifiedRecipients([user1, user2]);
267
+ const unknowns = {
268
+ [user1.domain]: {
269
+ [user1.id]: [user1.clients[0]],
270
+ },
271
+ };
272
+ jest.spyOn(proteusService, 'encrypt').mockResolvedValue({
273
+ payloads: {},
274
+ unknowns,
275
+ });
276
+ jest.spyOn(apiClient.api.conversation, 'postOTRMessage').mockResolvedValue(baseMessageSendingStatus);
277
+ const result = await messageService.sendMessage('senderclientid', recipients, new Uint8Array(), {
278
+ reportMissing: true,
279
+ onClientMismatch,
280
+ conversationId: { id: 'convid', domain: '' },
281
+ });
282
+ expect(result.deleted).toEqual(unknowns);
283
+ });
333
284
  it('stops message sending if onClientMismatch returns false', async () => {
334
285
  const [messageService, { apiClient }] = await buildMessageService();
335
- const onClientMismatch = jest.fn().mockReturnValue(Promise.resolve(false));
336
- const clientMismatch = Object.assign(Object.assign({}, baseMessageSendingStatus), { missing: { [user2.id]: ['client22'] } });
286
+ const onClientMismatch = jest.fn().mockReturnValue(false);
287
+ const clientMismatch = Object.assign(Object.assign({}, baseMessageSendingStatus), { missing: { '2.wire.test': { [user2.id]: ['client22'] } } });
337
288
  jest.spyOn(apiClient.api.conversation, 'postOTRMessage').mockImplementation(() => {
338
289
  const error = new Error();
339
290
  error.response = {
@@ -343,7 +294,7 @@ describe('MessageService', () => {
343
294
  return Promise.reject(error);
344
295
  });
345
296
  jest.spyOn(apiClient.api.user, 'postMultiPreKeyBundles').mockReturnValue(Promise.resolve({}));
346
- const recipients = generateRecipients([user1, user2]);
297
+ const recipients = generateQualifiedRecipients([user1, user2]);
347
298
  await messageService.sendMessage('senderclientid', recipients, new Uint8Array(), {
348
299
  reportMissing: true,
349
300
  onClientMismatch,