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 : }
|