import 'dart:async'; import 'dart:convert'; import 'package:ndk/shared/nips/nip01/bip340.dart'; import 'package:peridot/l10n/app_localizations.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:get/get.dart'; import 'package:ndk/ndk.dart'; import 'package:nostr_bunker/nostr_bunker.dart'; import 'package:peridot/config.dart'; import 'package:peridot/models/bunker_request.dart'; import 'package:peridot/services/notification_service.dart'; import 'package:peridot/utils/get_database.dart'; import 'package:sembast/sembast.dart' as sembast; // TODO add an option on the settings page to remove the client tag for new apps // TODO be able to edit the json // TODO show a diferent request screeen for know kinds like 0, 5 // TODO check cascading delete // TODO import export apps class Repository extends GetxController { static Repository get to => Get.find(); static Ndk get ndk => Get.find(); static Bunker get bunker => Get.find(); bool isAppLoaded = false; RxSet usersPubkeys = RxSet({}); Map appLastUsed = {}; StreamSubscription? pendingRequestsSub; StreamSubscription? blockedRequestsSub; StreamSubscription? processedRequestsSub; late sembast.Database db; Future loadApp() async { if (isAppLoaded) return; isAppLoaded = true; db = await getDatabase(databaseName); await loadBunkerState(); update(); bunker.start(); pendingRequestsSub = bunker.pendingRequestsStream.listen((req) { final reqObj = BunkerRequest( originalRequest: req, status: BunkerRequestStatus.pending, ); var store = sembast.stringMapStoreFactory.store('requests'); store.record(req.id).put(db, reqObj.toJson()); final app = bunker.getApp(req); if (Get.context != null) { final l10n = AppLocalizations.of(Get.context!)!; final appName = app?.name ?? l10n.deletedApp; NotificationService.to.showNotification( title: l10n.newPendingRequest, body: l10n.newPendingRequestBody(appName), ); } update(); }); blockedRequestsSub = bunker.blockedRequestsStream.listen((req) { final reqObj = BunkerRequest( originalRequest: req, status: BunkerRequestStatus.blocked, ); var store = sembast.stringMapStoreFactory.store('requests'); store.record(req.id).put(db, reqObj.toJson()); }); processedRequestsSub = bunker.processedRequestsStream.listen((req) async { final reqObj = BunkerRequest( originalRequest: req, status: BunkerRequestStatus.processed, ); var store = sembast.stringMapStoreFactory.store('requests'); await store.record(req.id).put(db, reqObj.toJson()); // Update last used timestamp for the app appLastUsed[req.appPubkey] = DateTime.now().millisecondsSinceEpoch; await saveAppLastUsed(); }); } Future addAccount(String privateKey) async { final pubkey = Bip340.getPublicKey(privateKey); usersPubkeys.add(pubkey); bunker.addPrivateKey(privateKey); update(); await saveBunkerState(); } Future removeAccount(String pubkey) async { usersPubkeys.remove(pubkey); bunker.removePrivateKey(pubkey); update(); await saveBunkerState(); } Future removeApp(App app) async { bunker.removeApp(app); update(); var appsStore = sembast.stringMapStoreFactory.store('apps'); var requestsStore = sembast.stringMapStoreFactory.store('requests'); final finder = sembast.Finder( filter: sembast.Filter.equals('originalRequest.appPubkey', app.appPubkey), ); await Future.wait([ appsStore.record(app.bunkerPubkey).delete(db), requestsStore.delete(db, finder: finder), ]); } Future> getPendingAndBlockedRequests({ String? appPubkey, }) async { var store = sembast.stringMapStoreFactory.store('requests'); List filters = [ sembast.Filter.or([ sembast.Filter.equals('status', BunkerRequestStatus.pending.name), sembast.Filter.equals('status', BunkerRequestStatus.blocked.name), ]), ]; if (appPubkey != null) { filters.add( sembast.Filter.equals('originalRequest.appPubkey', appPubkey), ); } final finder = sembast.Finder(filter: sembast.Filter.and(filters)); final records = await store.find(db, finder: finder); return records .map((record) => BunkerRequest.fromJson(record.value)) .toList(); } Future saveApp(App app) async { var store = sembast.stringMapStoreFactory.store('apps'); await store.record(app.bunkerPubkey).put(db, app.toJson()); } Future saveApps() async { await Future.wait(bunker.apps.map((app) => saveApp(app))); } Future loadApps() async { var store = sembast.stringMapStoreFactory.store('apps'); final records = await store.find(db); bunker.apps = records.map((record) => App.fromJson(record.value)).toList(); } Future saveRelays() async { var store = sembast.StoreRef.main(); await store .record("default_bunker_relays") .put(db, bunker.defaultBunkerRelays); } Future loadRelays() async { var store = sembast.StoreRef.main(); final storedRelays = await store.record("default_bunker_relays").get(db); if (storedRelays == null) return; bunker.defaultBunkerRelays = List.from(storedRelays as List); } Future saveUsersPubkeys() async { var store = sembast.StoreRef.main(); await store.record("user_pubkeys").put(db, usersPubkeys.toList()); } Future loadUsersPubkeys() async { var store = sembast.StoreRef.main(); final storedUsersPubkeys = await store.record("user_pubkeys").get(db); if (storedUsersPubkeys == null) return; usersPubkeys.addAll(List.from(storedUsersPubkeys as List)); } Future saveAppLastUsed() async { var store = sembast.StoreRef.main(); await store.record("app_last_used").put(db, appLastUsed); } Future loadAppLastUsed() async { var store = sembast.StoreRef.main(); final storedAppLastUsed = await store.record("app_last_used").get(db); if (storedAppLastUsed == null) return; appLastUsed = Map.from(storedAppLastUsed as Map); } Future saveBunkerState() async { final storage = FlutterSecureStorage(); await Future.wait([ storage.write( key: StorageKeys.privateKeys, value: jsonEncode(bunker.privateKeys), ), saveApps(), saveRelays(), saveUsersPubkeys(), ]); } Future loadBunkerState() async { final storage = FlutterSecureStorage(); final storedPrivateKeys = await storage.read(key: StorageKeys.privateKeys); if (storedPrivateKeys == null) return; List privateKeys = List.from(jsonDecode(storedPrivateKeys)); for (var privateKey in privateKeys) { bunker.addPrivateKey(privateKey); } await Future.wait([ loadApps(), loadRelays(), loadUsersPubkeys(), loadAppLastUsed(), ]); } }