LCOV - code coverage report
Current view: top level - client - at_client_impl.dart (source / functions) Hit Total Coverage
Test: coverage.lcov Lines: 27 565 4.8 %
Date: 2022-01-19 17:54:05 Functions: 0 0 -

          Line data    Source code
       1             : import 'dart:async';
       2             : import 'dart:convert';
       3             : import 'dart:io';
       4             : 
       5             : import 'package:at_base2e15/at_base2e15.dart';
       6             : import 'package:at_client/at_client.dart';
       7             : import 'package:at_client/src/client/secondary.dart';
       8             : import 'package:at_client/src/exception/at_client_error_codes.dart';
       9             : import 'package:at_client/src/exception/at_client_exception_util.dart';
      10             : import 'package:at_client/src/manager/storage_manager.dart';
      11             : import 'package:at_client/src/manager/sync_manager.dart';
      12             : import 'package:at_client/src/manager/sync_manager_impl.dart';
      13             : import 'package:at_client/src/response/json_utils.dart';
      14             : import 'package:at_client/src/service/encryption_service.dart';
      15             : import 'package:at_client/src/service/file_transfer_service.dart';
      16             : import 'package:at_client/src/service/notification_service.dart';
      17             : import 'package:at_client/src/stream/at_stream_notification.dart';
      18             : import 'package:at_client/src/stream/at_stream_response.dart';
      19             : import 'package:at_client/src/stream/file_transfer_object.dart';
      20             : import 'package:at_client/src/stream/stream_notification_handler.dart';
      21             : import 'package:at_client/src/util/at_client_validation.dart';
      22             : import 'package:at_client/src/util/constants.dart';
      23             : import 'package:at_client/src/util/network_util.dart';
      24             : import 'package:at_client/src/util/sync_util.dart';
      25             : import 'package:at_commons/at_builders.dart';
      26             : import 'package:at_commons/at_commons.dart';
      27             : import 'package:at_lookup/at_lookup.dart';
      28             : import 'package:at_persistence_secondary_server/at_persistence_secondary_server.dart';
      29             : import 'package:at_utils/at_utils.dart';
      30             : import 'package:http/http.dart' as http;
      31             : import 'package:path/path.dart';
      32             : import 'package:uuid/uuid.dart';
      33             : 
      34             : /// Implementation of [AtClient] interface
      35             : class AtClientImpl implements AtClient {
      36             :   AtClientPreference? _preference;
      37             : 
      38           0 :   AtClientPreference? get preference => _preference;
      39             :   String? currentAtSign;
      40             :   String? _namespace;
      41             :   LocalSecondary? _localSecondary;
      42             :   RemoteSecondary? _remoteSecondary;
      43             : 
      44             :   EncryptionService? _encryptionService;
      45             : 
      46           0 :   @override
      47           0 :   EncryptionService? get encryptionService => _encryptionService;
      48             : 
      49             :   AtClientManager? _atClientManager;
      50             : 
      51             :   final _logger = AtSignLogger('AtClientImpl');
      52           6 :   static final Map _atClientInstanceMap = <String, AtClient>{};
      53             : 
      54             :   /// Returns a new instance of [AtClient]. App has to pass the current user atSign
      55             :   /// and the client preference.
      56             :   @Deprecated("Use AtClientManger to get instance of atClient")
      57           0 :   static Future<AtClient?> getClient(String? currentAtSign) async {
      58           0 :     if (_atClientInstanceMap.containsKey(currentAtSign)) {
      59           0 :       return _atClientInstanceMap[currentAtSign];
      60             :     }
      61           0 :     AtSignLogger('AtClientImpl')
      62           0 :         .severe('Instance of atclientimpl for $currentAtSign is not created');
      63             :     return null;
      64             :   }
      65             : 
      66             :   @Deprecated("Use [create]")
      67             : 
      68             :   /// use [create]
      69           0 :   static Future<void> createClient(String currentAtSign, String? namespace,
      70             :       AtClientPreference preferences) async {
      71           0 :     currentAtSign = AtUtils.formatAtSign(currentAtSign)!;
      72           0 :     if (_atClientInstanceMap.containsKey(currentAtSign)) {
      73             :       return;
      74             :     }
      75           0 :     if (preferences.isLocalStoreRequired) {
      76           0 :       var storageManager = StorageManager(preferences);
      77           0 :       await storageManager.init(currentAtSign, preferences.keyStoreSecret);
      78             :     }
      79           0 :     var atClientImpl = AtClientImpl(currentAtSign, namespace, preferences);
      80           0 :     await atClientImpl._init();
      81           0 :     _atClientInstanceMap[currentAtSign] = atClientImpl;
      82             :   }
      83             : 
      84           0 :   @Deprecated("Use [create]")
      85             :   AtClientImpl(
      86             :       String _atSign, String? namespace, AtClientPreference preference) {
      87           0 :     currentAtSign = AtUtils.formatAtSign(_atSign);
      88           0 :     _preference = preference;
      89           0 :     _namespace = namespace;
      90             :   }
      91             : 
      92           2 :   static Future<AtClient> create(
      93             :       String currentAtSign, String? namespace, AtClientPreference preferences,
      94             :       {AtClientManager? atClientManager}) async {
      95           2 :     currentAtSign = AtUtils.formatAtSign(currentAtSign)!;
      96           4 :     if (_atClientInstanceMap.containsKey(currentAtSign)) {
      97           4 :       return _atClientInstanceMap[currentAtSign];
      98             :     }
      99           2 :     if (preferences.isLocalStoreRequired) {
     100           0 :       var storageManager = StorageManager(preferences);
     101           0 :       await storageManager.init(currentAtSign, preferences.keyStoreSecret);
     102             :     }
     103           0 :     atClientManager ??= AtClientManager.getInstance();
     104             :     var atClientImpl =
     105           2 :         AtClientImpl._(currentAtSign, namespace, preferences, atClientManager);
     106           4 :     await atClientImpl._init();
     107           4 :     _atClientInstanceMap[currentAtSign] = atClientImpl;
     108           4 :     return _atClientInstanceMap[currentAtSign];
     109             :   }
     110             : 
     111           2 :   AtClientImpl._(String _atSign, String? namespace,
     112             :       AtClientPreference preference, AtClientManager atClientManager) {
     113           4 :     currentAtSign = AtUtils.formatAtSign(_atSign);
     114           2 :     _preference = preference;
     115           2 :     _namespace = namespace;
     116           2 :     _atClientManager = atClientManager;
     117             :   }
     118             : 
     119           2 :   Future<void> _init() async {
     120           4 :     if (_preference!.isLocalStoreRequired) {
     121           0 :       _localSecondary = LocalSecondary(this);
     122             :     }
     123           8 :     _remoteSecondary = RemoteSecondary(currentAtSign!, _preference!,
     124           4 :         privateKey: _preference!.privateKey);
     125           4 :     _encryptionService = EncryptionService();
     126           6 :     _encryptionService!.remoteSecondary = _remoteSecondary;
     127           6 :     _encryptionService!.currentAtSign = currentAtSign;
     128           6 :     _encryptionService!.localSecondary = _localSecondary;
     129             :   }
     130             : 
     131           0 :   Secondary getSecondary() {
     132           0 :     if (_preference!.isLocalStoreRequired) {
     133           0 :       return _localSecondary!;
     134             :     }
     135           0 :     return _remoteSecondary!;
     136             :   }
     137             : 
     138             :   @override
     139           0 :   Future<void> startMonitor(String privateKey, Function? notificationCallback,
     140             :       {String? regex}) async {
     141           0 :     var monitorVerbBuilder = MonitorVerbBuilder();
     142             :     if (regex != null) {
     143           0 :       monitorVerbBuilder.regex = regex;
     144             :     }
     145           0 :     await _remoteSecondary!.monitor(
     146           0 :         monitorVerbBuilder.buildCommand(), notificationCallback, privateKey);
     147             :   }
     148             : 
     149           0 :   @override
     150             :   LocalSecondary? getLocalSecondary() {
     151           0 :     return _localSecondary;
     152             :   }
     153             : 
     154           0 :   @override
     155             :   RemoteSecondary? getRemoteSecondary() {
     156           0 :     return _remoteSecondary;
     157             :   }
     158             : 
     159           0 :   @override
     160             :   @Deprecated("Use SyncManager.sync")
     161             :   SyncManager? getSyncManager() {
     162           0 :     return SyncManagerImpl.getInstance().getSyncManager(currentAtSign);
     163             :   }
     164             : 
     165             :   @override
     166           0 :   void setPreferences(AtClientPreference preference) async {
     167           0 :     _preference = preference;
     168             :   }
     169             : 
     170           0 :   Future<bool> persistPrivateKey(String privateKey) async {
     171           0 :     var atData = AtData();
     172           0 :     atData.data = privateKey.toString();
     173           0 :     await _localSecondary!.keyStore!.put(AT_PKAM_PRIVATE_KEY, atData);
     174             :     return true;
     175             :   }
     176             : 
     177           0 :   Future<bool> persistPublicKey(String publicKey) async {
     178           0 :     var atData = AtData();
     179           0 :     atData.data = publicKey.toString();
     180           0 :     await getLocalSecondary()!.keyStore!.put(AT_PKAM_PUBLIC_KEY, atData);
     181             :     return true;
     182             :   }
     183             : 
     184           0 :   Future<String?> getPrivateKey(String atSign) async {
     185             :     var privateKeyData =
     186           0 :         await getLocalSecondary()!.keyStore!.get(AT_PKAM_PRIVATE_KEY);
     187           0 :     var privateKey = privateKeyData?.data;
     188             :     return privateKey;
     189             :   }
     190             : 
     191           0 :   @override
     192             :   Future<bool> delete(AtKey atKey, {bool isDedicated = false}) {
     193           0 :     var isPublic = atKey.metadata != null ? atKey.metadata!.isPublic! : false;
     194           0 :     var isCached = atKey.metadata != null ? atKey.metadata!.isCached : false;
     195             :     var isNamespaceAware =
     196           0 :         atKey.metadata != null ? atKey.metadata!.namespaceAware : true;
     197           0 :     return _delete(atKey.key!,
     198           0 :         sharedWith: atKey.sharedWith,
     199           0 :         sharedBy: atKey.sharedBy,
     200             :         isPublic: isPublic,
     201             :         isCached: isCached,
     202             :         namespaceAware: isNamespaceAware);
     203             :   }
     204             : 
     205           0 :   Future<bool> _delete(String key,
     206             :       {String? sharedWith,
     207             :       String? sharedBy,
     208             :       bool isPublic = false,
     209             :       bool isCached = false,
     210             :       bool namespaceAware = true}) async {
     211             :     String keyWithNamespace;
     212             :     if (namespaceAware) {
     213           0 :       keyWithNamespace = _getKeyWithNamespace(key);
     214             :     } else {
     215             :       keyWithNamespace = key;
     216             :     }
     217           0 :     sharedBy ??= currentAtSign;
     218           0 :     var builder = DeleteVerbBuilder()
     219           0 :       ..isCached = isCached
     220           0 :       ..isPublic = isPublic
     221           0 :       ..sharedWith = sharedWith
     222           0 :       ..atKey = keyWithNamespace
     223           0 :       ..sharedBy = sharedBy;
     224           0 :     var deleteResult = await getSecondary().executeVerb(builder, sync: true);
     225             :     return deleteResult != null;
     226             :   }
     227             : 
     228           0 :   Future<dynamic> _get(String key,
     229             :       {String? sharedWith,
     230             :       String? sharedBy,
     231             :       bool? isPublic = false,
     232             :       bool isCached = false,
     233             :       bool namespaceAware = true,
     234             :       String? operation}) async {
     235             :     dynamic builder;
     236             :     String keyWithNamespace;
     237             :     if (namespaceAware) {
     238           0 :       keyWithNamespace = _getKeyWithNamespace(key);
     239             :     } else {
     240             :       keyWithNamespace = key;
     241             :     }
     242             :     if (sharedBy != null && isCached && !isPublic!) {
     243           0 :       builder = LLookupVerbBuilder()
     244           0 :         ..atKey = keyWithNamespace
     245           0 :         ..sharedBy = sharedBy
     246           0 :         ..isCached = isCached
     247           0 :         ..sharedWith = currentAtSign
     248           0 :         ..operation = operation;
     249           0 :       var encryptedResult = await getSecondary().executeVerb(builder);
     250           0 :       if (encryptedResult == 'data:null') {
     251             :         return null;
     252             :       }
     253           0 :       encryptedResult = _formatResult(encryptedResult);
     254           0 :       var encryptedResultMap = JsonUtils.decodeJson(encryptedResult);
     255           0 :       if (operation == UPDATE_META) {
     256             :         return encryptedResultMap;
     257             :       }
     258           0 :       if (sharedBy != currentAtSign && operation == UPDATE_ALL) {
     259             :         //resultant value is encrypted. Decrypting to original value.
     260             :         try {
     261           0 :           var decryptedValue = await _encryptionService!
     262           0 :               .decrypt(encryptedResultMap['data'], sharedBy);
     263           0 :           encryptedResultMap['data'] = decryptedValue;
     264           0 :         } on IllegalArgumentException {
     265           0 :           _logger.severe(
     266           0 :               'Decryption failed. encryption value is null for $keyWithNamespace');
     267           0 :         } on Error catch (e) {
     268           0 :           _logger.severe(
     269           0 :               'decryption error for command ${builder.buildCommand()}: $e');
     270             :         }
     271             :       } else {
     272             :         //resultant value is encrypted. Decrypting to original value.
     273           0 :         var isEncrypted = encryptedResultMap['metaData']['isEncrypted'];
     274             :         isEncrypted ??= false;
     275             :         String? decryptedValue;
     276             :         try {
     277           0 :           decryptedValue = await _encryptionService!
     278           0 :               .decryptForSelf(encryptedResultMap['data'], isEncrypted);
     279           0 :         } on IllegalArgumentException {
     280           0 :           _logger.severe(
     281           0 :               'Decryption failed. encryption value is null for $keyWithNamespace');
     282             :         }
     283           0 :         encryptedResultMap['data'] = decryptedValue;
     284             :       }
     285             :       return encryptedResultMap;
     286           0 :     } else if (sharedBy != null && sharedBy != currentAtSign && !isCached) {
     287             :       if (isPublic!) {
     288           0 :         builder = PLookupVerbBuilder()
     289           0 :           ..atKey = keyWithNamespace
     290           0 :           ..sharedBy = sharedBy;
     291             :         if (operation != null) {
     292           0 :           builder.operation = operation;
     293             :         }
     294           0 :         var remoteSecondary = getRemoteSecondary();
     295           0 :         var result = await remoteSecondary!.executeVerb(builder);
     296             : 
     297           0 :         result = _formatResult(result);
     298           0 :         return JsonUtils.decodeJson(result);
     299             :       } else {
     300           0 :         builder = LookupVerbBuilder()
     301           0 :           ..atKey = keyWithNamespace
     302           0 :           ..sharedBy = sharedBy
     303           0 :           ..auth = true;
     304             :         if (operation != null) {
     305           0 :           builder.operation = operation;
     306             :         }
     307           0 :         var encryptedResult = await getRemoteSecondary()!.executeVerb(builder);
     308             :         // If lookup response from remote secondary is 'data:null'.
     309           0 :         if (encryptedResult == 'data:null') {
     310             :           return null;
     311             :         }
     312           0 :         encryptedResult = _formatResult(encryptedResult);
     313           0 :         var encryptedResultMap = JsonUtils.decodeJson(encryptedResult);
     314           0 :         if (operation == UPDATE_ALL) {
     315             :           String decryptedValue = '';
     316             :           try {
     317           0 :             decryptedValue = await _encryptionService!
     318           0 :                 .decrypt(encryptedResultMap['data'], sharedBy);
     319           0 :           } on IllegalArgumentException {
     320           0 :             _logger.severe(
     321           0 :                 'Decryption failed. encrypted value is null for $keyWithNamespace');
     322           0 :           } on KeyNotFoundException catch (e) {
     323           0 :             var errorCode = AtClientExceptionUtil.getErrorCode(e);
     324           0 :             return Future.error(AtClientException(errorCode, e.message));
     325             :           }
     326           0 :           encryptedResultMap['data'] = decryptedValue;
     327             :         }
     328             :         return encryptedResultMap;
     329             :       }
     330             :       // plookup and lookup can be executed only on remote
     331             :     } else if (sharedWith != null) {
     332           0 :       sharedWith = AtUtils.formatAtSign(sharedWith);
     333           0 :       builder = LLookupVerbBuilder()
     334           0 :         ..isCached = isCached
     335           0 :         ..isPublic = isPublic!
     336           0 :         ..sharedWith = sharedWith
     337           0 :         ..atKey = keyWithNamespace
     338           0 :         ..sharedBy = currentAtSign;
     339             :       if (operation != null) {
     340           0 :         builder.operation = operation;
     341             :       }
     342           0 :       if (sharedWith != currentAtSign) {
     343           0 :         var encryptedResult = await getSecondary().executeVerb(builder);
     344           0 :         if (encryptedResult != null && encryptedResult == 'data:null') {
     345             :           return null;
     346             :         }
     347             :         // If encrypted result is metadata decryption is not needed.
     348           0 :         encryptedResult = _formatResult(encryptedResult);
     349           0 :         var encryptedResultMap = JsonUtils.decodeJson(encryptedResult);
     350           0 :         if (operation == UPDATE_ALL) {
     351             :           try {
     352           0 :             var decryptedValue = await _encryptionService!.decryptLocal(
     353           0 :                 encryptedResultMap['data'], currentAtSign, sharedWith!);
     354           0 :             encryptedResultMap['data'] = decryptedValue;
     355           0 :           } on Exception catch (e) {
     356           0 :             _logger.severe(
     357           0 :                 'decryption exception for command ${builder.buildCommand()}: ${e.toString}');
     358           0 :           } on Error catch (e) {
     359           0 :             _logger.severe(
     360           0 :                 'decryption error for command ${builder.buildCommand()}: $e');
     361             :           }
     362             :         }
     363             :         return encryptedResultMap;
     364             :       }
     365             :     } else if (isPublic!) {
     366           0 :       builder = LLookupVerbBuilder()
     367           0 :         ..isCached = isCached
     368           0 :         ..atKey = 'public:' + keyWithNamespace;
     369           0 :       builder.sharedBy = sharedBy ?? currentAtSign;
     370             :     } else {
     371           0 :       builder = LLookupVerbBuilder()..atKey = keyWithNamespace;
     372           0 :       if (keyWithNamespace.startsWith(AT_PKAM_PRIVATE_KEY) ||
     373           0 :           keyWithNamespace.startsWith(AT_PKAM_PUBLIC_KEY)) {
     374           0 :         builder.sharedBy = null;
     375             :       } else {
     376           0 :         builder.sharedBy = currentAtSign;
     377             :       }
     378             :     }
     379             :     if (operation != null) {
     380           0 :       builder.operation = operation;
     381             :     }
     382           0 :     var result = await getSecondary().executeVerb(builder);
     383           0 :     if (result == null || result == 'data:null') {
     384             :       return null;
     385             :     }
     386           0 :     result = _formatResult(result);
     387           0 :     var encryptedResultMap = JsonUtils.decodeJson(result);
     388             :     //If operation is update_meta, return metadata.
     389           0 :     if (operation == UPDATE_META) {
     390             :       return encryptedResultMap;
     391             :     }
     392           0 :     var isEncrypted = encryptedResultMap['metaData']['isEncrypted'];
     393             :     isEncrypted ??= false;
     394             :     String? decryptedValue;
     395             :     try {
     396           0 :       decryptedValue = await _encryptionService!
     397           0 :           .decryptForSelf(encryptedResultMap['data'], isEncrypted);
     398           0 :     } on IllegalArgumentException {
     399           0 :       _logger.severe(
     400           0 :           'Decryption failed. encryption value is null for $keyWithNamespace');
     401             :     }
     402           0 :     encryptedResultMap['data'] = decryptedValue;
     403             :     return encryptedResultMap;
     404             :   }
     405             : 
     406             :   @override
     407           0 :   Future<AtValue> get(AtKey atKey, {bool isDedicated = false}) async {
     408           0 :     var isPublic = atKey.metadata != null ? atKey.metadata!.isPublic : false;
     409             :     var namespaceAware =
     410           0 :         atKey.metadata != null ? atKey.metadata!.namespaceAware : true;
     411           0 :     var isCached = atKey.metadata != null ? atKey.metadata!.isCached : false;
     412           0 :     var getResult = await _get(atKey.key!,
     413           0 :         sharedWith: AtUtils.formatAtSign(atKey.sharedWith),
     414           0 :         sharedBy: AtUtils.formatAtSign(atKey.sharedBy),
     415             :         isPublic: isPublic,
     416             :         isCached: isCached,
     417             :         namespaceAware: namespaceAware,
     418             :         operation: UPDATE_ALL);
     419             : 
     420           0 :     var atValue = AtValue();
     421           0 :     if (getResult == null || getResult == 'null' || getResult['data'] == null) {
     422             :       return atValue;
     423             :     }
     424           0 :     if (atKey.metadata != null && atKey.metadata!.isBinary!) {
     425           0 :       atValue.value = Base2e15.decode(getResult['data']);
     426             :     } else {
     427           0 :       atValue.value = getResult['data'];
     428           0 :       if (atValue.value is String) {
     429           0 :         atValue.value = VerbUtil.getFormattedValue(atValue.value);
     430             :       }
     431             :     }
     432           0 :     atValue.metadata = _prepareMetadata(getResult['metaData'], isPublic);
     433             :     return atValue;
     434             :   }
     435             : 
     436             :   @override
     437           0 :   Future<Metadata?> getMeta(AtKey atKey, {bool isDedicated = false}) async {
     438           0 :     var isPublic = atKey.metadata != null ? atKey.metadata!.isPublic : false;
     439             :     var namespaceAware =
     440           0 :         atKey.metadata != null ? atKey.metadata!.namespaceAware : true;
     441           0 :     var isCached = atKey.metadata != null ? atKey.metadata!.isCached : false;
     442           0 :     var getResult = await _get(atKey.key!,
     443           0 :         sharedWith: atKey.sharedWith,
     444           0 :         sharedBy: atKey.sharedBy,
     445             :         isPublic: isPublic,
     446             :         isCached: isCached,
     447             :         namespaceAware: namespaceAware,
     448             :         operation: UPDATE_META);
     449           0 :     if (getResult == null || getResult == 'null') {
     450             :       return null;
     451             :     }
     452           0 :     return _prepareMetadata(getResult, isPublic);
     453             :   }
     454             : 
     455             :   @override
     456           0 :   Future<List<String>> getKeys(
     457             :       {String? regex,
     458             :       String? sharedBy,
     459             :       String? sharedWith,
     460             :       bool isDedicated = false}) async {
     461           0 :     var builder = ScanVerbBuilder()
     462           0 :       ..sharedWith = sharedWith
     463           0 :       ..sharedBy = sharedBy
     464           0 :       ..regex = regex
     465           0 :       ..auth = true;
     466           0 :     var scanResult = await getSecondary().executeVerb(builder);
     467           0 :     scanResult = _formatResult(scanResult);
     468           0 :     var result = [];
     469           0 :     if (scanResult != null && scanResult.isNotEmpty) {
     470           0 :       result = List<String>.from(jsonDecode(scanResult));
     471             :     }
     472             :     return result as FutureOr<List<String>>;
     473             :   }
     474             : 
     475             :   @override
     476           0 :   Future<List<AtKey>> getAtKeys(
     477             :       {String? regex,
     478             :       String? sharedBy,
     479             :       String? sharedWith,
     480             :       bool isDedicated = false}) async {
     481           0 :     var getKeysResult = await getKeys(
     482             :         regex: regex,
     483             :         sharedBy: sharedBy,
     484             :         sharedWith: sharedWith,
     485             :         isDedicated: isDedicated);
     486           0 :     var result = <AtKey>[];
     487           0 :     if (getKeysResult.isNotEmpty) {
     488           0 :       for (var key in getKeysResult) {
     489             :         try {
     490           0 :           result.add(AtKey.fromString(key));
     491           0 :         } on InvalidSyntaxException {
     492           0 :           _logger.severe('$key is not a well-formed key');
     493           0 :         } on Exception catch (e) {
     494           0 :           _logger.severe(
     495           0 :               'Exception occured: ${e.toString()}. Unable to form key $key');
     496             :         }
     497             :       }
     498             :     }
     499             :     return result;
     500             :   }
     501             : 
     502           0 :   Future<bool> _put(String key, dynamic value,
     503             :       {String? sharedWith, Metadata? metadata}) async {
     504             :     var updateKey = key;
     505           0 :     if (metadata == null || metadata.namespaceAware) {
     506           0 :       updateKey = _getKeyWithNamespace(key);
     507             :     }
     508           0 :     var operation = getOperation(value, metadata);
     509           0 :     sharedWith = AtUtils.formatAtSign(sharedWith);
     510           0 :     var builder = UpdateVerbBuilder()
     511           0 :       ..atKey = updateKey
     512           0 :       ..sharedBy = currentAtSign
     513           0 :       ..sharedWith = sharedWith
     514           0 :       ..value = value
     515           0 :       ..operation = operation;
     516             : 
     517             :     if (metadata != null) {
     518           0 :       builder.ttl = metadata.ttl;
     519           0 :       builder.ttb = metadata.ttb;
     520           0 :       builder.ttr = metadata.ttr;
     521           0 :       builder.ccd = metadata.ccd;
     522           0 :       builder.isBinary = metadata.isBinary;
     523           0 :       builder.isEncrypted = metadata.isEncrypted;
     524           0 :       builder.isPublic = metadata.isPublic!;
     525           0 :       if (metadata.isHidden) {
     526           0 :         builder.atKey = '_' + updateKey;
     527             :       }
     528             :     }
     529             :     if (value != null) {
     530           0 :       if (sharedWith != null && sharedWith != currentAtSign) {
     531             :         try {
     532           0 :           builder.value =
     533           0 :               await _encryptionService!.encrypt(key, value, sharedWith);
     534           0 :           builder.isEncrypted = true;
     535           0 :         } on KeyNotFoundException catch (e) {
     536           0 :           var errorCode = AtClientExceptionUtil.getErrorCode(e);
     537           0 :           return Future.error(AtClientException(errorCode, e.message));
     538             :         }
     539           0 :       } else if (!builder.isPublic &&
     540           0 :           !builder.atKey.toString().startsWith('_')) {
     541           0 :         builder.value = await _encryptionService!.encryptForSelf(key, value);
     542           0 :         builder.isEncrypted = true;
     543             :       }
     544             :     }
     545             :     var isSyncRequired = true;
     546           0 :     if (updateKey.startsWith(AT_PKAM_PRIVATE_KEY) ||
     547           0 :         updateKey.startsWith(AT_PKAM_PUBLIC_KEY)) {
     548           0 :       builder.sharedBy = null;
     549             :     }
     550           0 :     if (SyncUtil.shouldSkipSync(updateKey)) {
     551             :       isSyncRequired = false;
     552             :     }
     553             :     //sign public data with private encryption key
     554           0 :     if (metadata != null && metadata.isPublic!) {
     555             :       try {
     556             :         var encryptionPrivateKey =
     557           0 :             await _localSecondary!.getEncryptionPrivateKey();
     558             :         if (encryptionPrivateKey != null) {
     559           0 :           _logger.finer('signing public data for key:$key');
     560           0 :           builder.dataSignature =
     561           0 :               _encryptionService!.signPublicData(encryptionPrivateKey, value);
     562             :         }
     563           0 :       } on Exception catch (e) {
     564           0 :         _logger.severe('Exception trying to sign public data:${e.toString()}');
     565             :       }
     566             :     }
     567             : 
     568             :     String? putResult;
     569             :     try {
     570           0 :       if (builder.dataSignature != null) {
     571           0 :         builder.isJson = true;
     572             :       }
     573             :       putResult =
     574           0 :           await getSecondary().executeVerb(builder, sync: isSyncRequired);
     575           0 :     } on AtClientException catch (e) {
     576           0 :       _logger.severe(
     577           0 :           'error code: ${e.errorCode} error message: ${e.errorMessage}');
     578           0 :     } on Exception catch (e) {
     579           0 :       _logger.severe('error in put: ${e.toString()}');
     580             :     }
     581             :     return putResult != null;
     582             :   }
     583             : 
     584             :   @override
     585           0 :   Future<bool> put(AtKey atKey, dynamic value,
     586             :       {bool isDedicated = false}) async {
     587           0 :     if (atKey.metadata != null && atKey.metadata!.isBinary!) {
     588           0 :       if (value != null && value.length > _preference!.maxDataSize) {
     589           0 :         throw AtClientException('AT0005', 'BufferOverFlowException');
     590             :       }
     591           0 :       value = Base2e15.encode(value);
     592             :     }
     593           0 :     return _put(atKey.key!, value,
     594           0 :         sharedWith: atKey.sharedWith, metadata: atKey.metadata);
     595             :   }
     596             : 
     597             :   @override
     598           0 :   Future<bool> notify(AtKey atKey, String value, OperationEnum operation,
     599             :       {MessageTypeEnum? messageType,
     600             :       PriorityEnum? priority,
     601             :       StrategyEnum? strategy,
     602             :       int? latestN,
     603             :       String? notifier = SYSTEM,
     604             :       bool isDedicated = false}) async {
     605             :     final notificationParams =
     606           0 :         NotificationParams.forUpdate(atKey, value: value);
     607             :     final notifyResult =
     608           0 :         await _atClientManager!.notificationService.notify(notificationParams);
     609           0 :     return notifyResult.notificationStatusEnum ==
     610             :         NotificationStatusEnum.delivered;
     611             :   }
     612             : 
     613             :   @override
     614           0 :   Future<String> notifyAll(AtKey atKey, String value, OperationEnum operation,
     615             :       {bool isDedicated = false}) async {
     616           0 :     var returnMap = {};
     617           0 :     var sharedWithList = jsonDecode(atKey.sharedWith!);
     618           0 :     for (var sharedWith in sharedWithList) {
     619           0 :       atKey.sharedWith = sharedWith;
     620             :       final notificationParams =
     621           0 :           NotificationParams.forUpdate(atKey, value: value);
     622           0 :       final notifyResult = await _atClientManager!.notificationService
     623           0 :           .notify(notificationParams);
     624           0 :       returnMap.putIfAbsent(
     625             :           sharedWith,
     626           0 :           () => (notifyResult.notificationStatusEnum ==
     627             :               NotificationStatusEnum.delivered));
     628             :     }
     629           0 :     return jsonEncode(returnMap);
     630             :   }
     631             : 
     632             :   @override
     633           0 :   Future<String> notifyStatus(String notificationId) async {
     634           0 :     var builder = NotifyStatusVerbBuilder()..notificationId = notificationId;
     635           0 :     var notifyStatus = await getRemoteSecondary()!.executeVerb(builder);
     636             :     return notifyStatus;
     637             :   }
     638             : 
     639             :   @override
     640           0 :   Future<String> notifyList(
     641             :       {String? fromDate,
     642             :       String? toDate,
     643             :       String? regex,
     644             :       bool isDedicated = false}) async {
     645             :     try {
     646           0 :       var builder = NotifyListVerbBuilder()
     647           0 :         ..fromDate = fromDate
     648           0 :         ..toDate = toDate
     649           0 :         ..regex = regex;
     650           0 :       var notifyList = await getRemoteSecondary()!.executeVerb(builder);
     651             :       return notifyList;
     652           0 :     } on AtLookUpException catch (e) {
     653           0 :       throw AtClientException(e.errorCode, e.errorMessage);
     654             :     }
     655             :   }
     656             : 
     657             :   @override
     658           0 :   Future<bool> putMeta(AtKey atKey) async {
     659           0 :     var updateKey = atKey.key;
     660           0 :     var metadata = atKey.metadata!;
     661           0 :     if (metadata.namespaceAware) {
     662           0 :       updateKey = _getKeyWithNamespace(atKey.key!);
     663             :     }
     664           0 :     var sharedWith = atKey.sharedWith;
     665           0 :     var builder = UpdateVerbBuilder();
     666             :     builder
     667           0 :       ..atKey = updateKey
     668           0 :       ..sharedBy = currentAtSign
     669           0 :       ..sharedWith = sharedWith
     670           0 :       ..ttl = metadata.ttl
     671           0 :       ..ttb = metadata.ttb
     672           0 :       ..ttr = metadata.ttr
     673           0 :       ..ccd = metadata.ccd
     674           0 :       ..isBinary = metadata.isBinary
     675           0 :       ..isEncrypted = metadata.isEncrypted
     676           0 :       ..dataSignature = metadata.dataSignature
     677           0 :       ..operation = UPDATE_META;
     678             : 
     679             :     var isSyncRequired = true;
     680           0 :     if (SyncUtil.shouldSkipSync(updateKey!)) {
     681             :       isSyncRequired = false;
     682             :     }
     683             : 
     684             :     var updateMetaResult =
     685           0 :         await getSecondary().executeVerb(builder, sync: isSyncRequired);
     686             :     return updateMetaResult != null;
     687             :   }
     688             : 
     689           0 :   String _getKeyWithNamespace(String key) {
     690             :     var keyWithNamespace = key;
     691           0 :     if (_namespace != null && _namespace!.isNotEmpty) {
     692           0 :       keyWithNamespace = '$keyWithNamespace.$_namespace';
     693             :     }
     694             :     return keyWithNamespace;
     695             :   }
     696             : 
     697           0 :   String? getOperation(dynamic value, Metadata? data) {
     698             :     if (value != null && data == null) {
     699             :       return VALUE;
     700             :     }
     701             :     // Verifies if any of the args are not null
     702           0 :     var isMetadataNotNull = AtClientUtil.isAnyNotNull(
     703           0 :         a1: data!.ttl,
     704           0 :         a2: data.ttb,
     705           0 :         a3: data.ttr,
     706           0 :         a4: data.ccd,
     707           0 :         a5: data.isBinary,
     708           0 :         a6: data.isEncrypted);
     709             :     //If value is not null and metadata is not null, return UPDATE_ALL
     710             :     if (value != null && isMetadataNotNull) {
     711             :       return UPDATE_ALL;
     712             :     }
     713             :     //If value is null and metadata is not null,
     714             :     if (value == null && isMetadataNotNull) {
     715             :       return UPDATE_META;
     716             :     }
     717             :     return null;
     718             :   }
     719             : 
     720           0 :   String _formatResult(String? commandResult) {
     721             :     var result = commandResult;
     722             :     if (result != null) {
     723           0 :       result = result.replaceFirst('data:', '');
     724             :     }
     725             :     return result ??= '';
     726             :   }
     727             : 
     728           0 :   Metadata? _prepareMetadata(
     729             :       Map<String, dynamic>? metadataMap, bool? isPublic) {
     730             :     if (metadataMap == null) {
     731             :       return null;
     732             :     }
     733           0 :     var metadata = Metadata();
     734           0 :     metadata.expiresAt =
     735           0 :         (metadataMap['expiresAt'] != null && metadataMap['expiresAt'] != 'null')
     736           0 :             ? DateTime.parse(metadataMap['expiresAt'])
     737             :             : null;
     738           0 :     metadata.availableAt = (metadataMap['availableAt'] != null &&
     739           0 :             metadataMap['availableAt'] != 'null')
     740           0 :         ? DateTime.parse(metadataMap['availableAt'])
     741             :         : null;
     742           0 :     metadata.refreshAt =
     743           0 :         (metadataMap[REFRESH_AT] != null && metadataMap[REFRESH_AT] != 'null')
     744           0 :             ? DateTime.parse(metadataMap[REFRESH_AT])
     745             :             : null;
     746           0 :     metadata.createdAt =
     747           0 :         (metadataMap[CREATED_AT] != null && metadataMap[CREATED_AT] != 'null')
     748           0 :             ? DateTime.parse(metadataMap[CREATED_AT])
     749             :             : null;
     750           0 :     metadata.updatedAt =
     751           0 :         (metadataMap[UPDATED_AT] != null && metadataMap[UPDATED_AT] != 'null')
     752           0 :             ? DateTime.parse(metadataMap[UPDATED_AT])
     753             :             : null;
     754           0 :     metadata.ttr = metadataMap[AT_TTR];
     755           0 :     metadata.ttl = metadataMap[AT_TTL];
     756           0 :     metadata.ttb = metadataMap[AT_TTB];
     757           0 :     metadata.ccd = metadataMap[CCD];
     758           0 :     metadata.isBinary = metadataMap[IS_BINARY];
     759           0 :     metadata.isEncrypted = metadataMap[IS_ENCRYPTED];
     760           0 :     metadata.dataSignature = metadataMap[PUBLIC_DATA_SIGNATURE];
     761             :     if (isPublic!) {
     762           0 :       metadata.isPublic = isPublic;
     763             :     }
     764             :     return metadata;
     765             :   }
     766             : 
     767             :   @override
     768           0 :   Future<AtStreamResponse> stream(String sharedWith, String filePath,
     769             :       {String? namespace}) async {
     770           0 :     var streamResponse = AtStreamResponse();
     771           0 :     var streamId = Uuid().v4();
     772           0 :     var file = File(filePath);
     773           0 :     var data = file.readAsBytesSync();
     774           0 :     var fileName = basename(filePath);
     775           0 :     fileName = base64.encode(utf8.encode(fileName));
     776             :     var encryptedData =
     777           0 :         await _encryptionService!.encryptStream(data, sharedWith);
     778             :     var command =
     779           0 :         'stream:init$sharedWith namespace:$namespace $streamId $fileName ${encryptedData.length}\n';
     780           0 :     _logger.finer('sending stream init:$command');
     781           0 :     var remoteSecondary = RemoteSecondary(currentAtSign!, _preference!);
     782           0 :     var result = await remoteSecondary.executeCommand(command, auth: true);
     783           0 :     _logger.finer('ack message:$result');
     784           0 :     if (result != null && result.startsWith('stream:ack')) {
     785           0 :       result = result.replaceAll('stream:ack ', '');
     786           0 :       result = result.trim();
     787           0 :       _logger.finer('ack received for streamId:$streamId');
     788           0 :       remoteSecondary.atLookUp.connection!.getSocket().add(encryptedData);
     789           0 :       var streamResult = await remoteSecondary.atLookUp.messageListener
     790           0 :           .read(maxWaitMilliSeconds: _preference!.outboundConnectionTimeout);
     791           0 :       if (streamResult != null && streamResult.startsWith('stream:done')) {
     792           0 :         await remoteSecondary.atLookUp.connection!.close();
     793           0 :         streamResponse.status = AtStreamStatus.complete;
     794             :       }
     795           0 :     } else if (result != null && result.startsWith('error:')) {
     796           0 :       result = result.replaceAll('error:', '');
     797           0 :       streamResponse.errorCode = result.split('-')[0];
     798           0 :       streamResponse.errorMessage = result.split('-')[1];
     799           0 :       streamResponse.status = AtStreamStatus.error;
     800             :     } else {
     801           0 :       streamResponse.status = AtStreamStatus.noAck;
     802             :     }
     803             :     return streamResponse;
     804             :   }
     805             : 
     806             :   @override
     807           0 :   Future<void> sendStreamAck(
     808             :       String streamId,
     809             :       String fileName,
     810             :       int fileLength,
     811             :       String senderAtSign,
     812             :       Function streamCompletionCallBack,
     813             :       Function streamReceiveCallBack) async {
     814           0 :     var handler = StreamNotificationHandler();
     815           0 :     handler.remoteSecondary = getRemoteSecondary();
     816           0 :     handler.localSecondary = getLocalSecondary();
     817           0 :     handler.preference = _preference;
     818           0 :     handler.encryptionService = _encryptionService;
     819           0 :     var notification = AtStreamNotification()
     820           0 :       ..streamId = streamId
     821           0 :       ..fileName = fileName
     822           0 :       ..currentAtSign = currentAtSign!
     823           0 :       ..senderAtSign = senderAtSign
     824           0 :       ..fileLength = fileLength;
     825           0 :     _logger.info('Sending ack for stream notification:$notification');
     826           0 :     await handler.streamAck(
     827             :         notification, streamCompletionCallBack, streamReceiveCallBack);
     828             :   }
     829             : 
     830             :   @override
     831           0 :   Future<Map<String, FileTransferObject>> uploadFile(
     832             :       List<File> files, List<String> sharedWithAtSigns) async {
     833           0 :     var encryptionKey = _encryptionService!.generateFileEncryptionKey();
     834           0 :     var key = TextConstants.fileTransferKey + Uuid().v4();
     835           0 :     var fileStatus = await _uploadFiles(key, files, encryptionKey);
     836           0 :     var fileUrl = TextConstants.fileBinURL + 'archive/' + key + '/zip';
     837           0 :     return shareFiles(
     838             :         sharedWithAtSigns, key, fileUrl, encryptionKey, fileStatus);
     839             :   }
     840             : 
     841             :   @override
     842           0 :   Future<Map<String, FileTransferObject>> shareFiles(
     843             :       List<String> sharedWithAtSigns,
     844             :       String key,
     845             :       String fileUrl,
     846             :       String encryptionKey,
     847             :       List<FileStatus> fileStatus,
     848             :       {DateTime? date}) async {
     849           0 :     var result = <String, FileTransferObject>{};
     850           0 :     for (var sharedWithAtSign in sharedWithAtSigns) {
     851           0 :       var fileTransferObject = FileTransferObject(
     852             :           key, encryptionKey, fileUrl, sharedWithAtSign, fileStatus,
     853             :           date: date);
     854             :       try {
     855           0 :         var atKey = AtKey()
     856           0 :           ..key = key
     857           0 :           ..sharedWith = sharedWithAtSign
     858           0 :           ..metadata = Metadata()
     859           0 :           ..metadata!.ttr = -1
     860             :           // file transfer key will be deleted after 30 days
     861           0 :           ..metadata!.ttl = 2592000000
     862           0 :           ..sharedBy = currentAtSign;
     863             : 
     864             :         var notificationResult =
     865           0 :             await _atClientManager!.notificationService.notify(
     866           0 :           NotificationParams.forUpdate(
     867             :             atKey,
     868           0 :             value: jsonEncode(fileTransferObject.toJson()),
     869             :           ),
     870             :         );
     871             : 
     872           0 :         if (notificationResult.notificationStatusEnum ==
     873             :             NotificationStatusEnum.delivered) {
     874           0 :           fileTransferObject.sharedStatus = true;
     875             :         } else {
     876           0 :           fileTransferObject.sharedStatus = false;
     877             :         }
     878           0 :       } on Exception catch (e) {
     879           0 :         fileTransferObject.sharedStatus = false;
     880           0 :         fileTransferObject.error = e.toString();
     881             :       }
     882           0 :       result[sharedWithAtSign] = fileTransferObject;
     883             :     }
     884             :     return result;
     885             :   }
     886             : 
     887           0 :   Future<List<FileStatus>> _uploadFiles(
     888             :       String transferId, List<File> files, String encryptionKey) async {
     889           0 :     var fileStatuses = <FileStatus>[];
     890           0 :     for (var file in files) {
     891           0 :       var fileStatus = FileStatus(
     892           0 :         fileName: file.path.split('/').last,
     893             :         isUploaded: false,
     894           0 :         size: await file.length(),
     895             :       );
     896             :       try {
     897           0 :         var encryptedFile = _encryptionService!.encryptFile(
     898           0 :           file.readAsBytesSync(),
     899             :           encryptionKey,
     900             :         );
     901           0 :         var response = await FileTransferService().uploadToFileBin(
     902             :           encryptedFile,
     903             :           transferId,
     904           0 :           fileStatus.fileName!,
     905             :         );
     906           0 :         if (response is http.Response && response.statusCode == 201) {
     907           0 :           Map fileInfo = jsonDecode(response.body);
     908             :           // changing file name if it's not url friendly
     909           0 :           fileStatus.fileName = fileInfo['file']['filename'];
     910           0 :           fileStatus.isUploaded = true;
     911             :         }
     912             : 
     913             :         // storing sent files in a a directory.
     914           0 :         if (preference?.downloadPath != null) {
     915             :           var sentFilesDirectory =
     916           0 :               await Directory(preference!.downloadPath! + '/sent-files')
     917           0 :                   .create();
     918           0 :           await File(file.path)
     919           0 :               .copy(sentFilesDirectory.path + '/${fileStatus.fileName}');
     920             :         }
     921           0 :       } on Exception catch (e) {
     922           0 :         fileStatus.error = e.toString();
     923             :       }
     924           0 :       fileStatuses.add(fileStatus);
     925             :     }
     926             :     return fileStatuses;
     927             :   }
     928             : 
     929             :   @override
     930           0 :   Future<List<FileStatus>> reuploadFiles(
     931             :       List<File> files, FileTransferObject fileTransferObject) async {
     932           0 :     var response = await _uploadFiles(fileTransferObject.transferId, files,
     933           0 :         fileTransferObject.fileEncryptionKey);
     934             :     return response;
     935             :   }
     936             : 
     937             :   @override
     938           0 :   Future<List<File>> downloadFile(String transferId, String sharedByAtSign,
     939             :       {String? downloadPath}) async {
     940           0 :     downloadPath ??= preference!.downloadPath;
     941             :     if (downloadPath == null) {
     942           0 :       throw Exception('downloadPath not found');
     943             :     }
     944           0 :     var atKey = AtKey()
     945           0 :       ..key = transferId
     946           0 :       ..sharedBy = sharedByAtSign;
     947           0 :     var result = await get(atKey);
     948             :     FileTransferObject fileTransferObject;
     949             :     try {
     950           0 :       if (FileTransferObject.fromJson(jsonDecode(result.value)) == null) {
     951           0 :         _logger.severe("FileTransferObject is null");
     952           0 :         throw AtClientException("AT0014", "FileTransferObject is null");
     953             :       }
     954             :       fileTransferObject =
     955           0 :           FileTransferObject.fromJson(jsonDecode(result.value))!;
     956           0 :     } on Exception catch (e) {
     957           0 :       throw Exception('json decode exception in download file ${e.toString()}');
     958             :     }
     959           0 :     var downloadedFiles = <File>[];
     960           0 :     var fileDownloadReponse = await FileTransferService()
     961           0 :         .downloadFromFileBin(fileTransferObject, downloadPath);
     962           0 :     if (fileDownloadReponse.isError) {
     963           0 :       throw Exception('download fail');
     964             :     }
     965           0 :     var encryptedFileList = Directory(fileDownloadReponse.filePath!).listSync();
     966             :     try {
     967           0 :       for (var encryptedFile in encryptedFileList) {
     968           0 :         var decryptedFile = _encryptionService!.decryptFile(
     969           0 :             File(encryptedFile.path).readAsBytesSync(),
     970           0 :             fileTransferObject.fileEncryptionKey);
     971             :         var downloadedFile =
     972           0 :             File(downloadPath + '/' + encryptedFile.path.split('/').last);
     973           0 :         downloadedFile.writeAsBytesSync(decryptedFile);
     974           0 :         downloadedFiles.add(downloadedFile);
     975             :       }
     976             :       // deleting temp directory
     977           0 :       Directory(fileDownloadReponse.filePath!).deleteSync(recursive: true);
     978             :       return downloadedFiles;
     979             :     } catch (e) {
     980           0 :       print('error in downloadFile: $e');
     981           0 :       return [];
     982             :     }
     983             :   }
     984             : 
     985             :   @Deprecated("Use EncryptionService")
     986           0 :   Future<void> encryptUnEncryptedData() async {
     987           0 :     await _encryptionService!.encryptUnencryptedData();
     988             :   }
     989             : 
     990           2 :   @override
     991             :   String? getCurrentAtSign() {
     992           2 :     return currentAtSign;
     993             :   }
     994             : 
     995           1 :   @override
     996             :   AtClientPreference? getPreferences() {
     997           1 :     return _preference;
     998             :   }
     999             : 
    1000             :   @override
    1001           0 :   Future<String?> notifyChange(NotificationParams notificationParams) async {
    1002             :     // Check for internet. Since notify invoke remote secondary directly, network connection
    1003             :     // is mandatory.
    1004           0 :     if (!await NetworkUtil.isNetworkAvailable()) {
    1005           0 :       throw AtClientException(
    1006           0 :           atClientErrorCodes['AtClientException'], 'No network availability');
    1007             :     }
    1008             :     // validate sharedWith atSign
    1009           0 :     AtUtils.fixAtSign(notificationParams.atKey.sharedWith!);
    1010             :     // Check if sharedWith AtSign exists
    1011           0 :     AtClientValidation.isAtSignExists(notificationParams.atKey.sharedWith!,
    1012           0 :         _preference!.rootDomain, _preference!.rootPort);
    1013             :     // validate sharedBy atSign
    1014           0 :     notificationParams.atKey.sharedBy ??= getCurrentAtSign();
    1015           0 :     AtUtils.fixAtSign(notificationParams.atKey.sharedBy!);
    1016             :     // validate atKey
    1017             :     // For messageType is text, text may contains spaces but key should not have spaces
    1018             :     // Hence do not validate the key.
    1019           0 :     if (notificationParams.messageType != MessageTypeEnum.text) {
    1020           0 :       AtClientValidation.validateKey(notificationParams.atKey.key);
    1021             :     }
    1022             :     // validate metadata
    1023           0 :     AtClientValidation.validateMetadata(notificationParams.atKey.metadata);
    1024             :     // If namespaceAware is set to true, append nameSpace to key.
    1025           0 :     if (notificationParams.atKey.metadata != null &&
    1026           0 :         notificationParams.atKey.metadata!.namespaceAware) {
    1027           0 :       notificationParams.atKey.key =
    1028           0 :           _getKeyWithNamespace(notificationParams.atKey.key!);
    1029             :     }
    1030           0 :     notificationParams.atKey.sharedBy ??= currentAtSign;
    1031             : 
    1032           0 :     var builder = NotifyVerbBuilder()
    1033           0 :       ..atKey = notificationParams.atKey.key
    1034           0 :       ..sharedBy = notificationParams.atKey.sharedBy
    1035           0 :       ..sharedWith = notificationParams.atKey.sharedWith
    1036           0 :       ..operation = notificationParams.operation
    1037           0 :       ..messageType = notificationParams.messageType
    1038           0 :       ..priority = notificationParams.priority
    1039           0 :       ..strategy = notificationParams.strategy
    1040           0 :       ..latestN = notificationParams.latestN
    1041           0 :       ..notifier = notificationParams.notifier;
    1042             : 
    1043             :     // If value is not null, encrypt the value
    1044           0 :     if (notificationParams.value != null &&
    1045           0 :         notificationParams.value!.isNotEmpty) {
    1046             :       // If atKey is being notified to another atSign, encrypt data with other
    1047             :       // atSign encryption public key.
    1048           0 :       if (notificationParams.atKey.sharedWith != null &&
    1049           0 :           notificationParams.atKey.sharedWith != currentAtSign) {
    1050             :         try {
    1051           0 :           builder.value = await _encryptionService!.encrypt(
    1052           0 :               notificationParams.atKey.key,
    1053           0 :               notificationParams.value!,
    1054           0 :               notificationParams.atKey.sharedWith!);
    1055           0 :         } on KeyNotFoundException catch (e) {
    1056           0 :           var errorCode = AtClientExceptionUtil.getErrorCode(e);
    1057           0 :           return Future.error(AtClientException(errorCode, e.message));
    1058             :         }
    1059             :       }
    1060             :       // If sharedWith is currentAtSign, encrypt data with currentAtSign encryption public key.
    1061           0 :       if (notificationParams.atKey.sharedWith == null ||
    1062           0 :           notificationParams.atKey.sharedWith == currentAtSign) {
    1063           0 :         builder.value = await _encryptionService!.encryptForSelf(
    1064           0 :             notificationParams.atKey.key, notificationParams.value!);
    1065             :       }
    1066             :     }
    1067             :     // If metadata is not null, add metadata to notify builder object.
    1068           0 :     if (notificationParams.atKey.metadata != null) {
    1069           0 :       builder.ttl = notificationParams.atKey.metadata!.ttl;
    1070           0 :       builder.ttb = notificationParams.atKey.metadata!.ttb;
    1071           0 :       builder.ttr = notificationParams.atKey.metadata!.ttr;
    1072           0 :       builder.ccd = notificationParams.atKey.metadata!.ccd;
    1073           0 :       builder.isPublic = notificationParams.atKey.metadata!.isPublic!;
    1074             :     }
    1075           0 :     if (notificationParams.atKey.key!.startsWith(AT_PKAM_PRIVATE_KEY) ||
    1076           0 :         notificationParams.atKey.key!.startsWith(AT_PKAM_PUBLIC_KEY)) {
    1077           0 :       builder.sharedBy = null;
    1078             :     }
    1079           0 :     return await getRemoteSecondary()?.executeVerb(builder);
    1080             :   }
    1081             : }

Generated by: LCOV version 1.13