@wireapp/core 17.22.1 → 17.24.2
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 +46 -0
- package/package.json +2 -2
- package/src/main/Account.d.ts +10 -1
- package/src/main/Account.js +8 -2
- package/src/main/Account.js.map +1 -1
- package/src/main/Account.ts +15 -2
- package/src/main/broadcast/BroadcastService.js +3 -4
- package/src/main/broadcast/BroadcastService.js.map +1 -1
- package/src/main/broadcast/BroadcastService.ts +3 -5
- package/src/main/conversation/ConversationService.d.ts +4 -6
- package/src/main/conversation/ConversationService.js +26 -59
- package/src/main/conversation/ConversationService.js.map +1 -1
- package/src/main/conversation/ConversationService.test.node.js +0 -38
- package/src/main/conversation/ConversationService.test.node.js.map +1 -1
- package/src/main/conversation/ConversationService.test.node.ts +1 -50
- package/src/main/conversation/ConversationService.ts +32 -116
- package/src/main/conversation/message/MessageService.d.ts +42 -12
- package/src/main/conversation/message/MessageService.js +147 -275
- package/src/main/conversation/message/MessageService.js.map +1 -1
- package/src/main/conversation/message/MessageService.test.node.js +117 -10
- package/src/main/conversation/message/MessageService.test.node.js.map +1 -1
- package/src/main/conversation/message/MessageService.test.node.ts +183 -40
- package/src/main/conversation/message/MessageService.ts +209 -354
- package/src/main/conversation/message/UserClientsUtil.d.ts +22 -0
- package/src/main/conversation/message/UserClientsUtil.js +38 -0
- package/src/main/conversation/message/UserClientsUtil.js.map +1 -0
- package/src/main/conversation/message/UserClientsUtil.ts +44 -0
- package/src/main/conversation/message/UserClientsUtils.test.node.d.ts +1 -0
- package/src/main/conversation/message/UserClientsUtils.test.node.js +42 -0
- package/src/main/conversation/message/UserClientsUtils.test.node.js.map +1 -0
- package/src/main/conversation/message/UserClientsUtils.test.node.ts +44 -0
- package/src/main/cryptography/CryptographyService.d.ts +6 -1
- package/src/main/cryptography/CryptographyService.js +14 -2
- package/src/main/cryptography/CryptographyService.js.map +1 -1
- package/src/main/cryptography/CryptographyService.test.node.js +2 -2
- package/src/main/cryptography/CryptographyService.test.node.js.map +1 -1
- package/src/main/cryptography/CryptographyService.test.node.ts +5 -4
- package/src/main/cryptography/CryptographyService.ts +22 -4
- package/src/main/util/TypePredicateUtil.js +3 -9
- package/src/main/util/TypePredicateUtil.js.map +1 -1
- package/src/main/util/TypePredicateUtil.ts +3 -9
|
@@ -21,7 +21,7 @@ import {StatusCodes as HTTP_STATUS} from 'http-status-codes';
|
|
|
21
21
|
import {AxiosError} from 'axios';
|
|
22
22
|
import {proteus as ProtobufOTR} from '@wireapp/protocol-messaging/web/otr';
|
|
23
23
|
import Long from 'long';
|
|
24
|
-
import {
|
|
24
|
+
import {uuidToBytes} from '@wireapp/commons/src/main/util/StringUtil';
|
|
25
25
|
import {APIClient} from '@wireapp/api-client';
|
|
26
26
|
import {
|
|
27
27
|
ClientMismatch,
|
|
@@ -32,135 +32,124 @@ import {
|
|
|
32
32
|
QualifiedUserClients,
|
|
33
33
|
UserClients,
|
|
34
34
|
} from '@wireapp/api-client/src/conversation';
|
|
35
|
-
import {
|
|
35
|
+
import {Encoder} from 'bazinga64';
|
|
36
36
|
|
|
37
|
+
import {encryptAsset} from '../../cryptography/AssetCryptography.node';
|
|
37
38
|
import {CryptographyService} from '../../cryptography';
|
|
38
|
-
import {QualifiedId, QualifiedUserPreKeyBundleMap} from '@wireapp/api-client/src/user';
|
|
39
|
+
import {QualifiedId, QualifiedUserPreKeyBundleMap, UserPreKeyBundleMap} from '@wireapp/api-client/src/user';
|
|
40
|
+
import {MessageBuilder} from './MessageBuilder';
|
|
41
|
+
import {GenericMessage} from '@wireapp/protocol-messaging';
|
|
42
|
+
import {GenericMessageType} from '..';
|
|
43
|
+
import {flattenUserClients, flattenQualifiedUserClients} from './UserClientsUtil';
|
|
39
44
|
|
|
40
|
-
type ClientMismatchError = AxiosError<ClientMismatch>;
|
|
45
|
+
type ClientMismatchError = AxiosError<ClientMismatch | MessageSendingStatus>;
|
|
41
46
|
|
|
42
47
|
export class MessageService {
|
|
43
48
|
constructor(private readonly apiClient: APIClient, private readonly cryptographyService: CryptographyService) {}
|
|
44
49
|
|
|
45
|
-
|
|
50
|
+
/**
|
|
51
|
+
* Sends a message to a non-federated backend.
|
|
52
|
+
*
|
|
53
|
+
* @param sendingClientId The clientId of the current user
|
|
54
|
+
* @param recipients The list of recipients to send the message to
|
|
55
|
+
* @param plainText The plainText data to send
|
|
56
|
+
* @param options.conversationId? the conversation to send the message to. Will broadcast if not set
|
|
57
|
+
* @param options.reportMissing? trigger a mismatch error when there are missing recipients in the payload
|
|
58
|
+
* @param options.sendAsProtobuf?
|
|
59
|
+
* @param options.onClientMismatch? Called when a mismatch happens on the server
|
|
60
|
+
* @return the ClientMismatch status returned by the backend
|
|
61
|
+
*/
|
|
62
|
+
public async sendMessage(
|
|
46
63
|
sendingClientId: string,
|
|
47
|
-
recipients:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
recipients: UserClients | UserPreKeyBundleMap,
|
|
65
|
+
plainText: Uint8Array,
|
|
66
|
+
options: {
|
|
67
|
+
conversationId?: string;
|
|
68
|
+
reportMissing?: boolean;
|
|
69
|
+
sendAsProtobuf?: boolean;
|
|
70
|
+
onClientMismatch?: (mismatch: ClientMismatch) => Promise<boolean | undefined>;
|
|
71
|
+
} = {},
|
|
51
72
|
): Promise<ClientMismatch> {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
* When creating the PreKey bundles we already found out to which users we want to send a message, so we can ignore
|
|
60
|
-
* missing clients. We have to ignore missing clients because there can be the case that there are clients that
|
|
61
|
-
* don't provide PreKeys (clients from the Pre-E2EE era).
|
|
62
|
-
*/
|
|
63
|
-
const ignoreMissing = true;
|
|
73
|
+
let plainTextPayload = plainText;
|
|
74
|
+
let cipherText: Uint8Array;
|
|
75
|
+
if (this.shouldSendAsExternal(plainText, recipients)) {
|
|
76
|
+
const externalPayload = await this.generateExternalPayload(plainText);
|
|
77
|
+
plainTextPayload = externalPayload.text;
|
|
78
|
+
cipherText = externalPayload.cipherText;
|
|
79
|
+
}
|
|
64
80
|
|
|
81
|
+
const encryptedPayload = await this.cryptographyService.encrypt(plainTextPayload, recipients);
|
|
82
|
+
const send = (payload: OTRRecipients<Uint8Array>) => {
|
|
83
|
+
return options.sendAsProtobuf
|
|
84
|
+
? this.sendOTRProtobufMessage(sendingClientId, payload, {...options, assetData: cipherText})
|
|
85
|
+
: this.sendOTRMessage(sendingClientId, payload, {...options, assetData: cipherText});
|
|
86
|
+
};
|
|
65
87
|
try {
|
|
66
|
-
|
|
67
|
-
return await this.apiClient.broadcast.api.postBroadcastMessage(sendingClientId, message, ignoreMissing);
|
|
68
|
-
}
|
|
69
|
-
return await this.apiClient.conversation.api.postOTRMessage(
|
|
70
|
-
sendingClientId,
|
|
71
|
-
conversationId,
|
|
72
|
-
message,
|
|
73
|
-
ignoreMissing,
|
|
74
|
-
);
|
|
88
|
+
return await send(encryptedPayload);
|
|
75
89
|
} catch (error) {
|
|
76
90
|
if (!this.isClientMismatchError(error)) {
|
|
77
91
|
throw error;
|
|
78
92
|
}
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
plainTextArray,
|
|
83
|
-
);
|
|
84
|
-
return await this.apiClient.broadcast.api.postBroadcastMessage(sendingClientId, {
|
|
85
|
-
data: reEncryptedMessage.data ? Encoder.toBase64(reEncryptedMessage.data).asString : undefined,
|
|
86
|
-
recipients: CryptographyService.convertArrayRecipientsToBase64(reEncryptedMessage.recipients),
|
|
87
|
-
sender: reEncryptedMessage.sender,
|
|
88
|
-
});
|
|
93
|
+
const mismatch = error.response!.data as ClientMismatch;
|
|
94
|
+
const reEncryptedMessage = await this.reencryptAfterMismatch(mismatch, encryptedPayload, plainText);
|
|
95
|
+
return send(reEncryptedMessage);
|
|
89
96
|
}
|
|
90
97
|
}
|
|
91
98
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
99
|
+
/**
|
|
100
|
+
* Sends a message to a federated backend.
|
|
101
|
+
*
|
|
102
|
+
* @param sendingClientId The clientId of the current user
|
|
103
|
+
* @param recipients The list of recipients to send the message to
|
|
104
|
+
* @param plainText The plainText data to send
|
|
105
|
+
* @param options.conversationId? the conversation to send the message to. Will broadcast if not set
|
|
106
|
+
* @param options.reportMissing? trigger a mismatch error when there are missing recipients in the payload
|
|
107
|
+
* @param options.sendAsProtobuf?
|
|
108
|
+
* @param options.onClientMismatch? Called when a mismatch happens on the server
|
|
109
|
+
* @return the MessageSendingStatus returned by the backend
|
|
110
|
+
*/
|
|
111
|
+
public async sendFederatedMessage(
|
|
112
|
+
sendingClientId: string,
|
|
113
|
+
recipients: QualifiedUserClients | QualifiedUserPreKeyBundleMap,
|
|
114
|
+
plainText: Uint8Array,
|
|
115
|
+
options: {
|
|
116
|
+
assetData?: Uint8Array;
|
|
117
|
+
conversationId?: QualifiedId;
|
|
118
|
+
reportMissing?: boolean;
|
|
119
|
+
onClientMismatch?: (mismatch: MessageSendingStatus) => Promise<boolean | undefined>;
|
|
120
|
+
},
|
|
121
|
+
): Promise<MessageSendingStatus> {
|
|
122
|
+
const send = (payload: QualifiedOTRRecipients) => {
|
|
123
|
+
return this.sendFederatedOtrMessage(sendingClientId, payload, options);
|
|
124
|
+
};
|
|
125
|
+
const encryptedPayload = await this.cryptographyService.encryptQualified(plainText, recipients);
|
|
126
|
+
try {
|
|
127
|
+
return await send(encryptedPayload);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
if (!this.isClientMismatchError(error)) {
|
|
130
|
+
throw error;
|
|
127
131
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
updatedMessageSendingStatus[sendingStatusKey],
|
|
133
|
-
)) {
|
|
134
|
-
for (const userIdDeleted of Object.keys(userClientsDeleted)) {
|
|
135
|
-
if (userIdDeleted !== reportUserId.id && domainDeleted !== reportDomain) {
|
|
136
|
-
delete updatedMessageSendingStatus[sendingStatusKey][domainDeleted][userIdDeleted];
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
132
|
+
const mismatch = error.response!.data as MessageSendingStatus;
|
|
133
|
+
const shouldStopSending = options.onClientMismatch && !(await options.onClientMismatch(mismatch));
|
|
134
|
+
if (shouldStopSending) {
|
|
135
|
+
return mismatch;
|
|
141
136
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
return null;
|
|
137
|
+
const reEncryptedPayload = await this.reencryptAfterFederatedMismatch(mismatch, encryptedPayload, plainText);
|
|
138
|
+
return send(reEncryptedPayload);
|
|
145
139
|
}
|
|
146
|
-
|
|
147
|
-
return updatedMessageSendingStatus;
|
|
148
140
|
}
|
|
149
141
|
|
|
150
|
-
|
|
142
|
+
private async sendFederatedOtrMessage(
|
|
151
143
|
sendingClientId: string,
|
|
152
|
-
|
|
153
|
-
recipients: QualifiedUserClients | QualifiedUserPreKeyBundleMap,
|
|
154
|
-
plainTextArray: Uint8Array,
|
|
144
|
+
recipients: QualifiedOTRRecipients,
|
|
155
145
|
options: {
|
|
156
146
|
assetData?: Uint8Array;
|
|
147
|
+
conversationId?: QualifiedId;
|
|
157
148
|
reportMissing?: boolean;
|
|
158
149
|
onClientMismatch?: (mismatch: MessageSendingStatus) => Promise<boolean | undefined>;
|
|
159
|
-
}
|
|
150
|
+
},
|
|
160
151
|
): Promise<MessageSendingStatus> {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
const qualifiedUserEntries = Object.entries(otrRecipients).map<ProtobufOTR.IQualifiedUserEntry>(
|
|
152
|
+
const qualifiedUserEntries = Object.entries(recipients).map<ProtobufOTR.IQualifiedUserEntry>(
|
|
164
153
|
([domain, otrRecipients]) => {
|
|
165
154
|
const userEntries = Object.entries(otrRecipients).map<ProtobufOTR.IUserEntry>(([userId, otrClientMap]) => {
|
|
166
155
|
const clientEntries = Object.entries(otrClientMap).map<ProtobufOTR.IClientEntry>(([clientId, payload]) => {
|
|
@@ -195,237 +184,96 @@ export class MessageService {
|
|
|
195
184
|
protoMessage.blob = options.assetData;
|
|
196
185
|
}
|
|
197
186
|
|
|
198
|
-
/*
|
|
199
|
-
* When creating the PreKey bundles we already found out to which users we want to send a message, so we can ignore
|
|
200
|
-
* missing clients. We have to ignore missing clients because there can be the case that there are clients that
|
|
201
|
-
* don't provide PreKeys (clients from the Pre-E2EE era).
|
|
202
|
-
*/
|
|
203
187
|
if (options.reportMissing) {
|
|
204
188
|
protoMessage.reportAll = {};
|
|
205
189
|
} else {
|
|
206
190
|
protoMessage.ignoreAll = {};
|
|
207
191
|
}
|
|
208
192
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
} catch (error) {
|
|
213
|
-
if (!this.isClientMismatchError(error)) {
|
|
214
|
-
throw error;
|
|
215
|
-
}
|
|
216
|
-
sendingStatus = error.response!.data! as unknown as MessageSendingStatus;
|
|
193
|
+
if (!options.conversationId) {
|
|
194
|
+
//TODO implement federated broadcast sending
|
|
195
|
+
throw new Error('Unimplemented federated broadcast');
|
|
217
196
|
}
|
|
218
197
|
|
|
219
|
-
const
|
|
198
|
+
const {id, domain} = options.conversationId;
|
|
220
199
|
|
|
221
|
-
|
|
222
|
-
const shouldStopSending = options.onClientMismatch && !(await options.onClientMismatch(mismatch));
|
|
223
|
-
if (shouldStopSending) {
|
|
224
|
-
return sendingStatus;
|
|
225
|
-
}
|
|
226
|
-
const reEncryptedMessage = await this.onFederatedMismatch(protoMessage, mismatch, plainTextArray);
|
|
227
|
-
await this.apiClient.conversation.api.postOTRMessageV2(conversationId, domain, reEncryptedMessage);
|
|
228
|
-
}
|
|
229
|
-
return sendingStatus;
|
|
200
|
+
return this.apiClient.conversation.api.postOTRMessageV2(id, domain, protoMessage);
|
|
230
201
|
}
|
|
231
202
|
|
|
232
|
-
|
|
203
|
+
private async sendOTRMessage(
|
|
233
204
|
sendingClientId: string,
|
|
234
205
|
recipients: OTRRecipients<Uint8Array>,
|
|
235
|
-
|
|
236
|
-
plainTextArray: Uint8Array,
|
|
237
|
-
assetData?: Uint8Array,
|
|
206
|
+
options: {conversationId?: string; assetData?: Uint8Array; reportMissing?: boolean},
|
|
238
207
|
): Promise<ClientMismatch> {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
},
|
|
245
|
-
text: payload,
|
|
246
|
-
};
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
return {
|
|
250
|
-
clients,
|
|
251
|
-
user: {
|
|
252
|
-
uuid: uuidToBytes(userId),
|
|
253
|
-
},
|
|
254
|
-
};
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
const protoMessage = ProtobufOTR.NewOtrMessage.create({
|
|
258
|
-
recipients: userEntries,
|
|
259
|
-
sender: {
|
|
260
|
-
client: Long.fromString(sendingClientId, 16),
|
|
261
|
-
},
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
if (assetData) {
|
|
265
|
-
protoMessage.blob = assetData;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
/*
|
|
269
|
-
* When creating the PreKey bundles we already found out to which users we want to send a message, so we can ignore
|
|
270
|
-
* missing clients. We have to ignore missing clients because there can be the case that there are clients that
|
|
271
|
-
* don't provide PreKeys (clients from the Pre-E2EE era).
|
|
272
|
-
*/
|
|
273
|
-
const ignoreMissing = true;
|
|
208
|
+
const message: NewOTRMessage<string> = {
|
|
209
|
+
data: options.assetData ? Encoder.toBase64(options.assetData).asString : undefined,
|
|
210
|
+
recipients: CryptographyService.convertArrayRecipientsToBase64(recipients),
|
|
211
|
+
sender: sendingClientId,
|
|
212
|
+
};
|
|
274
213
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
214
|
+
return !options.conversationId
|
|
215
|
+
? this.apiClient.broadcast.api.postBroadcastMessage(sendingClientId, message, !options.reportMissing)
|
|
216
|
+
: this.apiClient.conversation.api.postOTRMessage(
|
|
278
217
|
sendingClientId,
|
|
279
|
-
|
|
280
|
-
|
|
218
|
+
options.conversationId,
|
|
219
|
+
message,
|
|
220
|
+
!options.reportMissing,
|
|
281
221
|
);
|
|
282
|
-
}
|
|
283
|
-
return await this.apiClient.conversation.api.postOTRProtobufMessage(
|
|
284
|
-
sendingClientId,
|
|
285
|
-
conversationId,
|
|
286
|
-
protoMessage,
|
|
287
|
-
ignoreMissing,
|
|
288
|
-
);
|
|
289
|
-
} catch (error) {
|
|
290
|
-
if (!this.isClientMismatchError(error)) {
|
|
291
|
-
throw error;
|
|
292
|
-
}
|
|
293
|
-
const mismatch = error.response!.data;
|
|
294
|
-
const reEncryptedMessage = await this.onClientProtobufMismatch(mismatch, protoMessage, plainTextArray);
|
|
295
|
-
if (conversationId === null) {
|
|
296
|
-
return await this.apiClient.broadcast.api.postBroadcastProtobufMessage(sendingClientId, reEncryptedMessage);
|
|
297
|
-
}
|
|
298
|
-
return await this.apiClient.conversation.api.postOTRProtobufMessage(
|
|
299
|
-
sendingClientId,
|
|
300
|
-
conversationId,
|
|
301
|
-
reEncryptedMessage,
|
|
302
|
-
ignoreMissing,
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
222
|
}
|
|
306
223
|
|
|
307
|
-
private async
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
): Promise<NewOTRMessage<Uint8Array>> {
|
|
312
|
-
const {missing, deleted} = clientMismatch;
|
|
313
|
-
|
|
314
|
-
const deletedUserIds = Object.keys(deleted);
|
|
315
|
-
const missingUserIds = Object.keys(missing);
|
|
316
|
-
|
|
317
|
-
if (deletedUserIds.length) {
|
|
318
|
-
for (const deletedUserId of deletedUserIds) {
|
|
319
|
-
for (const deletedClientId of deleted[deletedUserId]) {
|
|
320
|
-
const deletedUser = message.recipients[deletedUserId];
|
|
321
|
-
if (deletedUser) {
|
|
322
|
-
delete deletedUser[deletedClientId];
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
224
|
+
private async generateExternalPayload(plainText: Uint8Array): Promise<{text: Uint8Array; cipherText: Uint8Array}> {
|
|
225
|
+
const asset = await encryptAsset({plainText});
|
|
226
|
+
const {cipherText, keyBytes, sha256} = asset;
|
|
227
|
+
const messageId = MessageBuilder.createId();
|
|
327
228
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
message.recipients[missingUserId][missingClientId] = reEncryptedPayloads[missingUserId][missingClientId];
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
229
|
+
const externalMessage = {
|
|
230
|
+
otrKey: new Uint8Array(keyBytes),
|
|
231
|
+
sha256: new Uint8Array(sha256),
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const genericMessage = GenericMessage.create({
|
|
235
|
+
[GenericMessageType.EXTERNAL]: externalMessage,
|
|
236
|
+
messageId,
|
|
237
|
+
});
|
|
342
238
|
|
|
343
|
-
return
|
|
239
|
+
return {text: GenericMessage.encode(genericMessage).finish(), cipherText};
|
|
344
240
|
}
|
|
345
241
|
|
|
346
|
-
private
|
|
347
|
-
|
|
348
|
-
message: ProtobufOTR.NewOtrMessage,
|
|
349
|
-
plainTextArray: Uint8Array,
|
|
350
|
-
): Promise<ProtobufOTR.NewOtrMessage> {
|
|
351
|
-
const {missing, deleted} = clientMismatch;
|
|
352
|
-
|
|
353
|
-
const deletedUserIds = Object.keys(deleted);
|
|
354
|
-
const missingUserIds = Object.keys(missing);
|
|
355
|
-
|
|
356
|
-
if (deletedUserIds.length) {
|
|
357
|
-
for (const deletedUserId of deletedUserIds) {
|
|
358
|
-
for (const deletedClientId of deleted[deletedUserId]) {
|
|
359
|
-
const deletedUserIndex = message.recipients.findIndex(({user}) => bytesToUUID(user.uuid) === deletedUserId);
|
|
360
|
-
if (deletedUserIndex > -1) {
|
|
361
|
-
const deletedClientIndex = message.recipients[deletedUserIndex].clients?.findIndex(({client}) => {
|
|
362
|
-
return client.client.toString(16) === deletedClientId;
|
|
363
|
-
});
|
|
364
|
-
if (typeof deletedClientIndex !== 'undefined' && deletedClientIndex > -1) {
|
|
365
|
-
delete message.recipients[deletedUserIndex].clients?.[deletedClientIndex!];
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
242
|
+
private shouldSendAsExternal(plainText: Uint8Array, preKeyBundles: UserPreKeyBundleMap | UserClients): boolean {
|
|
243
|
+
const EXTERNAL_MESSAGE_THRESHOLD_BYTES = 200 * 1024;
|
|
371
244
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
for (const missingUserId of missingUserIds) {
|
|
376
|
-
for (const missingClientId in reEncryptedPayloads[missingUserId]) {
|
|
377
|
-
const missingUserIndex = message.recipients.findIndex(({user}) => bytesToUUID(user.uuid) === missingUserId);
|
|
378
|
-
if (missingUserIndex === -1) {
|
|
379
|
-
message.recipients.push({
|
|
380
|
-
clients: [
|
|
381
|
-
{
|
|
382
|
-
client: {
|
|
383
|
-
client: Long.fromString(missingClientId, 16),
|
|
384
|
-
},
|
|
385
|
-
text: reEncryptedPayloads[missingUserId][missingClientId],
|
|
386
|
-
},
|
|
387
|
-
],
|
|
388
|
-
user: {
|
|
389
|
-
uuid: uuidToBytes(missingUserId),
|
|
390
|
-
},
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
}
|
|
245
|
+
let clientCount = 0;
|
|
246
|
+
for (const user in preKeyBundles) {
|
|
247
|
+
clientCount += Object.keys(preKeyBundles[user]).length;
|
|
395
248
|
}
|
|
396
249
|
|
|
397
|
-
|
|
250
|
+
const messageInBytes = new Uint8Array(plainText).length;
|
|
251
|
+
const estimatedPayloadInBytes = clientCount * messageInBytes;
|
|
252
|
+
|
|
253
|
+
return estimatedPayloadInBytes > EXTERNAL_MESSAGE_THRESHOLD_BYTES;
|
|
398
254
|
}
|
|
399
255
|
|
|
400
|
-
private
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
if (!!uuid && bytesToUUID(uuid) === deletedUserId) {
|
|
420
|
-
// delete this user from the message recipients
|
|
421
|
-
delete message.recipients[recipientIndex].entries![entriesIndex];
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
}
|
|
256
|
+
private isClientMismatchError(error: any): error is ClientMismatchError {
|
|
257
|
+
return error.response?.status === HTTP_STATUS.PRECONDITION_FAILED;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private async reencryptAfterMismatch(
|
|
261
|
+
mismatch: ClientMismatch,
|
|
262
|
+
recipients: OTRRecipients<Uint8Array>,
|
|
263
|
+
plainText: Uint8Array,
|
|
264
|
+
): Promise<OTRRecipients<Uint8Array>> {
|
|
265
|
+
const deleted = flattenUserClients(mismatch.deleted);
|
|
266
|
+
const missing = flattenUserClients(mismatch.missing);
|
|
267
|
+
// remove deleted clients to the recipients
|
|
268
|
+
deleted.forEach(({userId, data}) => data.forEach(clientId => delete recipients[userId.id][clientId]));
|
|
269
|
+
if (missing.length) {
|
|
270
|
+
const missingPreKeyBundles = await this.apiClient.user.api.postMultiPreKeyBundles(mismatch.missing);
|
|
271
|
+
const reEncrypted = await this.cryptographyService.encrypt(plainText, missingPreKeyBundles);
|
|
272
|
+
const reEncryptedPayloads = flattenUserClients<{[client: string]: Uint8Array}>(reEncrypted);
|
|
273
|
+
// add missing clients to the recipients
|
|
274
|
+
reEncryptedPayloads.forEach(({data, userId}) => (recipients[userId.id] = {...recipients[userId.id], ...data}));
|
|
427
275
|
}
|
|
428
|
-
return
|
|
276
|
+
return recipients;
|
|
429
277
|
}
|
|
430
278
|
|
|
431
279
|
/**
|
|
@@ -436,63 +284,70 @@ export class MessageService {
|
|
|
436
284
|
* @param {Uint8Array} plainText The text that should be encrypted for the missing clients
|
|
437
285
|
* @return resolves with a new message payload that can be sent
|
|
438
286
|
*/
|
|
439
|
-
private async
|
|
440
|
-
|
|
441
|
-
|
|
287
|
+
private async reencryptAfterFederatedMismatch(
|
|
288
|
+
mismatch: MessageSendingStatus,
|
|
289
|
+
recipients: QualifiedOTRRecipients,
|
|
442
290
|
plainText: Uint8Array,
|
|
443
|
-
): Promise<
|
|
444
|
-
|
|
291
|
+
): Promise<QualifiedOTRRecipients> {
|
|
292
|
+
const deleted = flattenQualifiedUserClients(mismatch.deleted);
|
|
293
|
+
const missing = flattenQualifiedUserClients(mismatch.missing);
|
|
294
|
+
// remove deleted clients to the recipients
|
|
295
|
+
deleted.forEach(({userId, data}) =>
|
|
296
|
+
data.forEach(clientId => delete recipients[userId.domain][userId.id][clientId]),
|
|
297
|
+
);
|
|
298
|
+
|
|
445
299
|
if (Object.keys(missing).length) {
|
|
446
|
-
const missingPreKeyBundles = await this.apiClient.user.api.postQualifiedMultiPreKeyBundles(missing);
|
|
447
|
-
const
|
|
448
|
-
|
|
300
|
+
const missingPreKeyBundles = await this.apiClient.user.api.postQualifiedMultiPreKeyBundles(mismatch.missing);
|
|
301
|
+
const reEncrypted = await this.cryptographyService.encryptQualified(plainText, missingPreKeyBundles);
|
|
302
|
+
const reEncryptedPayloads = flattenQualifiedUserClients<{[client: string]: Uint8Array}>(reEncrypted);
|
|
303
|
+
reEncryptedPayloads.forEach(
|
|
304
|
+
({data, userId}) => (recipients[userId.domain][userId.id] = {...recipients[userId.domain][userId.id], ...data}),
|
|
305
|
+
);
|
|
449
306
|
}
|
|
450
|
-
return
|
|
307
|
+
return recipients;
|
|
451
308
|
}
|
|
452
309
|
|
|
453
|
-
private
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
client: Long.fromString(missingClientId, 16),
|
|
486
|
-
},
|
|
487
|
-
text: missingClientPayload,
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
}
|
|
310
|
+
private async sendOTRProtobufMessage(
|
|
311
|
+
sendingClientId: string,
|
|
312
|
+
recipients: OTRRecipients<Uint8Array>,
|
|
313
|
+
options: {conversationId?: string; assetData?: Uint8Array; reportMissing?: boolean},
|
|
314
|
+
): Promise<ClientMismatch> {
|
|
315
|
+
const userEntries: ProtobufOTR.IUserEntry[] = Object.entries(recipients).map(([userId, otrClientMap]) => {
|
|
316
|
+
const clients: ProtobufOTR.IClientEntry[] = Object.entries(otrClientMap).map(([clientId, payload]) => {
|
|
317
|
+
return {
|
|
318
|
+
client: {
|
|
319
|
+
client: Long.fromString(clientId, 16),
|
|
320
|
+
},
|
|
321
|
+
text: payload,
|
|
322
|
+
};
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
clients,
|
|
327
|
+
user: {
|
|
328
|
+
uuid: uuidToBytes(userId),
|
|
329
|
+
},
|
|
330
|
+
};
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
const protoMessage = ProtobufOTR.NewOtrMessage.create({
|
|
334
|
+
recipients: userEntries,
|
|
335
|
+
sender: {
|
|
336
|
+
client: Long.fromString(sendingClientId, 16),
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
if (options.assetData) {
|
|
341
|
+
protoMessage.blob = options.assetData;
|
|
494
342
|
}
|
|
495
343
|
|
|
496
|
-
return
|
|
344
|
+
return !options.conversationId
|
|
345
|
+
? this.apiClient.broadcast.api.postBroadcastProtobufMessage(sendingClientId, protoMessage, !options.reportMissing)
|
|
346
|
+
: this.apiClient.conversation.api.postOTRProtobufMessage(
|
|
347
|
+
sendingClientId,
|
|
348
|
+
options.conversationId,
|
|
349
|
+
protoMessage,
|
|
350
|
+
!options.reportMissing,
|
|
351
|
+
);
|
|
497
352
|
}
|
|
498
353
|
}
|