@wireapp/core 38.15.2 → 39.0.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/lib/Account.d.ts.map +1 -1
- package/lib/Account.js +1 -3
- package/lib/broadcast/BroadcastService.d.ts +2 -2
- package/lib/broadcast/BroadcastService.d.ts.map +1 -1
- package/lib/broadcast/BroadcastService.js +5 -12
- package/lib/client/ClientDatabaseRepository.d.ts +5 -5
- package/lib/client/ClientDatabaseRepository.d.ts.map +1 -1
- package/lib/client/ClientDatabaseRepository.js +9 -16
- package/lib/client/ClientService.d.ts.map +1 -1
- package/lib/client/ClientService.js +2 -2
- package/lib/connection/ConnectionService.d.ts +2 -2
- package/lib/connection/ConnectionService.d.ts.map +1 -1
- package/lib/connection/ConnectionService.js +2 -2
- package/lib/conversation/ConversationService/ConversationService.d.ts +1 -1
- package/lib/conversation/ConversationService/ConversationService.d.ts.map +1 -1
- package/lib/conversation/ConversationService/ConversationService.test.js +1 -1
- package/lib/conversation/ConversationService/ConversationService.types.d.ts +2 -6
- package/lib/conversation/ConversationService/ConversationService.types.d.ts.map +1 -1
- package/lib/conversation/ConversationService/Utility/getConversationQualifiedMembers.d.ts +1 -1
- package/lib/conversation/ConversationService/Utility/getConversationQualifiedMembers.d.ts.map +1 -1
- package/lib/conversation/message/MessageService.d.ts +5 -31
- package/lib/conversation/message/MessageService.d.ts.map +1 -1
- package/lib/conversation/message/MessageService.js +9 -151
- package/lib/conversation/message/MessageService.test.js +107 -156
- package/lib/messagingProtocols/proteus/ProteusService/ProteusService.d.ts +7 -11
- package/lib/messagingProtocols/proteus/ProteusService/ProteusService.d.ts.map +1 -1
- package/lib/messagingProtocols/proteus/ProteusService/ProteusService.js +16 -35
- package/lib/messagingProtocols/proteus/ProteusService/ProteusService.mocks.d.ts +1 -1
- package/lib/messagingProtocols/proteus/ProteusService/ProteusService.mocks.d.ts.map +1 -1
- package/lib/messagingProtocols/proteus/ProteusService/ProteusService.mocks.js +1 -2
- package/lib/messagingProtocols/proteus/ProteusService/ProteusService.test.js +55 -94
- package/lib/messagingProtocols/proteus/ProteusService/ProteusService.types.d.ts +5 -8
- package/lib/messagingProtocols/proteus/ProteusService/ProteusService.types.d.ts.map +1 -1
- package/lib/messagingProtocols/proteus/Utility/Recipients.d.ts +4 -10
- package/lib/messagingProtocols/proteus/Utility/Recipients.d.ts.map +1 -1
- package/lib/messagingProtocols/proteus/Utility/Recipients.js +2 -13
- package/lib/messagingProtocols/proteus/Utility/SessionHandler/SessionHandler.d.ts +8 -11
- package/lib/messagingProtocols/proteus/Utility/SessionHandler/SessionHandler.d.ts.map +1 -1
- package/lib/messagingProtocols/proteus/Utility/SessionHandler/SessionHandler.js +54 -75
- package/lib/messagingProtocols/proteus/Utility/SessionHandler/SessionHandler.test.js +26 -33
- package/lib/messagingProtocols/proteus/Utility/getGenericMessageParams.d.ts +5 -14
- package/lib/messagingProtocols/proteus/Utility/getGenericMessageParams.d.ts.map +1 -1
- package/lib/messagingProtocols/proteus/Utility/getGenericMessageParams.js +3 -35
- package/lib/user/UserService.d.ts +1 -16
- package/lib/user/UserService.d.ts.map +1 -1
- package/lib/user/UserService.js +1 -43
- 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
|
|
98
|
-
const { payloads: encryptedPayload, unknowns: unknows } = await this.proteusService.
|
|
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.
|
|
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.
|
|
66
|
+
const reEncryptedPayload = await this.reencryptAfterMismatch(mismatch, encryptedPayload, plainText);
|
|
116
67
|
return send(reEncryptedPayload);
|
|
117
68
|
}
|
|
118
69
|
}
|
|
119
|
-
async
|
|
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.
|
|
112
|
+
return this.apiClient.api.broadcast.postBroadcastMessage(sendingClientId, protoMessage);
|
|
162
113
|
}
|
|
163
114
|
const { id, domain } = options.conversationId;
|
|
164
|
-
return this.apiClient.api.conversation.
|
|
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
|
|
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.
|
|
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[
|
|
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
|
|
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)(
|
|
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('
|
|
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, '
|
|
101
|
+
jest.spyOn(apiClient.api.conversation, 'postOTRMessage').mockResolvedValue(baseMessageSendingStatus);
|
|
95
102
|
const recipients = generateQualifiedRecipients([user1, user2]);
|
|
96
|
-
const result = await messageService.
|
|
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.
|
|
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({},
|
|
107
|
-
jest.spyOn(apiClient.api.conversation, '
|
|
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(
|
|
151
|
+
return Promise.resolve(baseClientMismatch);
|
|
118
152
|
});
|
|
119
|
-
jest.spyOn(apiClient.api.user, '
|
|
120
|
-
const recipients =
|
|
121
|
-
await messageService.
|
|
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.
|
|
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({},
|
|
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, '
|
|
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(
|
|
176
|
+
return Promise.resolve(baseClientMismatch);
|
|
143
177
|
});
|
|
144
|
-
jest.spyOn(apiClient.api.user, '
|
|
145
|
-
const recipients =
|
|
146
|
-
await messageService.
|
|
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.
|
|
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: {
|
|
179
|
-
jest.spyOn(apiClient.api.conversation, '
|
|
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, '
|
|
188
|
-
const recipients =
|
|
189
|
-
await messageService.
|
|
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.
|
|
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({},
|
|
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(
|
|
226
|
+
return Promise.resolve(baseMessageSendingStatus);
|
|
297
227
|
});
|
|
298
228
|
jest.spyOn(apiClient.api.user, 'postMultiPreKeyBundles').mockReturnValue(Promise.resolve({}));
|
|
299
|
-
const recipients =
|
|
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(
|
|
309
|
-
const clientMismatch = Object.assign(Object.assign({},
|
|
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(
|
|
251
|
+
return Promise.resolve(baseMessageSendingStatus);
|
|
322
252
|
});
|
|
323
253
|
jest.spyOn(apiClient.api.user, 'postMultiPreKeyBundles').mockReturnValue(Promise.resolve({}));
|
|
324
|
-
const recipients =
|
|
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(
|
|
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 =
|
|
297
|
+
const recipients = generateQualifiedRecipients([user1, user2]);
|
|
347
298
|
await messageService.sendMessage('senderclientid', recipients, new Uint8Array(), {
|
|
348
299
|
reportMissing: true,
|
|
349
300
|
onClientMismatch,
|