From 9dbb75dada6a69e1240393a5ee7c3c2c94af41af Mon Sep 17 00:00:00 2001 From: OfficialDakari Date: Thu, 13 Nov 2025 20:19:51 +0500 Subject: [PATCH] Follow up notification action isolation handling fix: possible database corruption by allow multiple instances --- lib/config/app_config.dart | 3 + lib/main.dart | 14 ++ lib/utils/background_push.dart | 32 ++-- .../flutter_hive_collections_database.txt | 149 ------------------ .../builder.dart | 8 +- .../notification_background_handler.dart | 5 +- 6 files changed, 44 insertions(+), 167 deletions(-) delete mode 100644 lib/utils/matrix_sdk_extensions/flutter_hive_collections_database.txt diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index c552835..631feef 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -3,6 +3,9 @@ import 'dart:ui'; import 'package:matrix/matrix.dart'; abstract class AppConfig { + static const String pushIsolatePortName = 'push_isolate'; + static const String mainIsolatePortName = 'main_isolate'; + static String _applicationName = 'Extera'; static String get applicationName => _applicationName; diff --git a/lib/main.dart b/lib/main.dart index 38151e7..e598192 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,3 +1,6 @@ +import 'dart:isolate'; +import 'dart:ui'; + import 'package:flutter/material.dart'; import 'package:collection/collection.dart'; @@ -15,9 +18,20 @@ import 'config/setting_keys.dart'; import 'utils/background_push.dart'; import 'widgets/fluffy_chat_app.dart'; +ReceivePort? mainIsolateReceivePort; + void main() async { Logs().i('Welcome to ${AppConfig.applicationName} <3'); + if (PlatformInfos.isAndroid) { + final port = mainIsolateReceivePort = ReceivePort(); + IsolateNameServer.removePortNameMapping('main_isolate'); + IsolateNameServer.registerPortWithName( + port.sendPort, + 'main_isolate', + ); + } + // Our background push shared isolate accesses flutter-internal things very early in the startup proccess // To make sure that the parts of flutter needed are started up already, we need to ensure that the // widget bindings are initialized already. diff --git a/lib/utils/background_push.dart b/lib/utils/background_push.dart index 75f6d3a..a3c3daf 100644 --- a/lib/utils/background_push.dart +++ b/lib/utils/background_push.dart @@ -23,6 +23,7 @@ import 'dart:io'; import 'dart:isolate'; import 'dart:ui'; +import 'package:extera_next/main.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -75,6 +76,20 @@ class BackgroundPush { void _init() async { try { + mainIsolateReceivePort?.listen( + (message) async { + try { + await notificationTap( + NotificationResponseJson.fromJsonString(message), + client: client, + router: FluffyChatApp.router, + l10n: l10n, + ); + } catch (e, s) { + Logs().wtf('Main Notification Tap crashed', e, s); + } + }, + ); if (PlatformInfos.isAndroid) { final port = ReceivePort(); IsolateNameServer.removePortNameMapping('background_tab_port'); @@ -103,11 +118,10 @@ class BackgroundPush { iOS: DarwinInitializationSettings(), ), onDidReceiveNotificationResponse: (response) => notificationTap( - response, - client: client, - router: FluffyChatApp.router, - l10n: l10n - ), + response, + client: client, + router: FluffyChatApp.router, + l10n: l10n), onDidReceiveBackgroundNotificationResponse: notificationTapBackground, ); Logs().v('Flutter Local Notifications initialized'); @@ -312,12 +326,8 @@ class BackgroundPush { final response = details.notificationResponse; if (response != null) { - notificationTap( - response, - client: client, - router: FluffyChatApp.router, - l10n: l10n - ); + notificationTap(response, + client: client, router: FluffyChatApp.router, l10n: l10n); } }); } diff --git a/lib/utils/matrix_sdk_extensions/flutter_hive_collections_database.txt b/lib/utils/matrix_sdk_extensions/flutter_hive_collections_database.txt deleted file mode 100644 index 9ad5ce5..0000000 --- a/lib/utils/matrix_sdk_extensions/flutter_hive_collections_database.txt +++ /dev/null @@ -1,149 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:flutter/foundation.dart' hide Key; -import 'package:flutter/services.dart'; - -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:hive/hive.dart'; -import 'package:matrix/matrix.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:universal_html/html.dart' as html; - -// ignore: deprecated_member_use -class FlutterHiveCollectionsDatabase extends HiveCollectionsDatabase { - FlutterHiveCollectionsDatabase( - super.name, - String super.path, { - super.key, - }); - - static const String _cipherStorageKey = 'hive_encryption_key'; - - static Future databaseBuilder( - Client client, - ) async { - Logs().d('Open Hive...'); - HiveAesCipher? hiverCipher; - try { - // Workaround for secure storage is calling Platform.operatingSystem on web - if (kIsWeb) { - // ignore: unawaited_futures - html.window.navigator.storage?.persist(); - throw MissingPluginException(); - } - - const secureStorage = FlutterSecureStorage(); - final containsEncryptionKey = - await secureStorage.read(key: _cipherStorageKey) != null; - if (!containsEncryptionKey) { - // do not try to create a buggy secure storage for new Linux users - if (Platform.isLinux) throw MissingPluginException(); - final key = Hive.generateSecureKey(); - await secureStorage.write( - key: _cipherStorageKey, - value: base64UrlEncode(key), - ); - } - - // workaround for if we just wrote to the key and it still doesn't exist - final rawEncryptionKey = await secureStorage.read(key: _cipherStorageKey); - if (rawEncryptionKey == null) throw MissingPluginException(); - - hiverCipher = HiveAesCipher(base64Url.decode(rawEncryptionKey)); - } on MissingPluginException catch (_) { - const FlutterSecureStorage() - .delete(key: _cipherStorageKey) - .catchError((_) {}); - Logs().i('Hive encryption is not supported on this platform'); - } catch (e, s) { - const FlutterSecureStorage() - .delete(key: _cipherStorageKey) - .catchError((_) {}); - Logs().w('Unable to init Hive encryption', e, s); - } - - final db = FlutterHiveCollectionsDatabase( - 'hive_collections_${client.clientName.replaceAll(' ', '_').toLowerCase()}', - await findDatabasePath(client), - key: hiverCipher, - ); - try { - await db.open(); - } catch (e, s) { - Logs().w('Unable to open Hive. Delete database and storage key...', e, s); - const FlutterSecureStorage().delete(key: _cipherStorageKey); - await db.clear().catchError((_) {}); - await Hive.deleteFromDisk(); - rethrow; - } - Logs().d('Hive is ready'); - return db; - } - - static Future findDatabasePath(Client client) async { - var path = client.clientName; - if (!kIsWeb) { - Directory directory; - try { - if (Platform.isLinux) { - directory = await getApplicationSupportDirectory(); - } else { - directory = await getApplicationDocumentsDirectory(); - } - } catch (_) { - try { - directory = await getLibraryDirectory(); - } catch (_) { - directory = Directory.current; - } - } - // do not destroy your stable FluffyChat in debug mode - directory = Directory( - directory.uri.resolve(kDebugMode ? 'hive_debug' : 'hive').toFilePath(), - ); - directory.create(recursive: true); - path = directory.path; - } - return path; - } - - @override - int get maxFileSize => supportsFileStoring ? 100 * 1000 * 1000 : 0; - @override - bool get supportsFileStoring => !kIsWeb; - - Future _getFileStoreDirectory() async { - try { - try { - return (await getTemporaryDirectory()).path; - } catch (_) { - return (await getApplicationDocumentsDirectory()).path; - } - } catch (_) { - return (await getDownloadsDirectory())!.path; - } - } - - @override - Future getFile(Uri mxcUri) async { - if (!supportsFileStoring) return null; - final tempDirectory = await _getFileStoreDirectory(); - final file = - File('$tempDirectory/${Uri.encodeComponent(mxcUri.toString())}'); - if (await file.exists() == false) return null; - final bytes = await file.readAsBytes(); - return bytes; - } - - @override - Future storeFile(Uri mxcUri, Uint8List bytes, int time) async { - if (!supportsFileStoring) return null; - final tempDirectory = await _getFileStoreDirectory(); - final file = - File('$tempDirectory/${Uri.encodeComponent(mxcUri.toString())}'); - if (await file.exists()) return; - await file.writeAsBytes(bytes); - return; - } -} diff --git a/lib/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart b/lib/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart index 351339a..6635d19 100644 --- a/lib/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart +++ b/lib/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart @@ -110,10 +110,10 @@ Future _constructDatabase(String clientName) async { final database = await factory.openDatabase( path, options: OpenDatabaseOptions( - version: 1, - // most important : apply encryption when opening the DB - onConfigure: helper?.applyPragmaKey, - singleInstance: false), + version: 1, + // most important : apply encryption when opening the DB + onConfigure: helper?.applyPragmaKey, + ), ); return await MatrixSdkDatabase.init( diff --git a/lib/utils/notification_background_handler.dart b/lib/utils/notification_background_handler.dart index bf46915..43ae6c1 100644 --- a/lib/utils/notification_background_handler.dart +++ b/lib/utils/notification_background_handler.dart @@ -46,11 +46,10 @@ extension NotificationResponseJson on NotificationResponse { void notificationTapBackground( NotificationResponse notificationResponse, ) async { - Logs().i('Notification tap in background'); - - final sendPort = IsolateNameServer.lookupPortByName('background_tab_port'); + final sendPort = IsolateNameServer.lookupPortByName(AppConfig.mainIsolatePortName); if (sendPort != null) { sendPort.send(notificationResponse.toJsonString()); + Logs().i('Notification tap sent to main isolate'); return; }