@wireapp/core 46.39.12 → 46.40.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.
- package/lib/conversation/ConversationService/ConversationService.d.ts.map +1 -1
- package/lib/conversation/ConversationService/ConversationService.js +25 -11
- package/lib/conversation/ConversationService/ConversationService.test.js +28 -0
- package/lib/conversation/SubconversationService/SubconversationService.d.ts.map +1 -1
- package/lib/conversation/SubconversationService/SubconversationService.js +153 -7
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConversationService.d.ts","sourceRoot":"","sources":["../../../src/conversation/ConversationService/ConversationService.ts"],"names":[],"mappings":"AAmBA,OAAO,EACL,YAAY,EACZ,2BAA2B,EAC3B,WAAW,EACX,eAAe,EACf,oBAAoB,EAEpB,mBAAmB,EACnB,eAAe,EACf,kBAAkB,
|
|
1
|
+
{"version":3,"file":"ConversationService.d.ts","sourceRoot":"","sources":["../../../src/conversation/ConversationService/ConversationService.ts"],"names":[],"mappings":"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"}
|
|
@@ -263,9 +263,9 @@ class ConversationService extends commons_1.TypedEventEmitter {
|
|
|
263
263
|
};
|
|
264
264
|
}
|
|
265
265
|
catch (error) {
|
|
266
|
-
this.logger.
|
|
266
|
+
this.logger.warn('Failed to send MLS message', { error, groupId });
|
|
267
267
|
if (!shouldRetry) {
|
|
268
|
-
this.logger.
|
|
268
|
+
this.logger.error("Tried to send MLS message but it's still failing after recovery", {
|
|
269
269
|
error,
|
|
270
270
|
groupId,
|
|
271
271
|
});
|
|
@@ -280,7 +280,6 @@ class ConversationService extends commons_1.TypedEventEmitter {
|
|
|
280
280
|
groupId,
|
|
281
281
|
});
|
|
282
282
|
await this.handleBrokenMLSConversation(conversationId);
|
|
283
|
-
return this.sendMLSMessage(params, false);
|
|
284
283
|
}
|
|
285
284
|
/**
|
|
286
285
|
* We may have the same error from core-crypto or from the backend error mapper
|
|
@@ -293,13 +292,19 @@ class ConversationService extends commons_1.TypedEventEmitter {
|
|
|
293
292
|
groupId,
|
|
294
293
|
});
|
|
295
294
|
await this.recoverMLSGroupFromEpochMismatch(conversationId);
|
|
296
|
-
return this.sendMLSMessage(params, false);
|
|
297
295
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
296
|
+
if (error instanceof conversation_1.MLSGroupOutOfSyncError) {
|
|
297
|
+
this.logger.info('Failed to send MLS message because of group out of sync, recovering by adding missing users', {
|
|
298
|
+
error,
|
|
299
|
+
groupId,
|
|
300
|
+
});
|
|
301
|
+
await this.addUsersToMLSConversation({
|
|
302
|
+
groupId,
|
|
303
|
+
conversationId,
|
|
304
|
+
qualifiedUsers: error.missing_users,
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return this.sendMLSMessage(params, false);
|
|
303
308
|
}
|
|
304
309
|
}
|
|
305
310
|
/**
|
|
@@ -311,6 +316,7 @@ class ConversationService extends commons_1.TypedEventEmitter {
|
|
|
311
316
|
*/
|
|
312
317
|
async addUsersToMLSConversation({ qualifiedUsers, groupId, conversationId, shouldRetry = true, }) {
|
|
313
318
|
try {
|
|
319
|
+
this.logger.info(`Adding users to MLS conversation`, { groupId, conversationId, qualifiedUsers });
|
|
314
320
|
const exisitingClientIdsInGroup = await this.mlsService.getClientIdsInGroup(groupId);
|
|
315
321
|
const conversation = await this.getConversation(conversationId);
|
|
316
322
|
const { keyPackages, failures: keysClaimingFailures } = await this.mlsService.getKeyPackagesPayload(qualifiedUsers, exisitingClientIdsInGroup);
|
|
@@ -326,6 +332,7 @@ class ConversationService extends commons_1.TypedEventEmitter {
|
|
|
326
332
|
};
|
|
327
333
|
}
|
|
328
334
|
catch (error) {
|
|
335
|
+
this.logger.warn('Failed to add users to MLS conversation', { error, groupId, conversationId });
|
|
329
336
|
if (mls_1.MLSService.isBrokenMLSConversationError(error)) {
|
|
330
337
|
if (!shouldRetry) {
|
|
331
338
|
this.logger.warn("Tried to add users to MLS conversation but it's still broken after reset", error);
|
|
@@ -510,8 +517,14 @@ class ConversationService extends commons_1.TypedEventEmitter {
|
|
|
510
517
|
async hasEpochMismatch(groupId, epoch) {
|
|
511
518
|
const isEstablished = await this.mlsGroupExistsLocally(groupId);
|
|
512
519
|
const doesEpochMatch = isEstablished && (await this.matchesEpoch(groupId, epoch));
|
|
513
|
-
//if conversation is not established or epoch does not match -> try to rejoin
|
|
514
|
-
|
|
520
|
+
// if conversation is not established or epoch does not match -> try to rejoin
|
|
521
|
+
const hasEpochMismatch = !isEstablished || !doesEpochMatch;
|
|
522
|
+
this.logger.info(`Conversation (group_id: ${groupId}) epoch mismatch check result: ${hasEpochMismatch}`, {
|
|
523
|
+
isEstablished,
|
|
524
|
+
doesEpochMatch,
|
|
525
|
+
epoch,
|
|
526
|
+
});
|
|
527
|
+
return hasEpochMismatch;
|
|
515
528
|
}
|
|
516
529
|
/**
|
|
517
530
|
* Get a MLS 1:1-conversation with a given user.
|
|
@@ -628,6 +641,7 @@ class ConversationService extends commons_1.TypedEventEmitter {
|
|
|
628
641
|
}
|
|
629
642
|
}
|
|
630
643
|
async recoverMLSGroupFromEpochMismatch(conversationId, subconversationId) {
|
|
644
|
+
this.logger.info(`Recovering MLS group from epoch mismatch`, { conversationId, subconversationId });
|
|
631
645
|
if (subconversationId) {
|
|
632
646
|
const parentGroupId = await this.groupIdFromConversationId(conversationId);
|
|
633
647
|
const subconversation = await this.apiClient.api.conversation.getSubconversation(conversationId, subconversationId);
|
|
@@ -222,6 +222,34 @@ describe('ConversationService', () => {
|
|
|
222
222
|
});
|
|
223
223
|
expect(apiClient.api.conversation.postMlsMessage).toHaveBeenCalledTimes(2);
|
|
224
224
|
});
|
|
225
|
+
it('adds missing users to MLS group and retries when group is out of sync during send', async () => {
|
|
226
|
+
const [conversationService, { apiClient }] = await buildConversationService();
|
|
227
|
+
const mockGroupId = 'AAEAAH87aajaQ011i+rNLmwpy0sAZGl5YS53aXJlamxpbms=';
|
|
228
|
+
const mockConversationId = { id: 'mockConversationId', domain: 'staging.zinfra.io' };
|
|
229
|
+
const mockedMessage = MessageBuilder.buildTextMessage({ text: 'test' });
|
|
230
|
+
const missingUsers = [
|
|
231
|
+
{ id: 'user-1', domain: 'staging.zinfra.io' },
|
|
232
|
+
{ id: 'user-2', domain: 'staging.zinfra.io' },
|
|
233
|
+
];
|
|
234
|
+
const outOfSyncError = new conversation_1.MLSGroupOutOfSyncError(http_status_codes_1.StatusCodes.CONFLICT, missingUsers, http_1.BackendErrorLabel.MLS_GROUP_OUT_OF_SYNC);
|
|
235
|
+
// First send fails with out-of-sync, second succeeds via default mock
|
|
236
|
+
jest.spyOn(apiClient.api.conversation, 'postMlsMessage').mockRejectedValueOnce(outOfSyncError);
|
|
237
|
+
const addUsersSpy = jest
|
|
238
|
+
.spyOn(conversationService, 'addUsersToMLSConversation')
|
|
239
|
+
.mockResolvedValueOnce({ conversation: { members: { others: [] } } });
|
|
240
|
+
await conversationService.send({
|
|
241
|
+
protocol: conversation_1.ConversationProtocol.MLS,
|
|
242
|
+
groupId: mockGroupId,
|
|
243
|
+
payload: mockedMessage,
|
|
244
|
+
conversationId: mockConversationId,
|
|
245
|
+
});
|
|
246
|
+
expect(addUsersSpy).toHaveBeenCalledWith({
|
|
247
|
+
groupId: mockGroupId,
|
|
248
|
+
conversationId: mockConversationId,
|
|
249
|
+
qualifiedUsers: missingUsers,
|
|
250
|
+
});
|
|
251
|
+
expect(apiClient.api.conversation.postMlsMessage).toHaveBeenCalledTimes(2);
|
|
252
|
+
});
|
|
225
253
|
});
|
|
226
254
|
describe('handleConversationsEpochMismatch', () => {
|
|
227
255
|
beforeEach(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubconversationService.d.ts","sourceRoot":"","sources":["../../../src/conversation/SubconversationService/SubconversationService.ts"],"names":[],"mappings":"AAmBA,OAAO,EAAC,kBAAkB,EAAkB,MAAM,sCAAsC,CAAC;AACzF,OAAO,EAAC,WAAW,EAAC,MAAM,8BAA8B,CAAC;AAGzD,OAAO,EAAC,SAAS,EAAC,MAAM,qBAAqB,CAAC;AAC9C,OAAO,EAAa,iBAAiB,EAAC,MAAM,kBAAkB,CAAC;AAI/D,OAAO,EAAC,UAAU,EAAmB,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAC,YAAY,EAAC,MAAM,sBAAsB,CAAC;AAGlD,KAAK,MAAM,GAAG;IACZ,wBAAwB,EAAE;QAAC,cAAc,EAAE,WAAW,CAAA;KAAC,CAAC;CACzD,CAAC;AAEF,MAAM,WAAW,8BAA8B;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,OAAO,CAAC;CACrB;AAID,qBAAa,sBAAuB,SAAQ,iBAAiB,CAAC,MAAM,CAAC;IAIjE,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;IAL/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgE;gBAGpE,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,YAAY,EAC1B,WAAW,CAAC,EAAE,UAAU,YAAA;IAK3C,IAAI,UAAU,IAAI,UAAU,CAK3B;IAED;;;;;;OAMG;IACU,6BAA6B,CACxC,cAAc,EAAE,WAAW,EAC3B,OAAO,EAAE,MAAM,EACf,WAAW,UAAO,GACjB,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"SubconversationService.d.ts","sourceRoot":"","sources":["../../../src/conversation/SubconversationService/SubconversationService.ts"],"names":[],"mappings":"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"}
|
|
@@ -52,6 +52,7 @@ class SubconversationService extends commons_1.TypedEventEmitter {
|
|
|
52
52
|
*/
|
|
53
53
|
async joinConferenceSubconversation(conversationId, groupId, shouldRetry = true) {
|
|
54
54
|
try {
|
|
55
|
+
this.logger.info('Joining conference subconversation', { conversationId, groupId });
|
|
55
56
|
const { group_id: subconversationGroupId, epoch: subconversationEpoch, epoch_timestamp: subconversationEpochTimestamp, subconv_id: subconversationId, } = await this.getConferenceSubconversation(conversationId);
|
|
56
57
|
// We store the mapping between the subconversation and the parent conversation
|
|
57
58
|
await this.saveSubconversationGroupId(conversationId, subconversationId, subconversationGroupId);
|
|
@@ -81,6 +82,7 @@ class SubconversationService extends commons_1.TypedEventEmitter {
|
|
|
81
82
|
return { groupId: subconversationGroupId, epoch };
|
|
82
83
|
}
|
|
83
84
|
catch (error) {
|
|
85
|
+
this.logger.error('Failed to join conference subconversation', { conversationId, groupId, error, shouldRetry });
|
|
84
86
|
if (shouldRetry) {
|
|
85
87
|
return this.joinConferenceSubconversation(conversationId, groupId, false);
|
|
86
88
|
}
|
|
@@ -93,71 +95,132 @@ class SubconversationService extends commons_1.TypedEventEmitter {
|
|
|
93
95
|
* @param conversationId Id of the parent conversation which subconversation we want to leave
|
|
94
96
|
*/
|
|
95
97
|
async leaveConferenceSubconversation(conversationId) {
|
|
98
|
+
this.logger.info('Leaving conference subconversation', { conversationId });
|
|
96
99
|
const subconversationGroupId = await this.getSubconversationGroupId(conversationId, conversation_1.SUBCONVERSATION_ID.CONFERENCE);
|
|
97
100
|
if (!subconversationGroupId) {
|
|
101
|
+
this.logger.warn('No subconversation groupId found when leaving conference subconversation', { conversationId });
|
|
98
102
|
return;
|
|
99
103
|
}
|
|
100
104
|
const doesGroupExistLocally = await this.mlsService.conversationExists(subconversationGroupId);
|
|
101
105
|
if (!doesGroupExistLocally) {
|
|
102
106
|
// If the subconversation was known by a client but is does not exist locally, we can remove it from the store.
|
|
107
|
+
this.logger.info('Subconversation not found locally; clearing stored mapping', {
|
|
108
|
+
conversationId,
|
|
109
|
+
subconversationId: conversation_1.SUBCONVERSATION_ID.CONFERENCE,
|
|
110
|
+
subconversationGroupId,
|
|
111
|
+
});
|
|
103
112
|
return this.clearSubconversationGroupId(conversationId, conversation_1.SUBCONVERSATION_ID.CONFERENCE);
|
|
104
113
|
}
|
|
105
114
|
try {
|
|
106
115
|
await this.apiClient.api.conversation.deleteSubconversationSelf(conversationId, conversation_1.SUBCONVERSATION_ID.CONFERENCE);
|
|
107
116
|
}
|
|
108
117
|
catch (error) {
|
|
109
|
-
this.logger.error(
|
|
118
|
+
this.logger.error('Failed to leave conference subconversation', {
|
|
119
|
+
conversationId,
|
|
120
|
+
subconversationId: conversation_1.SUBCONVERSATION_ID.CONFERENCE,
|
|
121
|
+
subconversationGroupId,
|
|
122
|
+
error,
|
|
123
|
+
});
|
|
110
124
|
}
|
|
111
125
|
await this.mlsService.wipeConversation(subconversationGroupId);
|
|
112
126
|
// once we've left the subconversation, we can remove it from the store
|
|
127
|
+
this.logger.info('Clearing stored mapping after leaving conference subconversation', {
|
|
128
|
+
conversationId,
|
|
129
|
+
subconversationId: conversation_1.SUBCONVERSATION_ID.CONFERENCE,
|
|
130
|
+
subconversationGroupId,
|
|
131
|
+
});
|
|
113
132
|
await this.clearSubconversationGroupId(conversationId, conversation_1.SUBCONVERSATION_ID.CONFERENCE);
|
|
114
133
|
}
|
|
115
134
|
async leaveStaleConferenceSubconversations() {
|
|
135
|
+
this.logger.info('Leaving all stale conference subconversations');
|
|
116
136
|
const conversationIds = await this.getAllGroupIdsBySubconversationId(conversation_1.SUBCONVERSATION_ID.CONFERENCE);
|
|
117
137
|
for (const { parentConversationId } of conversationIds) {
|
|
138
|
+
this.logger.debug('Leaving stale conference subconversation for parent conversation', {
|
|
139
|
+
parentConversationId,
|
|
140
|
+
});
|
|
118
141
|
await this.leaveConferenceSubconversation(parentConversationId);
|
|
119
142
|
}
|
|
120
143
|
}
|
|
121
144
|
async getSubconversationEpochInfo(parentConversationId, parentConversationGroupId, shouldAdvanceEpoch = false) {
|
|
145
|
+
this.logger.info('Getting subconversation epoch info', {
|
|
146
|
+
parentConversationId,
|
|
147
|
+
parentConversationGroupId,
|
|
148
|
+
shouldAdvanceEpoch,
|
|
149
|
+
});
|
|
122
150
|
const subconversationGroupId = await this.getSubconversationGroupId(parentConversationId, conversation_1.SUBCONVERSATION_ID.CONFERENCE);
|
|
123
151
|
// this method should not be called if the subconversation (and its parent conversation) is not established
|
|
124
152
|
if (!subconversationGroupId) {
|
|
125
|
-
this.logger.error(
|
|
153
|
+
this.logger.error('Could not obtain epoch info for conference subconversation: missing groupId', {
|
|
154
|
+
parentConversationId,
|
|
155
|
+
});
|
|
126
156
|
return null;
|
|
127
157
|
}
|
|
128
158
|
//we don't want to react to avs callbacks when conversation was not yet established
|
|
129
159
|
const doesMLSGroupExist = await this.mlsService.conversationExists(subconversationGroupId);
|
|
130
160
|
if (!doesMLSGroupExist) {
|
|
161
|
+
this.logger.debug('Subconversation MLS group does not exist locally; skipping epoch info', {
|
|
162
|
+
parentConversationId,
|
|
163
|
+
parentConversationGroupId,
|
|
164
|
+
subconversationGroupId,
|
|
165
|
+
});
|
|
131
166
|
return null;
|
|
132
167
|
}
|
|
133
168
|
const members = await this.generateSubconversationMembers(subconversationGroupId, parentConversationGroupId);
|
|
134
169
|
if (shouldAdvanceEpoch) {
|
|
170
|
+
this.logger.info('Advancing epoch and renewing key material for subconversation', { subconversationGroupId });
|
|
135
171
|
await this.mlsService.renewKeyMaterial(subconversationGroupId);
|
|
136
172
|
}
|
|
137
173
|
const epoch = Number(await this.mlsService.getEpoch(subconversationGroupId));
|
|
138
174
|
const secretKey = await this.mlsService.exportSecretKey(subconversationGroupId, MLS_CONVERSATION_KEY_LENGTH);
|
|
175
|
+
this.logger.debug('Obtained subconversation epoch info', {
|
|
176
|
+
parentConversationId,
|
|
177
|
+
parentConversationGroupId,
|
|
178
|
+
subconversationGroupId,
|
|
179
|
+
epoch,
|
|
180
|
+
membersCount: members.length,
|
|
181
|
+
keyLength: MLS_CONVERSATION_KEY_LENGTH,
|
|
182
|
+
});
|
|
139
183
|
return { members, epoch, keyLength: MLS_CONVERSATION_KEY_LENGTH, secretKey };
|
|
140
184
|
}
|
|
141
185
|
async subscribeToEpochUpdates(parentConversationId, parentConversationGroupId, findConversationByGroupId, onEpochUpdate) {
|
|
186
|
+
this.logger.info('Subscribing to subconversation epoch updates', {
|
|
187
|
+
parentConversationId,
|
|
188
|
+
parentConversationGroupId,
|
|
189
|
+
});
|
|
142
190
|
const { epoch: initialEpoch, groupId: subconversationGroupId } = await this.joinConferenceSubconversation(parentConversationId, parentConversationGroupId);
|
|
143
191
|
const forwardNewEpoch = async ({ groupId }) => {
|
|
192
|
+
this.logger.debug('Received MLS NEW_EPOCH event', { eventGroupId: groupId, subconversationGroupId });
|
|
144
193
|
if (groupId !== subconversationGroupId) {
|
|
145
194
|
// if the epoch update did not happen in the subconversation directly, check if it happened in the parent conversation
|
|
146
195
|
const parentConversationId = findConversationByGroupId(groupId);
|
|
147
196
|
if (!parentConversationId) {
|
|
197
|
+
this.logger.debug('Ignoring NEW_EPOCH event: could not map to parent conversation');
|
|
148
198
|
return;
|
|
149
199
|
}
|
|
150
200
|
const foundSubconversationGroupId = await this.getSubconversationGroupId(parentConversationId, conversation_1.SUBCONVERSATION_ID.CONFERENCE);
|
|
151
201
|
// if the conference subconversation of parent conversation is not known, ignore the epoch update
|
|
152
202
|
if (foundSubconversationGroupId !== subconversationGroupId) {
|
|
203
|
+
this.logger.debug('Ignoring NEW_EPOCH event: not related to subscribed subconversation', {
|
|
204
|
+
eventGroupId: groupId,
|
|
205
|
+
foundSubconversationGroupId,
|
|
206
|
+
expectedSubconversationGroupId: subconversationGroupId,
|
|
207
|
+
});
|
|
153
208
|
return;
|
|
154
209
|
}
|
|
155
210
|
}
|
|
156
211
|
const subconversationEpochInfo = await this.getSubconversationEpochInfo(parentConversationId, parentConversationGroupId);
|
|
157
212
|
if (!subconversationEpochInfo) {
|
|
213
|
+
this.logger.debug('No subconversation epoch info available; skipping callback', {
|
|
214
|
+
parentConversationId,
|
|
215
|
+
parentConversationGroupId,
|
|
216
|
+
});
|
|
158
217
|
return;
|
|
159
218
|
}
|
|
160
219
|
const newSubconversationEpoch = Number(await this.mlsService.getEpoch(subconversationGroupId));
|
|
220
|
+
this.logger.info('Forwarding epoch update to subscriber', {
|
|
221
|
+
subconversationGroupId,
|
|
222
|
+
epoch: newSubconversationEpoch,
|
|
223
|
+
});
|
|
161
224
|
return onEpochUpdate({
|
|
162
225
|
...subconversationEpochInfo,
|
|
163
226
|
epoch: newSubconversationEpoch,
|
|
@@ -165,15 +228,26 @@ class SubconversationService extends commons_1.TypedEventEmitter {
|
|
|
165
228
|
};
|
|
166
229
|
this.mlsService.on(mls_1.MLSServiceEvents.NEW_EPOCH, forwardNewEpoch);
|
|
167
230
|
await forwardNewEpoch({ groupId: subconversationGroupId, epoch: initialEpoch });
|
|
231
|
+
this.logger.info('Subscribed to MLS NEW_EPOCH events for subconversation', { subconversationGroupId });
|
|
168
232
|
return () => this.mlsService.off(mls_1.MLSServiceEvents.NEW_EPOCH, forwardNewEpoch);
|
|
169
233
|
}
|
|
170
234
|
async removeClientFromConferenceSubconversation(conversationId, clientToRemove) {
|
|
235
|
+
this.logger.info('Removing client from conference subconversation', {
|
|
236
|
+
conversationId,
|
|
237
|
+
user: clientToRemove.user,
|
|
238
|
+
clientId: clientToRemove.clientId,
|
|
239
|
+
});
|
|
171
240
|
const subconversationGroupId = await this.getSubconversationGroupId(conversationId, conversation_1.SUBCONVERSATION_ID.CONFERENCE);
|
|
172
241
|
if (!subconversationGroupId) {
|
|
242
|
+
this.logger.warn('Cannot remove client: subconversation groupId missing', { conversationId });
|
|
173
243
|
return;
|
|
174
244
|
}
|
|
175
245
|
const doesMLSGroupExist = await this.mlsService.conversationExists(subconversationGroupId);
|
|
176
246
|
if (!doesMLSGroupExist) {
|
|
247
|
+
this.logger.debug('Cannot remove client: subconversation MLS group does not exist locally', {
|
|
248
|
+
conversationId,
|
|
249
|
+
subconversationGroupId,
|
|
250
|
+
});
|
|
177
251
|
return;
|
|
178
252
|
}
|
|
179
253
|
const { user: { id: userId, domain }, clientId, } = clientToRemove;
|
|
@@ -181,23 +255,70 @@ class SubconversationService extends commons_1.TypedEventEmitter {
|
|
|
181
255
|
const subconversationMembers = await this.mlsService.getClientIds(subconversationGroupId);
|
|
182
256
|
const isSubconversationMember = subconversationMembers.some(({ userId, clientId, domain }) => (0, fullyQualifiedClientIdUtils_1.constructFullyQualifiedClientId)(userId, clientId, domain) === clientToRemoveQualifiedId);
|
|
183
257
|
if (!isSubconversationMember) {
|
|
258
|
+
this.logger.info('Client is not a member of the subconversation; nothing to remove', {
|
|
259
|
+
conversationId,
|
|
260
|
+
subconversationGroupId,
|
|
261
|
+
clientToRemoveQualifiedId,
|
|
262
|
+
});
|
|
184
263
|
return;
|
|
185
264
|
}
|
|
186
|
-
|
|
265
|
+
this.logger.info('Removing client from subconversation', {
|
|
266
|
+
subconversationGroupId,
|
|
267
|
+
clientToRemoveQualifiedId,
|
|
268
|
+
});
|
|
269
|
+
try {
|
|
270
|
+
await this.mlsService.removeClientsFromConversation(subconversationGroupId, [clientToRemoveQualifiedId]);
|
|
271
|
+
}
|
|
272
|
+
catch (error) {
|
|
273
|
+
this.logger.error('Failed to remove client from subconversation', {
|
|
274
|
+
subconversationGroupId,
|
|
275
|
+
clientToRemoveQualifiedId,
|
|
276
|
+
error,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
187
279
|
}
|
|
188
280
|
async joinSubconversationByExternalCommit(conversationId, subconversation) {
|
|
189
|
-
|
|
281
|
+
try {
|
|
282
|
+
this.logger.info('Joining subconversation by external commit', { conversationId, subconversation });
|
|
283
|
+
await this.mlsService.joinByExternalCommit(() => this.apiClient.api.conversation.getSubconversationGroupInfo(conversationId, subconversation));
|
|
284
|
+
}
|
|
285
|
+
catch (error) {
|
|
286
|
+
this.logger.error('Failed to join subconversation by external commit', {
|
|
287
|
+
conversationId,
|
|
288
|
+
subconversation,
|
|
289
|
+
error,
|
|
290
|
+
});
|
|
291
|
+
throw error;
|
|
292
|
+
}
|
|
190
293
|
}
|
|
191
294
|
async getConferenceSubconversation(conversationId) {
|
|
192
|
-
|
|
295
|
+
this.logger.debug('Fetching conference subconversation metadata', { conversationId });
|
|
296
|
+
try {
|
|
297
|
+
return await this.apiClient.api.conversation.getSubconversation(conversationId, conversation_1.SUBCONVERSATION_ID.CONFERENCE);
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
this.logger.error('Failed to fetch conference subconversation metadata', { conversationId, error });
|
|
301
|
+
throw error;
|
|
302
|
+
}
|
|
193
303
|
}
|
|
194
304
|
async deleteConferenceSubconversation(conversationId, data) {
|
|
195
|
-
|
|
305
|
+
this.logger.info('Deleting conference subconversation', { conversationId, data });
|
|
306
|
+
try {
|
|
307
|
+
return await this.apiClient.api.conversation.deleteSubconversation(conversationId, conversation_1.SUBCONVERSATION_ID.CONFERENCE, data);
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
this.logger.error('Failed to delete conference subconversation', { conversationId, data, error });
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
196
313
|
}
|
|
197
314
|
async generateSubconversationMembers(subconversationGroupId, parentGroupId) {
|
|
315
|
+
this.logger.debug('Generating subconversation members info', {
|
|
316
|
+
subconversationGroupId,
|
|
317
|
+
parentGroupId,
|
|
318
|
+
});
|
|
198
319
|
const subconversationMemberIds = await this.mlsService.getClientIds(subconversationGroupId);
|
|
199
320
|
const parentMemberIds = await this.mlsService.getClientIds(parentGroupId);
|
|
200
|
-
|
|
321
|
+
const members = parentMemberIds.map(parentMember => {
|
|
201
322
|
const isSubconversationMember = subconversationMemberIds.some(({ userId, clientId, domain }) => (0, fullyQualifiedClientIdUtils_1.constructFullyQualifiedClientId)(userId, clientId, domain) ===
|
|
202
323
|
(0, fullyQualifiedClientIdUtils_1.constructFullyQualifiedClientId)(parentMember.userId, parentMember.clientId, parentMember.domain));
|
|
203
324
|
return {
|
|
@@ -208,20 +329,45 @@ class SubconversationService extends commons_1.TypedEventEmitter {
|
|
|
208
329
|
in_subconv: isSubconversationMember,
|
|
209
330
|
};
|
|
210
331
|
});
|
|
332
|
+
this.logger.debug('Generated subconversation members info', {
|
|
333
|
+
subconversationGroupId,
|
|
334
|
+
parentGroupId,
|
|
335
|
+
membersCount: members.length,
|
|
336
|
+
});
|
|
337
|
+
return members;
|
|
211
338
|
}
|
|
212
339
|
getSubconversationGroupId = async (parentConversationId, subconversationId) => {
|
|
213
340
|
const foundSubconversation = await this.coreDatabase.get('subconversations', (0, subconversationUtil_1.generateSubconversationStoreKey)(parentConversationId, subconversationId));
|
|
341
|
+
this.logger.debug('Loaded subconversation groupId from store', {
|
|
342
|
+
parentConversationId,
|
|
343
|
+
subconversationId,
|
|
344
|
+
found: Boolean(foundSubconversation?.groupId),
|
|
345
|
+
});
|
|
214
346
|
return foundSubconversation?.groupId;
|
|
215
347
|
};
|
|
216
348
|
getAllGroupIdsBySubconversationId = async (subconversationId) => {
|
|
349
|
+
this.logger.debug('Retrieving all subconversations by subconversationId', { subconversationId });
|
|
217
350
|
const allSubconversations = await this.coreDatabase.getAll('subconversations');
|
|
218
351
|
const foundSubconversations = allSubconversations.filter(subconversation => subconversation.subconversationId === subconversationId);
|
|
352
|
+
this.logger.debug('Found subconversations by id', {
|
|
353
|
+
subconversationId,
|
|
354
|
+
count: foundSubconversations.length,
|
|
355
|
+
});
|
|
219
356
|
return foundSubconversations;
|
|
220
357
|
};
|
|
221
358
|
saveSubconversationGroupId = async (parentConversationId, subconversationId, groupId) => {
|
|
359
|
+
this.logger.debug('Saving subconversation groupId mapping', {
|
|
360
|
+
parentConversationId,
|
|
361
|
+
subconversationId,
|
|
362
|
+
groupId,
|
|
363
|
+
});
|
|
222
364
|
return this.coreDatabase.put('subconversations', { parentConversationId, subconversationId, groupId }, (0, subconversationUtil_1.generateSubconversationStoreKey)(parentConversationId, subconversationId));
|
|
223
365
|
};
|
|
224
366
|
clearSubconversationGroupId = async (parentConversationId, subconversationId) => {
|
|
367
|
+
this.logger.debug('Clearing subconversation groupId mapping', {
|
|
368
|
+
parentConversationId,
|
|
369
|
+
subconversationId,
|
|
370
|
+
});
|
|
225
371
|
return this.coreDatabase.delete('subconversations', (0, subconversationUtil_1.generateSubconversationStoreKey)(parentConversationId, subconversationId));
|
|
226
372
|
};
|
|
227
373
|
}
|
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"./lib/cryptography/AssetCryptography/crypto.node": "./lib/cryptography/AssetCryptography/crypto.browser.js"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@wireapp/api-client": "^27.
|
|
14
|
+
"@wireapp/api-client": "^27.82.0",
|
|
15
15
|
"@wireapp/commons": "^5.4.5",
|
|
16
16
|
"@wireapp/core-crypto": "9.1.0",
|
|
17
17
|
"@wireapp/cryptobox": "12.8.0",
|
|
@@ -61,6 +61,6 @@
|
|
|
61
61
|
"test:coverage": "jest --coverage",
|
|
62
62
|
"watch": "tsc --watch"
|
|
63
63
|
},
|
|
64
|
-
"version": "46.
|
|
65
|
-
"gitHead": "
|
|
64
|
+
"version": "46.40.1",
|
|
65
|
+
"gitHead": "b1f2c021022374610797c31b73dfeb8a934b8f2d"
|
|
66
66
|
}
|