LCOV - code coverage report
Current view: top level - lib/src/voip/utils - famedly_call_extension.dart (source / functions) Coverage Total Hit
Test: merged.info Lines: 51.8 % 85 44
Test Date: 2025-01-14 13:39:53 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 2.0-1