LCOV - code coverage report
Current view: top level - service - notification_service_impl.dart (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 0 122 0.0 %
Date: 2022-01-19 17:54:05 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:async';
       2             : import 'dart:convert';
       3             : 
       4             : import 'package:at_client/at_client.dart';
       5             : import 'package:at_client/src/exception/at_client_exception_util.dart';
       6             : import 'package:at_client/src/listener/at_sign_change_listener.dart';
       7             : import 'package:at_client/src/listener/switch_at_sign_event.dart';
       8             : import 'package:at_client/src/manager/monitor.dart';
       9             : import 'package:at_client/src/preference/monitor_preference.dart';
      10             : import 'package:at_client/src/response/notification_response_parser.dart';
      11             : import 'package:at_client/src/service/notification_service.dart';
      12             : import 'package:at_commons/at_commons.dart';
      13             : import 'package:at_utils/at_logger.dart';
      14             : 
      15             : class NotificationServiceImpl
      16             :     implements NotificationService, AtSignChangeListener {
      17             :   Map<String, StreamController> streamListeners = {};
      18             :   final emptyRegex = '';
      19             :   static const notificationIdKey = '_latestNotificationId';
      20           0 :   static final Map<String, NotificationService> _notificationServiceMap = {};
      21             : 
      22             :   final _logger = AtSignLogger('NotificationServiceImpl');
      23             :   var _isMonitorPaused = false;
      24             :   late AtClient _atClient;
      25             :   Monitor? _monitor;
      26             :   ConnectivityListener? _connectivityListener;
      27             :   dynamic _lastMonitorRetried;
      28             : 
      29           0 :   static Future<NotificationService> create(AtClient atClient) async {
      30           0 :     if (_notificationServiceMap.containsKey(atClient.getCurrentAtSign())) {
      31           0 :       return _notificationServiceMap[atClient.getCurrentAtSign()]!;
      32             :     }
      33           0 :     final notificationService = NotificationServiceImpl._(atClient);
      34           0 :     await notificationService._init();
      35           0 :     _notificationServiceMap[atClient.getCurrentAtSign()!] = notificationService;
      36           0 :     return _notificationServiceMap[atClient.getCurrentAtSign()]!;
      37             :   }
      38             : 
      39           0 :   NotificationServiceImpl._(AtClient atClient) {
      40           0 :     _atClient = atClient;
      41             :   }
      42             : 
      43           0 :   Future<void> _init() async {
      44           0 :     _logger.finer('notification init starting monitor');
      45           0 :     await _startMonitor();
      46           0 :     if (_connectivityListener == null) {
      47           0 :       _connectivityListener = ConnectivityListener();
      48           0 :       _connectivityListener!.subscribe().listen((isConnected) {
      49             :         if (isConnected) {
      50           0 :           _logger.finer('starting monitor through connectivity listener event');
      51           0 :           _startMonitor();
      52             :         } else {
      53           0 :           _logger.finer('lost network connectivity');
      54             :         }
      55             :       });
      56             :     }
      57             :   }
      58             : 
      59           0 :   Future<void> _startMonitor() async {
      60           0 :     if (_monitor != null && _monitor!.status == MonitorStatus.started) {
      61           0 :       _logger.finer(
      62           0 :           'monitor is already started for ${_atClient.getCurrentAtSign()}');
      63             :       return;
      64             :     }
      65           0 :     final lastNotificationTime = await _getLastNotificationTime();
      66           0 :     _monitor = Monitor(
      67           0 :         _internalNotificationCallback,
      68           0 :         _onMonitorError,
      69           0 :         _atClient.getCurrentAtSign()!,
      70           0 :         _atClient.getPreferences()!,
      71           0 :         MonitorPreference()..keepAlive = true,
      72           0 :         _monitorRetry);
      73           0 :     await _monitor!.start(lastNotificationTime: lastNotificationTime);
      74           0 :     if (_monitor!.status == MonitorStatus.started) {
      75           0 :       _isMonitorPaused = false;
      76             :     }
      77             :   }
      78             : 
      79           0 :   Future<int?> _getLastNotificationTime() async {
      80           0 :     var atKey = AtKey()..key = notificationIdKey;
      81           0 :     if (_atClient.getLocalSecondary()!.keyStore!.isKeyExists(atKey.key!)) {
      82           0 :       final atValue = await _atClient.get(atKey);
      83           0 :       if (atValue.value != null) {
      84           0 :         _logger.finer('json from hive: ${atValue.value}');
      85           0 :         return jsonDecode(atValue.value)['epochMillis'];
      86             :       }
      87             :     }
      88             :     return null;
      89             :   }
      90             : 
      91           0 :   @override
      92             :   void stopAllSubscriptions() {
      93           0 :     _isMonitorPaused = true;
      94           0 :     _monitor?.stop();
      95           0 :     _connectivityListener?.unSubscribe();
      96           0 :     streamListeners.forEach((regex, streamController) {
      97           0 :       if (!streamController.isClosed) () => streamController.close();
      98             :     });
      99           0 :     streamListeners.clear();
     100             :   }
     101             : 
     102           0 :   void _internalNotificationCallback(String notificationJSON) async {
     103             :     try {
     104           0 :       final notificationParser = NotificationResponseParser();
     105             :       final atNotifications = notificationParser
     106           0 :           .getAtNotifications(notificationParser.parse(notificationJSON));
     107           0 :       for (var atNotification in atNotifications) {
     108             :         // Saves latest notification id to the keys if its not a stats notification.
     109           0 :         if (atNotification.id != '-1') {
     110           0 :           await _atClient.put(AtKey()..key = notificationIdKey,
     111           0 :               jsonEncode(atNotification.toJson()));
     112             :         }
     113           0 :         streamListeners.forEach((regex, streamController) {
     114           0 :           if (regex != emptyRegex) {
     115           0 :             if (regex.allMatches(atNotification.key).isNotEmpty) {
     116           0 :               streamController.add(atNotification);
     117             :             }
     118             :           } else {
     119           0 :             streamController.add(atNotification);
     120             :           }
     121             :         });
     122             :       }
     123           0 :     } on Exception catch (e) {
     124           0 :       _logger.severe(
     125           0 :           'exception processing: error:${e.toString()} notificationJson: $notificationJSON');
     126             :     }
     127             :   }
     128             : 
     129           0 :   void _monitorRetry() {
     130           0 :     if (_lastMonitorRetried != null &&
     131           0 :         DateTime.now().toUtc().difference(_lastMonitorRetried).inSeconds < 15) {
     132           0 :       _logger.info('Attempting to retry in less than 15 seconds... Rejected');
     133             :       return;
     134             :     }
     135           0 :     if (_isMonitorPaused) {
     136           0 :       _logger.finer('monitor is paused. not retrying');
     137             :       return;
     138             :     }
     139           0 :     _lastMonitorRetried = DateTime.now().toUtc();
     140           0 :     _logger.finer('monitor retry for ${_atClient.getCurrentAtSign()}');
     141           0 :     Future.delayed(
     142           0 :         Duration(seconds: 15),
     143           0 :         () async => _monitor!
     144           0 :             .start(lastNotificationTime: await _getLastNotificationTime()));
     145             :   }
     146             : 
     147           0 :   void _onMonitorError(Exception e) {
     148           0 :     _logger.severe('internal error in monitor: ${e.toString()}');
     149             :   }
     150             : 
     151             :   @override
     152           0 :   Future<NotificationResult> notify(NotificationParams notificationParams,
     153             :       {Function? onSuccess, Function? onError}) async {
     154           0 :     var notificationResult = NotificationResult()
     155           0 :       ..atKey = notificationParams.atKey;
     156             :     dynamic notificationId;
     157             :     try {
     158             :       // Notifies key to another notificationParams.atKey.sharedWith atsign
     159             :       // Returns the notificationId.
     160           0 :       notificationId = await _atClient.notifyChange(notificationParams);
     161           0 :     } on Exception catch (e) {
     162             :       // Setting notificationStatusEnum to errored
     163           0 :       notificationResult.notificationStatusEnum =
     164             :           NotificationStatusEnum.undelivered;
     165           0 :       var errorCode = AtClientExceptionUtil.getErrorCode(e);
     166           0 :       var atClientException = AtClientException(
     167           0 :           errorCode, AtClientExceptionUtil.getErrorDescription(errorCode));
     168           0 :       notificationResult.atClientException = atClientException;
     169             :       // Invoke onErrorCallback
     170             :       if (onError != null) {
     171           0 :         onError(notificationResult);
     172             :       }
     173             :       return notificationResult;
     174             :     }
     175           0 :     var notificationParser = NotificationResponseParser();
     176           0 :     notificationResult.notificationID =
     177           0 :         notificationParser.parse(notificationId).response;
     178             :     // Gets the notification status and parse the response.
     179           0 :     var notificationStatus = notificationParser.parse(
     180           0 :         await _getFinalNotificationStatus(notificationResult.notificationID!));
     181           0 :     switch (notificationStatus.response) {
     182           0 :       case 'delivered':
     183           0 :         notificationResult.notificationStatusEnum =
     184             :             NotificationStatusEnum.delivered;
     185             :         // If onSuccess callback is registered, invoke callback method.
     186             :         if (onSuccess != null) {
     187           0 :           onSuccess(notificationResult);
     188             :         }
     189             :         break;
     190           0 :       case 'undelivered':
     191           0 :         notificationResult.notificationStatusEnum =
     192             :             NotificationStatusEnum.undelivered;
     193           0 :         notificationResult.atClientException = AtClientException(
     194           0 :             error_codes['SecondaryConnectException'],
     195           0 :             error_description[error_codes['SecondaryConnectException']]);
     196             :         // If onError callback is registered, invoke callback method.
     197             :         if (onError != null) {
     198           0 :           onError(notificationResult);
     199             :         }
     200             :         break;
     201             :     }
     202             :     return notificationResult;
     203             :   }
     204             : 
     205             :   /// Queries the status of the notification
     206             :   /// Takes the notificationId as input as returns the status of the notification
     207           0 :   Future<String> _getFinalNotificationStatus(String notificationId) async {
     208             :     String status = '';
     209             :     // For every 2 seconds, queries the status of the notification
     210           0 :     while (status.isEmpty || status == 'data:queued') {
     211           0 :       await Future.delayed(Duration(seconds: 2),
     212           0 :           () async => status = await _atClient.notifyStatus(notificationId));
     213             :     }
     214             :     return status;
     215             :   }
     216             : 
     217           0 :   @override
     218             :   Stream<AtNotification> subscribe({String? regex}) {
     219           0 :     regex ??= emptyRegex;
     220           0 :     if (streamListeners.containsKey(regex)) {
     221           0 :       _logger.finer('subscription already exists');
     222           0 :       return streamListeners[regex]!.stream as Stream<AtNotification>;
     223             :     }
     224           0 :     final _controller = StreamController<AtNotification>.broadcast();
     225           0 :     streamListeners[regex] = _controller;
     226           0 :     _logger.finer('added regex to listener $regex');
     227           0 :     return _controller.stream;
     228             :   }
     229             : 
     230           0 :   @override
     231             :   void listenToAtSignChange(SwitchAtSignEvent switchAtSignEvent) {
     232           0 :     if (switchAtSignEvent.previousAtClient?.getCurrentAtSign() ==
     233           0 :         _atClient.getCurrentAtSign()) {
     234             :       // actions for previous atSign
     235           0 :       _logger.finer(
     236           0 :           'stopping notification listeners for ${_atClient.getCurrentAtSign()}');
     237           0 :       stopAllSubscriptions();
     238             :     }
     239             :   }
     240             : }

Generated by: LCOV version 1.13