LCOV - code coverage report
Current view: top level - lib/src/voip/utils - famedly_call_extension.dart (source / functions) Hit Total Coverage
Test: merged.info Lines: 44 85 51.8 %
Date: 2024-11-12 07:37:08 Functions: 0 0 -

          Line data    Source code
       1             : import 'package:collection/collection.dart';
       2             : 
       3             : import 'package:matrix/matrix.dart';
       4             : import 'package:matrix/src/voip/models/call_membership.dart';
       5             : 
       6             : extension FamedlyCallMemberEventsExtension on Room {
       7             :   /// a map of every users famedly call event, holds the memberships list
       8             :   /// returns sorted according to originTs (oldest to newest)
       9           2 :   Map<String, FamedlyCallMemberEvent> getFamedlyCallEvents() {
      10           2 :     final Map<String, FamedlyCallMemberEvent> mappedEvents = {};
      11             :     final famedlyCallMemberStates =
      12           4 :         states.tryGetMap<String, Event>(EventTypes.GroupCallMember);
      13             : 
      14           2 :     if (famedlyCallMemberStates == null) return {};
      15           2 :     final sortedEvents = famedlyCallMemberStates.values
      16          10 :         .sorted((a, b) => a.originServerTs.compareTo(b.originServerTs));
      17             : 
      18           4 :     for (final element in sortedEvents) {
      19             :       mappedEvents
      20           8 :           .addAll({element.senderId: FamedlyCallMemberEvent.fromJson(element)});
      21             :     }
      22             :     return mappedEvents;
      23             :   }
      24             : 
      25             :   /// extracts memberships list form a famedly call event and maps it to a userid
      26             :   /// returns sorted (oldest to newest)
      27           2 :   Map<String, List<CallMembership>> getCallMembershipsFromRoom() {
      28           2 :     final parsedMemberEvents = getFamedlyCallEvents();
      29           2 :     final Map<String, List<CallMembership>> memberships = {};
      30           4 :     for (final element in parsedMemberEvents.entries) {
      31          10 :       memberships.addAll({element.key: element.value.memberships});
      32             :     }
      33             :     return memberships;
      34             :   }
      35             : 
      36             :   /// returns a list of memberships in the room for `user`
      37           0 :   List<CallMembership> getCallMembershipsForUser(String userId) {
      38           0 :     final parsedMemberEvents = getCallMembershipsFromRoom();
      39           0 :     final mem = parsedMemberEvents.tryGet<List<CallMembership>>(userId);
      40           0 :     return mem ?? [];
      41             :   }
      42             : 
      43             :   /// returns the user count (not sessions, yet) for the group call with id: `groupCallId`.
      44             :   /// returns 0 if group call not found
      45           2 :   int groupCallParticipantCount(String groupCallId) {
      46             :     int participantCount = 0;
      47             :     // userid:membership
      48           2 :     final memberships = getCallMembershipsFromRoom();
      49             : 
      50           4 :     memberships.forEach((key, value) {
      51           4 :       for (final membership in value) {
      52           6 :         if (membership.callId == groupCallId && !membership.isExpired) {
      53           2 :           participantCount++;
      54             :         }
      55             :       }
      56             :     });
      57             : 
      58             :     return participantCount;
      59             :   }
      60             : 
      61           2 :   bool get hasActiveGroupCall {
      62           4 :     if (activeGroupCallIds.isNotEmpty) {
      63             :       return true;
      64             :     }
      65             :     return false;
      66             :   }
      67             : 
      68             :   /// list of active group call ids
      69           2 :   List<String> get activeGroupCallIds {
      70             :     final Set<String> ids = {};
      71           2 :     final memberships = getCallMembershipsFromRoom();
      72             : 
      73           4 :     memberships.forEach((key, value) {
      74           4 :       for (final mem in value) {
      75           6 :         if (!mem.isExpired) ids.add(mem.callId);
      76             :       }
      77             :     });
      78           2 :     return ids.toList();
      79             :   }
      80             : 
      81             :   /// passing no `CallMembership` removes it from the state event.
      82           0 :   Future<void> updateFamedlyCallMemberStateEvent(
      83             :     CallMembership callMembership,
      84             :   ) async {
      85           0 :     final ownMemberships = getCallMembershipsForUser(client.userID!);
      86             : 
      87             :     // do not bother removing other deviceId expired events because we have no
      88             :     // ownership over them
      89             :     ownMemberships
      90           0 :         .removeWhere((element) => client.deviceID! == element.deviceId);
      91             : 
      92           0 :     ownMemberships.removeWhere((e) => e == callMembership);
      93             : 
      94           0 :     ownMemberships.add(callMembership);
      95             : 
      96           0 :     final newContent = {
      97           0 :       'memberships': List.from(ownMemberships.map((e) => e.toJson())),
      98             :     };
      99             : 
     100           0 :     await setFamedlyCallMemberEvent(newContent);
     101             :   }
     102             : 
     103           0 :   Future<void> removeFamedlyCallMemberEvent(
     104             :     String groupCallId,
     105             :     String deviceId, {
     106             :     String? application = 'm.call',
     107             :     String? scope = 'm.room',
     108             :   }) async {
     109           0 :     final ownMemberships = getCallMembershipsForUser(client.userID!);
     110             : 
     111           0 :     ownMemberships.removeWhere(
     112           0 :       (mem) =>
     113           0 :           mem.callId == groupCallId &&
     114           0 :           mem.deviceId == deviceId &&
     115           0 :           mem.application == application &&
     116           0 :           mem.scope == scope,
     117             :     );
     118             : 
     119           0 :     final newContent = {
     120           0 :       'memberships': List.from(ownMemberships.map((e) => e.toJson())),
     121             :     };
     122           0 :     await setFamedlyCallMemberEvent(newContent);
     123             :   }
     124             : 
     125           0 :   Future<void> setFamedlyCallMemberEvent(Map<String, List> newContent) async {
     126           0 :     if (canJoinGroupCall) {
     127           0 :       await client.setRoomStateWithKey(
     128           0 :         id,
     129             :         EventTypes.GroupCallMember,
     130           0 :         client.userID!,
     131             :         newContent,
     132             :       );
     133             :     } else {
     134           0 :       throw MatrixSDKVoipException(
     135             :         '''
     136           0 :         User ${client.userID}:${client.deviceID} is not allowed to join famedly calls in room $id, 
     137           0 :         canJoinGroupCall: $canJoinGroupCall, 
     138           0 :         groupCallsEnabledForEveryone: $groupCallsEnabledForEveryone, 
     139           0 :         needed: ${powerForChangingStateEvent(EventTypes.GroupCallMember)}, 
     140           0 :         own: $ownPowerLevel}
     141           0 :         plMap: ${getState(EventTypes.RoomPowerLevels)?.content}
     142           0 :         ''',
     143             :       );
     144             :     }
     145             :   }
     146             : 
     147             :   /// returns a list of memberships from a famedly call matrix event
     148           2 :   List<CallMembership> getCallMembershipsFromEvent(MatrixEvent event) {
     149           6 :     if (event.roomId != id) return [];
     150           2 :     return getCallMembershipsFromEventContent(
     151           2 :       event.content,
     152           2 :       event.senderId,
     153           2 :       event.roomId!,
     154             :     );
     155             :   }
     156             : 
     157             :   /// returns a list of memberships from a famedly call matrix event
     158           2 :   List<CallMembership> getCallMembershipsFromEventContent(
     159             :     Map<String, Object?> content,
     160             :     String senderId,
     161             :     String roomId,
     162             :   ) {
     163           2 :     final mems = content.tryGetList<Map>('memberships');
     164           2 :     final callMems = <CallMembership>[];
     165           4 :     for (final m in mems ?? []) {
     166           2 :       final mem = CallMembership.fromJson(m, senderId, roomId);
     167           2 :       if (mem != null) callMems.add(mem);
     168             :     }
     169             :     return callMems;
     170             :   }
     171             : }
     172             : 
     173           2 : bool isValidMemEvent(Map<String, Object?> event) {
     174           4 :   if (event['call_id'] is String &&
     175           4 :       event['device_id'] is String &&
     176           4 :       event['expires_ts'] is num &&
     177           4 :       event['foci_active'] is List) {
     178             :     return true;
     179             :   } else {
     180           0 :     Logs()
     181           0 :         .v('[VOIP] FamedlyCallMemberEvent ignoring unclean membership $event');
     182             :     return false;
     183             :   }
     184             : }
     185             : 
     186             : class MatrixSDKVoipException implements Exception {
     187             :   final String cause;
     188             :   final StackTrace? stackTrace;
     189             : 
     190           0 :   MatrixSDKVoipException(this.cause, {this.stackTrace});
     191             : 
     192           0 :   @override
     193           0 :   String toString() => '[VOIP] $cause, ${super.toString()}, $stackTrace';
     194             : }

Generated by: LCOV version 1.14