Skip to content

Commit e57a5da

Browse files
committed
feat: Send state event to timeline when sharing megolm session
1 parent 352b3fa commit e57a5da

File tree

5 files changed

+138
-83
lines changed

5 files changed

+138
-83
lines changed

lib/encryption/key_manager.dart

Lines changed: 124 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,43 @@ class KeyManager {
292292
return roomInboundGroupSessions[sessionId] = dbSess;
293293
}
294294

295+
void _sendEncryptionInfoEvent({
296+
required String roomId,
297+
required List<String> userIds,
298+
List<String>? deviceIds,
299+
}) async {
300+
await client.database?.transaction(() async {
301+
await client.handleSync(
302+
SyncUpdate(
303+
nextBatch: '',
304+
rooms: RoomsUpdate(
305+
join: {
306+
roomId: JoinedRoomUpdate(
307+
timeline: TimelineUpdate(
308+
events: [
309+
MatrixEvent(
310+
eventId:
311+
'fake_event_${client.generateUniqueTransactionId()}',
312+
content: {
313+
'body':
314+
'${userIds.join(', ')} can now read along${deviceIds != null ? ' on ${deviceIds.length} new device(s)' : ''}',
315+
if (deviceIds != null) 'devices': deviceIds,
316+
'users': userIds,
317+
},
318+
type: EventTypes.encryptionInfo,
319+
senderId: client.userID!,
320+
originServerTs: DateTime.now(),
321+
),
322+
],
323+
),
324+
),
325+
},
326+
),
327+
),
328+
);
329+
});
330+
}
331+
295332
Map<String, Map<String, bool>> _getDeviceKeyIdMap(
296333
List<DeviceKeys> deviceKeys,
297334
) {
@@ -327,21 +364,6 @@ class KeyManager {
327364
return true;
328365
}
329366

330-
if (!wipe) {
331-
// first check if it needs to be rotated
332-
final encryptionContent =
333-
room.getState(EventTypes.Encryption)?.parsedRoomEncryptionContent;
334-
final maxMessages = encryptionContent?.rotationPeriodMsgs ?? 100;
335-
final maxAge = encryptionContent?.rotationPeriodMs ??
336-
604800000; // default of one week
337-
if ((sess.sentMessages ?? maxMessages) >= maxMessages ||
338-
sess.creationTime
339-
.add(Duration(milliseconds: maxAge))
340-
.isBefore(DateTime.now())) {
341-
wipe = true;
342-
}
343-
}
344-
345367
final inboundSess = await loadInboundGroupSession(
346368
room.id,
347369
sess.outboundGroupSession!.session_id(),
@@ -352,81 +374,100 @@ class KeyManager {
352374
wipe = true;
353375
}
354376

355-
if (!wipe) {
356-
// next check if the devices in the room changed
357-
final devicesToReceive = <DeviceKeys>[];
358-
final newDeviceKeys = await room.getUserDeviceKeys();
359-
final newDeviceKeyIds = _getDeviceKeyIdMap(newDeviceKeys);
360-
// first check for user differences
361-
final oldUserIds = sess.devices.keys.toSet();
362-
final newUserIds = newDeviceKeyIds.keys.toSet();
363-
if (oldUserIds.difference(newUserIds).isNotEmpty) {
364-
// a user left the room, we must wipe the session
365-
wipe = true;
366-
} else {
367-
final newUsers = newUserIds.difference(oldUserIds);
368-
if (newUsers.isNotEmpty) {
369-
// new user! Gotta send the megolm session to them
370-
devicesToReceive
371-
.addAll(newDeviceKeys.where((d) => newUsers.contains(d.userId)));
377+
// next check if the devices in the room changed
378+
final devicesToReceive = <DeviceKeys>[];
379+
final newDeviceKeys = await room.getUserDeviceKeys();
380+
final newDeviceKeyIds = _getDeviceKeyIdMap(newDeviceKeys);
381+
// first check for user differences
382+
final oldUserIds = sess.devices.keys.toSet();
383+
final newUserIds = newDeviceKeyIds.keys.toSet();
384+
if (oldUserIds.difference(newUserIds).isNotEmpty) {
385+
// a user left the room, we must wipe the session
386+
wipe = true;
387+
} else {
388+
final newUsers = newUserIds.difference(oldUserIds);
389+
if (newUsers.isNotEmpty) {
390+
// new user! Gotta send the megolm session to them
391+
devicesToReceive
392+
.addAll(newDeviceKeys.where((d) => newUsers.contains(d.userId)));
393+
_sendEncryptionInfoEvent(roomId: roomId, userIds: newUsers.toList());
394+
}
395+
// okay, now we must test all the individual user devices, if anything new got blocked
396+
// or if we need to send to any new devices.
397+
// for this it is enough if we iterate over the old user Ids, as the new ones already have the needed keys in the list.
398+
// we also know that all the old user IDs appear in the old one, else we have already wiped the session
399+
for (final userId in oldUserIds) {
400+
final oldBlockedDevices = sess.devices.containsKey(userId)
401+
? sess.devices[userId]!.entries
402+
.where((e) => e.value)
403+
.map((e) => e.key)
404+
.toSet()
405+
: <String>{};
406+
final newBlockedDevices = newDeviceKeyIds.containsKey(userId)
407+
? newDeviceKeyIds[userId]!
408+
.entries
409+
.where((e) => e.value)
410+
.map((e) => e.key)
411+
.toSet()
412+
: <String>{};
413+
// we don't really care about old devices that got dropped (deleted), we only care if new ones got added and if new ones got blocked
414+
// check if new devices got blocked
415+
if (newBlockedDevices.difference(oldBlockedDevices).isNotEmpty) {
416+
wipe = true;
417+
}
418+
// and now add all the new devices!
419+
final oldDeviceIds = sess.devices.containsKey(userId)
420+
? sess.devices[userId]!.entries
421+
.where((e) => !e.value)
422+
.map((e) => e.key)
423+
.toSet()
424+
: <String>{};
425+
final newDeviceIds = newDeviceKeyIds.containsKey(userId)
426+
? newDeviceKeyIds[userId]!
427+
.entries
428+
.where((e) => !e.value)
429+
.map((e) => e.key)
430+
.toSet()
431+
: <String>{};
432+
433+
// check if a device got removed
434+
if (oldDeviceIds.difference(newDeviceIds).isNotEmpty) {
435+
wipe = true;
372436
}
373-
// okay, now we must test all the individual user devices, if anything new got blocked
374-
// or if we need to send to any new devices.
375-
// for this it is enough if we iterate over the old user Ids, as the new ones already have the needed keys in the list.
376-
// we also know that all the old user IDs appear in the old one, else we have already wiped the session
377-
for (final userId in oldUserIds) {
378-
final oldBlockedDevices = sess.devices.containsKey(userId)
379-
? sess.devices[userId]!.entries
380-
.where((e) => e.value)
381-
.map((e) => e.key)
382-
.toSet()
383-
: <String>{};
384-
final newBlockedDevices = newDeviceKeyIds.containsKey(userId)
385-
? newDeviceKeyIds[userId]!
386-
.entries
387-
.where((e) => e.value)
388-
.map((e) => e.key)
389-
.toSet()
390-
: <String>{};
391-
// we don't really care about old devices that got dropped (deleted), we only care if new ones got added and if new ones got blocked
392-
// check if new devices got blocked
393-
if (newBlockedDevices.difference(oldBlockedDevices).isNotEmpty) {
394-
wipe = true;
395-
break;
396-
}
397-
// and now add all the new devices!
398-
final oldDeviceIds = sess.devices.containsKey(userId)
399-
? sess.devices[userId]!.entries
400-
.where((e) => !e.value)
401-
.map((e) => e.key)
402-
.toSet()
403-
: <String>{};
404-
final newDeviceIds = newDeviceKeyIds.containsKey(userId)
405-
? newDeviceKeyIds[userId]!
406-
.entries
407-
.where((e) => !e.value)
408-
.map((e) => e.key)
409-
.toSet()
410-
: <String>{};
411-
412-
// check if a device got removed
413-
if (oldDeviceIds.difference(newDeviceIds).isNotEmpty) {
414-
wipe = true;
415-
break;
416-
}
417437

418-
// check if any new devices need keys
419-
final newDevices = newDeviceIds.difference(oldDeviceIds);
420-
if (newDeviceIds.isNotEmpty) {
421-
devicesToReceive.addAll(
422-
newDeviceKeys.where(
423-
(d) => d.userId == userId && newDevices.contains(d.deviceId),
424-
),
438+
// check if any new devices need keys
439+
final newDevices = newDeviceIds.difference(oldDeviceIds);
440+
if (newDeviceIds.isNotEmpty) {
441+
devicesToReceive.addAll(
442+
newDeviceKeys.where(
443+
(d) => d.userId == userId && newDevices.contains(d.deviceId),
444+
),
445+
);
446+
if (userId != client.userID && newDevices.isNotEmpty) {
447+
_sendEncryptionInfoEvent(
448+
roomId: roomId,
449+
userIds: [userId],
450+
deviceIds: newDevices.toList(),
425451
);
426452
}
427453
}
428454
}
429455

456+
if (!wipe) {
457+
// first check if it needs to be rotated
458+
final encryptionContent =
459+
room.getState(EventTypes.Encryption)?.parsedRoomEncryptionContent;
460+
final maxMessages = encryptionContent?.rotationPeriodMsgs ?? 100;
461+
final maxAge = encryptionContent?.rotationPeriodMs ??
462+
604800000; // default of one week
463+
if ((sess.sentMessages ?? maxMessages) >= maxMessages ||
464+
sess.creationTime
465+
.add(Duration(milliseconds: maxAge))
466+
.isBefore(DateTime.now())) {
467+
wipe = true;
468+
}
469+
}
470+
430471
if (!wipe) {
431472
if (!use) {
432473
return false;

lib/matrix_api_lite/model/event_types.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,7 @@ abstract class EventTypes {
114114
static const String GroupCallMemberReplaces = '$GroupCallMember.replaces';
115115
static const String GroupCallMemberAssertedIdentity =
116116
'$GroupCallMember.asserted_identity';
117+
118+
// Internal
119+
static const String encryptionInfo = 'sdk.dart.matrix.new_megolm_session';
117120
}

lib/src/utils/event_localizations.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,8 @@ abstract class EventLocalizations {
119119
EventTypes.Sticker: (event, i18n, body) => i18n.sentASticker(
120120
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),
121121
),
122+
'sdk.dart.matrix.new_megolm_session': (event, i18n, body) =>
123+
i18n.userCanNowReadAlong(event),
122124
EventTypes.Redaction: (event, i18n, body) => i18n.redactedAnEvent(event),
123125
EventTypes.RoomAliases: (event, i18n, body) => i18n.changedTheRoomAliases(
124126
event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n),

lib/src/utils/matrix_default_localizations.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,4 +310,11 @@ class MatrixDefaultLocalizations extends MatrixLocalizations {
310310
@override
311311
String startedKeyVerification(String senderName) =>
312312
'$senderName started key verification';
313+
314+
@override
315+
String userCanNowReadAlong(Event event) {
316+
final users = event.content.tryGetList<String>('users') ?? [unknownUser];
317+
final deviceCount = event.content.tryGetList<String>('devices')?.length;
318+
return '${users.join(', ')} can now read along${deviceCount == null ? '' : ' on $deviceCount new device(s)'}';
319+
}
313320
}

lib/src/utils/matrix_localizations.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ abstract class MatrixLocalizations {
8686

8787
String redactedAnEvent(Event redactedEvent);
8888

89+
String userCanNowReadAlong(Event event);
90+
8991
String changedTheRoomAliases(String senderName);
9092

9193
String changedTheRoomInvitationLink(String senderName);

0 commit comments

Comments
 (0)