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: 42 82 51.2 %
Date: 2024-07-12 20:20:16 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) async {
      84           0 :     final ownMemberships = getCallMembershipsForUser(client.userID!);
      85             : 
      86             :     // do not bother removing other deviceId expired events because we have no
      87             :     // ownership over them
      88             :     ownMemberships
      89           0 :         .removeWhere((element) => client.deviceID! == element.deviceId);
      90             : 
      91           0 :     ownMemberships.removeWhere((e) => e == callMembership);
      92             : 
      93           0 :     ownMemberships.add(callMembership);
      94             : 
      95           0 :     final newContent = {
      96           0 :       'memberships': List.from(ownMemberships.map((e) => e.toJson()))
      97             :     };
      98             : 
      99           0 :     await setFamedlyCallMemberEvent(newContent);
     100             :   }
     101             : 
     102           0 :   Future<void> removeFamedlyCallMemberEvent(
     103             :     String groupCallId,
     104             :     String deviceId, {
     105             :     String? application = 'm.call',
     106             :     String? scope = 'm.room',
     107             :   }) async {
     108           0 :     final ownMemberships = getCallMembershipsForUser(client.userID!);
     109             : 
     110           0 :     ownMemberships.removeWhere((mem) =>
     111           0 :         mem.callId == groupCallId &&
     112           0 :         mem.deviceId == deviceId &&
     113           0 :         mem.application == application &&
     114           0 :         mem.scope == scope);
     115             : 
     116           0 :     final newContent = {
     117           0 :       'memberships': List.from(ownMemberships.map((e) => e.toJson()))
     118             :     };
     119           0 :     await setFamedlyCallMemberEvent(newContent);
     120             :   }
     121             : 
     122           0 :   Future<void> setFamedlyCallMemberEvent(Map<String, List> newContent) async {
     123           0 :     if (canJoinGroupCall) {
     124           0 :       await client.setRoomStateWithKey(
     125           0 :         id,
     126             :         EventTypes.GroupCallMember,
     127           0 :         client.userID!,
     128             :         newContent,
     129             :       );
     130             :     } else {
     131           0 :       throw MatrixSDKVoipException(
     132             :         '''
     133           0 :         User ${client.userID}:${client.deviceID} is not allowed to join famedly calls in room $id, 
     134           0 :         canJoinGroupCall: $canJoinGroupCall, 
     135           0 :         groupCallsEnabledForEveryone: $groupCallsEnabledForEveryone, 
     136           0 :         needed: ${powerForChangingStateEvent(EventTypes.GroupCallMember)}, 
     137           0 :         own: $ownPowerLevel}
     138           0 :         plMap: ${getState(EventTypes.RoomPowerLevels)?.content}
     139           0 :         ''',
     140             :       );
     141             :     }
     142             :   }
     143             : 
     144             :   /// returns a list of memberships from a famedly call matrix event
     145           2 :   List<CallMembership> getCallMembershipsFromEvent(MatrixEvent event) {
     146           6 :     if (event.roomId != id) return [];
     147           2 :     return getCallMembershipsFromEventContent(
     148           6 :         event.content, event.senderId, event.roomId!);
     149             :   }
     150             : 
     151             :   /// returns a list of memberships from a famedly call matrix event
     152           2 :   List<CallMembership> getCallMembershipsFromEventContent(
     153             :       Map<String, Object?> content, String senderId, String roomId) {
     154           2 :     final mems = content.tryGetList<Map>('memberships');
     155           2 :     final callMems = <CallMembership>[];
     156           4 :     for (final m in mems ?? []) {
     157           2 :       final mem = CallMembership.fromJson(m, senderId, roomId);
     158           2 :       if (mem != null) callMems.add(mem);
     159             :     }
     160             :     return callMems;
     161             :   }
     162             : }
     163             : 
     164           2 : bool isValidMemEvent(Map<String, Object?> event) {
     165           4 :   if (event['call_id'] is String &&
     166           4 :       event['device_id'] is String &&
     167           4 :       event['expires_ts'] is num &&
     168           4 :       event['foci_active'] is List) {
     169             :     return true;
     170             :   } else {
     171           0 :     Logs()
     172           0 :         .v('[VOIP] FamedlyCallMemberEvent ignoring unclean membership $event');
     173             :     return false;
     174             :   }
     175             : }
     176             : 
     177             : class MatrixSDKVoipException implements Exception {
     178             :   final String cause;
     179             :   final StackTrace? stackTrace;
     180             : 
     181           0 :   MatrixSDKVoipException(this.cause, {this.stackTrace});
     182             : 
     183           0 :   @override
     184           0 :   String toString() => '[VOIP] $cause, ${super.toString()}, $stackTrace';
     185             : }

Generated by: LCOV version 1.14