diff --git a/CHANGELOG.md b/CHANGELOG.md index 9228b98eb..6763f13f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ and this project adheres to Calendar Versioning (CalVer). ### Removed ### Fixed +- Fix deleting all app data reusing a closed Whitenoise database instance [PR #662](https://github.com/marmot-protocol/whitenoise/pull/662) - Fixed QR scanner not showing camera on first attempt after giving permission [PR #654](https://github.com/marmot-protocol/whitenoise/pull/654) - Fix show author name in last message of pending invites in chat list [PR #668](https://github.com/marmot-protocol/whitenoise/pull/668) diff --git a/lib/hooks/use_delete_all_data.dart b/lib/hooks/use_delete_all_data.dart index 5db8233ec..98e97eef8 100644 --- a/lib/hooks/use_delete_all_data.dart +++ b/lib/hooks/use_delete_all_data.dart @@ -1,12 +1,14 @@ -import 'dart:async'; - import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:logging/logging.dart'; import 'package:whitenoise/src/rust/api.dart' as api; +import 'package:whitenoise/utils/reset_marker.dart'; final _logger = Logger('useDeleteAllData'); -const defaultTimeout = Duration(seconds: 10); +enum DeleteAllDataFailure { + deleteFailed, + reinitializeFailed, +} class DeleteAllDataState { final bool isDeleting; @@ -27,12 +29,16 @@ class DeleteAllDataState { ({ DeleteAllDataState state, Future Function() deleteAllData, + DeleteAllDataFailure? Function() latestFailure, + Object? Function() latestError, + StackTrace? Function() latestStackTrace, }) -useDeleteAllData({ - Duration timeout = defaultTimeout, -}) { +useDeleteAllData() { final state = useState(const DeleteAllDataState()); final isMountedRef = useRef(true); + final latestFailureRef = useRef(null); + final latestErrorRef = useRef(null); + final latestStackTraceRef = useRef(null); useEffect(() { return () { @@ -42,22 +48,37 @@ useDeleteAllData({ Future deleteAllData() async { state.value = state.value.copyWith(isDeleting: true); + latestFailureRef.value = null; + latestErrorRef.value = null; + latestStackTraceRef.value = null; try { _logger.info('Deleting all application data'); - await api.deleteAllData().timeout(timeout); - _logger.info('All data deleted successfully'); + await markResetPending(); + await api.deleteAllData(); + } catch (e, stackTrace) { + latestFailureRef.value = DeleteAllDataFailure.deleteFailed; + latestErrorRef.value = e; + latestStackTraceRef.value = stackTrace; + _logger.severe('Failed to delete all data', e, stackTrace); if (isMountedRef.value) { state.value = state.value.copyWith(isDeleting: false); } - return true; - } on TimeoutException catch (e, stackTrace) { - _logger.severe('Delete all data timed out', e, stackTrace); + return false; + } + + try { + await api.reinitializeWhitenoise(); + await clearResetPending(); + _logger.info('All data deleted successfully'); if (isMountedRef.value) { state.value = state.value.copyWith(isDeleting: false); } - return false; + return true; } catch (e, stackTrace) { - _logger.severe('Failed to delete all data', e, stackTrace); + latestFailureRef.value = DeleteAllDataFailure.reinitializeFailed; + latestErrorRef.value = e; + latestStackTraceRef.value = stackTrace; + _logger.severe('Data deleted, but failed to reinitialize Whitenoise', e, stackTrace); if (isMountedRef.value) { state.value = state.value.copyWith(isDeleting: false); } @@ -68,5 +89,8 @@ useDeleteAllData({ return ( state: state.value, deleteAllData: deleteAllData, + latestFailure: () => latestFailureRef.value, + latestError: () => latestErrorRef.value, + latestStackTrace: () => latestStackTraceRef.value, ); } diff --git a/lib/main.dart b/lib/main.dart index 8b2a2793b..f0eaab89b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -25,6 +25,7 @@ import 'package:whitenoise/screens/fatal_error_screen.dart'; import 'package:whitenoise/src/rust/api.dart' as rust_api; import 'package:whitenoise/src/rust/frb_generated.dart'; import 'package:whitenoise/theme.dart'; +import 'package:whitenoise/utils/reset_marker.dart'; // TODO: Remove migration gate and related code in the next release. const kDataVersion = 1; @@ -65,6 +66,7 @@ Future initializeAppContainer() async { final dir = await getApplicationDocumentsDirectory(); final dataDir = '${dir.path}/whitenoise/data'; final logsDir = '${dir.path}/whitenoise/logs'; + await recoverPendingReset(dataDir: dataDir, logsDir: logsDir); await Directory(dataDir).create(recursive: true); await Directory(logsDir).create(recursive: true); diff --git a/lib/screens/privacy_security_screen.dart b/lib/screens/privacy_security_screen.dart index 7aefbb6f6..93d3ee883 100644 --- a/lib/screens/privacy_security_screen.dart +++ b/lib/screens/privacy_security_screen.dart @@ -6,6 +6,7 @@ import 'package:whitenoise/hooks/use_system_notice.dart'; import 'package:whitenoise/l10n/l10n.dart'; import 'package:whitenoise/providers/auth_provider.dart'; import 'package:whitenoise/routes.dart'; +import 'package:whitenoise/screens/fatal_error_screen.dart'; import 'package:whitenoise/theme.dart'; import 'package:whitenoise/widgets/wn_button.dart'; import 'package:whitenoise/widgets/wn_confirmation_slate.dart'; @@ -21,7 +22,8 @@ class PrivacySecurityScreen extends HookConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final colors = context.colors; final typography = context.typographyScaled; - final (:state, :deleteAllData) = useDeleteAllData(); + final (:state, :deleteAllData, :latestFailure, :latestError, :latestStackTrace) = + useDeleteAllData(); final systemNotice = useSystemNotice(); Future handleDeleteAllData() async { @@ -46,6 +48,19 @@ class PrivacySecurityScreen extends HookConsumerWidget { } }); } else { + if (latestFailure() == DeleteAllDataFailure.reinitializeFailed) { + final error = latestError(); + await Navigator.of(context, rootNavigator: true).pushAndRemoveUntil( + MaterialPageRoute( + builder: (_) => FatalErrorScreen( + errorMessage: error?.toString() ?? 'Whitenoise reinitialization failed', + stackTrace: latestStackTrace(), + ), + ), + (_) => false, + ); + return; + } systemNotice.showErrorNotice(context.l10n.deleteAllDataError); } } diff --git a/lib/src/rust/api.dart b/lib/src/rust/api.dart index 701dda16e..6aedff29a 100644 --- a/lib/src/rust/api.dart +++ b/lib/src/rust/api.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import @@ -8,8 +8,9 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; import 'api/error.dart'; import 'frb_generated.dart'; -// These functions are ignored because they are not marked as `pub`: `wn_session`, `wn` -// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `fmt`, `from` +// These functions are ignored because they are not marked as `pub`: `new_whitenoise_with_timeout`, `release_lifecycle`, `to_core_config`, `wn_session`, `wn` +// These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `WnHandle`, `WnSessionHandle` +// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `deref`, `deref`, `fmt`, `from` /// Creates a `WhitenoiseConfig` object from string directory paths. /// @@ -44,6 +45,13 @@ Future createWhitenoiseConfig({ Future initializeWhitenoise({required WhitenoiseConfig config}) => RustLib.instance.api.crateApiInitializeWhitenoise(config: config); +Future reinitializeWhitenoise() => RustLib.instance.api.crateApiReinitializeWhitenoise(); + +/// Wipes all on-disk data and clears the process-global Whitenoise instance. +/// The lifecycle write lock waits for in-flight bridge calls before the old +/// database pool is closed, then blocks new bridge calls until the global +/// instance has been cleared. Call `initialize_whitenoise` to install a fresh +/// instance after the reset. Future deleteAllData() => RustLib.instance.api.crateApiDeleteAllData(); Future getAppSettings() => RustLib.instance.api.crateApiGetAppSettings(); diff --git a/lib/src/rust/api/account_groups.dart b/lib/src/rust/api/account_groups.dart index 0ded683f4..1ed88d55f 100644 --- a/lib/src/rust/api/account_groups.dart +++ b/lib/src/rust/api/account_groups.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/accounts.dart b/lib/src/rust/api/accounts.dart index f784a4573..78bec81c9 100644 --- a/lib/src/rust/api/accounts.dart +++ b/lib/src/rust/api/accounts.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/bug_report.dart b/lib/src/rust/api/bug_report.dart index 24bece932..d65379d50 100644 --- a/lib/src/rust/api/bug_report.dart +++ b/lib/src/rust/api/bug_report.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/chat_list.dart b/lib/src/rust/api/chat_list.dart index 5512721f3..efb350148 100644 --- a/lib/src/rust/api/chat_list.dart +++ b/lib/src/rust/api/chat_list.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/chat_summary.dart b/lib/src/rust/api/chat_summary.dart index 489267b94..5f38c2875 100644 --- a/lib/src/rust/api/chat_summary.dart +++ b/lib/src/rust/api/chat_summary.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/drafts.dart b/lib/src/rust/api/drafts.dart index cbacbdc62..4583f10aa 100644 --- a/lib/src/rust/api/drafts.dart +++ b/lib/src/rust/api/drafts.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/error.dart b/lib/src/rust/api/error.dart index 538f1e19f..453efb6c2 100644 --- a/lib/src/rust/api/error.dart +++ b/lib/src/rust/api/error.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/group_state.dart b/lib/src/rust/api/group_state.dart index af4eaab43..d20e5398c 100644 --- a/lib/src/rust/api/group_state.dart +++ b/lib/src/rust/api/group_state.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/groups.dart b/lib/src/rust/api/groups.dart index fdc9687c3..1e13686c3 100644 --- a/lib/src/rust/api/groups.dart +++ b/lib/src/rust/api/groups.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/logs.dart b/lib/src/rust/api/logs.dart index 9d29da993..fe637907b 100644 --- a/lib/src/rust/api/logs.dart +++ b/lib/src/rust/api/logs.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/markdown.dart b/lib/src/rust/api/markdown.dart index 0b5a2df36..c1c6627a5 100644 --- a/lib/src/rust/api/markdown.dart +++ b/lib/src/rust/api/markdown.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/media_files.dart b/lib/src/rust/api/media_files.dart index c5b9b31de..b8478a189 100644 --- a/lib/src/rust/api/media_files.dart +++ b/lib/src/rust/api/media_files.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/messages.dart b/lib/src/rust/api/messages.dart index b6c53fcfd..5f4147d2c 100644 --- a/lib/src/rust/api/messages.dart +++ b/lib/src/rust/api/messages.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/metadata.dart b/lib/src/rust/api/metadata.dart index 88333b09f..fa2ae712c 100644 --- a/lib/src/rust/api/metadata.dart +++ b/lib/src/rust/api/metadata.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/mute_list.dart b/lib/src/rust/api/mute_list.dart index f3544a721..3c0d6d5e2 100644 --- a/lib/src/rust/api/mute_list.dart +++ b/lib/src/rust/api/mute_list.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/notifications.dart b/lib/src/rust/api/notifications.dart index 4b22bb774..847a9274f 100644 --- a/lib/src/rust/api/notifications.dart +++ b/lib/src/rust/api/notifications.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/relay_defaults.dart b/lib/src/rust/api/relay_defaults.dart index d9e01587a..2f47e3db0 100644 --- a/lib/src/rust/api/relay_defaults.dart +++ b/lib/src/rust/api/relay_defaults.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/relays.dart b/lib/src/rust/api/relays.dart index 4c71f54ac..3cfa6adea 100644 --- a/lib/src/rust/api/relays.dart +++ b/lib/src/rust/api/relays.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/signer.dart b/lib/src/rust/api/signer.dart index b0c1809a9..f9c5387aa 100644 --- a/lib/src/rust/api/signer.dart +++ b/lib/src/rust/api/signer.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/user_search.dart b/lib/src/rust/api/user_search.dart index 6f3ef1ab5..6d36a7494 100644 --- a/lib/src/rust/api/user_search.dart +++ b/lib/src/rust/api/user_search.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/users.dart b/lib/src/rust/api/users.dart index 81c88ebdd..116ed575b 100644 --- a/lib/src/rust/api/users.dart +++ b/lib/src/rust/api/users.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/utils.dart b/lib/src/rust/api/utils.dart index a4cfc6802..63f12d4ff 100644 --- a/lib/src/rust/api/utils.dart +++ b/lib/src/rust/api/utils.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/api/zapstore.dart b/lib/src/rust/api/zapstore.dart index 4078bfa06..68979f9de 100644 --- a/lib/src/rust/api/zapstore.dart +++ b/lib/src/rust/api/zapstore.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/src/rust/frb_generated.dart b/lib/src/rust/frb_generated.dart index 450f178ac..32486aa06 100644 --- a/lib/src/rust/frb_generated.dart +++ b/lib/src/rust/frb_generated.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field @@ -88,10 +88,10 @@ class RustLib extends BaseEntrypoint { kDefaultExternalLibraryLoaderConfig; @override - String get codegenVersion => '2.11.1'; + String get codegenVersion => '2.12.0'; @override - int get rustContentHash => -1064693455; + int get rustContentHash => -1547864171; static const kDefaultExternalLibraryLoaderConfig = ExternalLibraryLoaderConfig( stem: 'rust_lib_whitenoise', @@ -489,6 +489,8 @@ abstract class RustLibApi extends BaseApi { required FutureOr Function(String, String) nip44Decrypt, }); + Future crateApiReinitializeWhitenoise(); + Future crateApiRelaysRelayTypeInbox(); Future crateApiRelaysRelayTypeKeyPackage(); @@ -3954,7 +3956,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ); @override - Future crateApiRelaysRelayTypeInbox() { + Future crateApiReinitializeWhitenoise() { return handler.executeNormal( NormalTask( callFfi: (port_) { @@ -3966,6 +3968,35 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { port: port_, ); }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_api_error, + ), + constMeta: kCrateApiReinitializeWhitenoiseConstMeta, + argValues: [], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateApiReinitializeWhitenoiseConstMeta => const TaskConstMeta( + debugName: 'reinitialize_whitenoise', + argNames: [], + ); + + @override + Future crateApiRelaysRelayTypeInbox() { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 98, + port: port_, + ); + }, codec: SseCodec( decodeSuccessData: sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerRelayType, @@ -3992,7 +4023,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 98, + funcId: 99, port: port_, ); }, @@ -4022,7 +4053,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 99, + funcId: 100, port: port_, ); }, @@ -4053,7 +4084,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 100, + funcId: 101, port: port_, ); }, @@ -4093,7 +4124,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 101, + funcId: 102, port: port_, ); }, @@ -4129,7 +4160,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 102, + funcId: 103, port: port_, ); }, @@ -4159,7 +4190,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 103, + funcId: 104, port: port_, ); }, @@ -4195,7 +4226,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 104, + funcId: 105, port: port_, ); }, @@ -4235,7 +4266,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 105, + funcId: 106, port: port_, ); }, @@ -4271,7 +4302,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 106, + funcId: 107, port: port_, ); }, @@ -4309,7 +4340,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 107, + funcId: 108, port: port_, ); }, @@ -4350,7 +4381,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 108, + funcId: 109, port: port_, ); }, @@ -4392,7 +4423,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 109, + funcId: 110, port: port_, ); }, @@ -4440,7 +4471,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 110, + funcId: 111, port: port_, ); }, @@ -4503,7 +4534,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 111, + funcId: 112, port: port_, ); }, @@ -4539,7 +4570,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 112, + funcId: 113, port: port_, ); }, @@ -4572,7 +4603,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 113, + funcId: 114, port: port_, ); }, @@ -4607,7 +4638,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 114, + funcId: 115, port: port_, ); }, @@ -4644,7 +4675,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 115, + funcId: 116, port: port_, ); }, @@ -4683,7 +4714,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 116, + funcId: 117, port: port_, ); }, @@ -4722,7 +4753,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 117, + funcId: 118, port: port_, ); }, @@ -4756,7 +4787,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 118, + funcId: 119, port: port_, ); }, @@ -4793,7 +4824,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 119, + funcId: 120, port: port_, ); }, @@ -4830,7 +4861,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 120, + funcId: 121, port: port_, ); }, @@ -4862,7 +4893,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 121, + funcId: 122, port: port_, ); }, @@ -4892,7 +4923,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 122, + funcId: 123, )!; }, codec: SseCodec( @@ -4921,7 +4952,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 123, + funcId: 124, )!; }, codec: SseCodec( @@ -4950,7 +4981,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 124, + funcId: 125, )!; }, codec: SseCodec( @@ -4983,7 +5014,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { return pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 125, + funcId: 126, )!; }, codec: SseCodec( @@ -5016,7 +5047,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 126, + funcId: 127, port: port_, ); }, @@ -5050,7 +5081,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 127, + funcId: 128, port: port_, ); }, @@ -5084,7 +5115,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 128, + funcId: 129, port: port_, ); }, @@ -5118,7 +5149,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 129, + funcId: 130, port: port_, ); }, @@ -5152,7 +5183,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 130, + funcId: 131, port: port_, ); }, @@ -5185,7 +5216,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 131, + funcId: 132, port: port_, ); }, @@ -5219,7 +5250,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 132, + funcId: 133, port: port_, ); }, @@ -5252,7 +5283,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 133, + funcId: 134, port: port_, ); }, @@ -5290,7 +5321,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 134, + funcId: 135, port: port_, ); }, @@ -5326,7 +5357,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 135, + funcId: 136, port: port_, ); }, @@ -5364,7 +5395,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 136, + funcId: 137, port: port_, ); }, @@ -5404,7 +5435,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 137, + funcId: 138, port: port_, ); }, @@ -5444,7 +5475,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 138, + funcId: 139, port: port_, ); }, @@ -5478,7 +5509,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 139, + funcId: 140, port: port_, ); }, @@ -5517,7 +5548,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 140, + funcId: 141, port: port_, ); }, @@ -5549,7 +5580,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { pdeCallFfi( generalizedFrbRustBinding, serializer, - funcId: 141, + funcId: 142, port: port_, ); }, diff --git a/lib/src/rust/frb_generated.io.dart b/lib/src/rust/frb_generated.io.dart index 2dcef3e9a..40b929c25 100644 --- a/lib/src/rust/frb_generated.io.dart +++ b/lib/src/rust/frb_generated.io.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field diff --git a/lib/src/rust/lib.dart b/lib/src/rust/lib.dart index 5131749dc..af0ead60b 100644 --- a/lib/src/rust/lib.dart +++ b/lib/src/rust/lib.dart @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. // ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import diff --git a/lib/utils/reset_marker.dart b/lib/utils/reset_marker.dart new file mode 100644 index 000000000..f29ffb9bb --- /dev/null +++ b/lib/utils/reset_marker.dart @@ -0,0 +1,83 @@ +// ignore_for_file: avoid_slow_async_io + +import 'dart:io' show Directory, File; + +import 'package:flutter/foundation.dart' show visibleForTesting; +import 'package:flutter_foreground_task/flutter_foreground_task.dart' show FlutterForegroundTask; +import 'package:flutter_secure_storage/flutter_secure_storage.dart' show FlutterSecureStorage; +import 'package:path_provider/path_provider.dart' show getApplicationDocumentsDirectory; + +const kResetPendingFileName = 'reset_pending'; + +@visibleForTesting +Future Function()? debugResetPendingMarkerFile; + +@visibleForTesting +Future Function()? debugMarkResetPending; + +@visibleForTesting +Future Function()? debugClearResetPending; + +Future resetPendingMarkerFile() async { + final debugMarkerFile = debugResetPendingMarkerFile; + if (debugMarkerFile != null) { + return debugMarkerFile(); + } + + final dir = await getApplicationDocumentsDirectory(); + return File('${dir.path}/whitenoise/$kResetPendingFileName'); +} + +Future markResetPending() async { + final debugMark = debugMarkResetPending; + if (debugMark != null) { + await debugMark(); + return; + } + + final marker = await resetPendingMarkerFile(); + await marker.parent.create(recursive: true); + await marker.writeAsString(DateTime.now().toUtc().toIso8601String(), flush: true); +} + +Future clearResetPending() async { + final debugClear = debugClearResetPending; + if (debugClear != null) { + await debugClear(); + return; + } + + final marker = await resetPendingMarkerFile(); + if (await marker.exists()) { + await marker.delete(); + } +} + +Future recoverPendingReset({ + required String dataDir, + required String logsDir, + Future Function()? clearSecureStorage, + Future Function()? clearForegroundTaskData, +}) async { + final marker = await resetPendingMarkerFile(); + if (!await marker.exists()) return; + + for (final path in [dataDir, logsDir]) { + final dir = Directory(path); + if (await dir.exists()) { + await dir.delete(recursive: true); + } + } + + final clearStorage = + clearSecureStorage ?? + () async { + const secureStorage = FlutterSecureStorage(); + await secureStorage.deleteAll(); + }; + await clearStorage(); + // Foreground-task plugin state can contain account-bound service metadata; + // clear it with the rest of the local reset surface. + await (clearForegroundTaskData ?? FlutterForegroundTask.clearAllData)(); + await clearResetPending(); +} diff --git a/pubspec.lock b/pubspec.lock index 783eaf523..922a0900b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -468,10 +468,10 @@ packages: dependency: "direct main" description: name: flutter_rust_bridge - sha256: "37ef40bc6f863652e865f0b2563ea07f0d3c58d8efad803cc01933a4b2ee067e" + sha256: e87d6b9ee934dcd24a128ccb2bd91905d2d5fe5c06245d6a8f5477d4907a437a url: "https://pub.dev" source: hosted - version: "2.11.1" + version: "2.12.0" flutter_screenutil: dependency: "direct main" description: @@ -1698,4 +1698,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.11.1 <3.16.0" - flutter: ">=3.41.4" + flutter: ">=3.41.4 <4.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 85e7dfd92..f0b5eef69 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,7 +36,7 @@ dependencies: intl: ^0.20.2 rust_lib_whitenoise: path: rust_builder - flutter_rust_bridge: 2.11.1 + flutter_rust_bridge: 2.12.0 logging: ^1.2.0 freezed_annotation: ^3.1.0 connectivity_plus: ^7.1.0 diff --git a/rust/Cargo.lock b/rust/Cargo.lock index d0bad148d..1f64eae4d 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1326,9 +1326,9 @@ dependencies = [ [[package]] name = "flutter_rust_bridge" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde126295b2acc5f0a712e265e91b6fdc0ed38767496483e592ae7134db83725" +checksum = "a0884853aae8a6517b5b58cf36f55da487f2fe110e1686938eb29b6640aae4a5" dependencies = [ "allo-isolate", "android_logger", @@ -1356,9 +1356,9 @@ dependencies = [ [[package]] name = "flutter_rust_bridge_macros" -version = "2.11.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f0420326b13675321b194928bb7830043b68cf8b810e1c651285c747abb080" +checksum = "6b5ce32f35f710ced8c5aa557f023f1a624e737b5460cee2b70fcd3a8df09e1b" dependencies = [ "hex", "md-5", diff --git a/rust/Cargo.toml b/rust/Cargo.toml index ff77c42cc..894958ed7 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -9,7 +9,7 @@ crate-type = ["cdylib", "staticlib"] [dependencies] anyhow = "1.0.99" chrono = { version = "0.4.40", features = ["serde"] } -flutter_rust_bridge = { version = "=2.11.1", features = ["chrono"] } +flutter_rust_bridge = { version = "=2.12.0", features = ["chrono"] } hex = "0.4" nostr-sdk = { version = "0.44", features = [ "nip04", diff --git a/rust/src/api/account_groups.rs b/rust/src/api/account_groups.rs index c8514b41b..be32fd95a 100644 --- a/rust/src/api/account_groups.rs +++ b/rust/src/api/account_groups.rs @@ -79,7 +79,7 @@ pub async fn accept_account_group( let pubkey = PublicKey::parse(&account_pubkey)?; let group_id_bytes = ::hex::decode(&mls_group_id)?; let group_id = GroupId::from_slice(&group_id_bytes); - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let updated = session.membership().for_group(&group_id).accept().await?; Ok(updated.into()) } @@ -94,7 +94,7 @@ pub async fn decline_account_group( let pubkey = PublicKey::parse(&account_pubkey)?; let group_id_bytes = ::hex::decode(&mls_group_id)?; let group_id = GroupId::from_slice(&group_id_bytes); - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let updated = session.membership().for_group(&group_id).decline().await?; Ok(updated.into()) } @@ -107,7 +107,7 @@ pub async fn get_account_group( let pubkey = PublicKey::parse(&account_pubkey)?; let group_id_bytes = ::hex::decode(&mls_group_id)?; let group_id = GroupId::from_slice(&group_id_bytes); - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let (ag, _) = session .membership() .for_group(&group_id) @@ -123,7 +123,7 @@ pub async fn get_dm_group_with_peer( ) -> Result, ApiError> { let pubkey = PublicKey::parse(&account_pubkey)?; let peer = PublicKey::parse(&peer_pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let group_id = session.membership().dm_group_with_peer(&peer).await?; Ok(group_id.map(|id| group_id_to_string(&id))) } @@ -133,7 +133,7 @@ pub async fn archive_chat(account_pubkey: String, mls_group_id: String) -> Resul let pubkey = PublicKey::parse(&account_pubkey)?; let group_id_bytes = ::hex::decode(&mls_group_id)?; let group_id = GroupId::from_slice(&group_id_bytes); - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; session.membership().for_group(&group_id).archive().await?; Ok(()) } @@ -143,7 +143,7 @@ pub async fn unarchive_chat(account_pubkey: String, mls_group_id: String) -> Res let pubkey = PublicKey::parse(&account_pubkey)?; let group_id_bytes = ::hex::decode(&mls_group_id)?; let group_id = GroupId::from_slice(&group_id_bytes); - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; session .membership() .for_group(&group_id) @@ -163,7 +163,7 @@ pub async fn mark_message_read( ) -> Result { let pubkey = PublicKey::parse(&account_pubkey)?; let event_id = EventId::from_hex(&message_id)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let updated = session.membership().mark_message_read(&event_id).await?; Ok(updated.into()) } diff --git a/rust/src/api/accounts.rs b/rust/src/api/accounts.rs index c1f9ae7c2..ddca19b86 100644 --- a/rust/src/api/accounts.rs +++ b/rust/src/api/accounts.rs @@ -126,14 +126,14 @@ impl From for FlutterEvent { #[frb] pub async fn get_accounts() -> Result, ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let accounts = whitenoise.all_accounts().await?; Ok(accounts.into_iter().map(|a| a.into()).collect()) } #[frb] pub async fn get_account(pubkey: String) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let account = whitenoise.find_account_by_pubkey(&pubkey).await?; Ok(account.into()) @@ -141,7 +141,7 @@ pub async fn get_account(pubkey: String) -> Result { #[frb] pub async fn create_identity() -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let account = whitenoise.create_identity().await?; Ok(account.into()) } @@ -152,14 +152,14 @@ pub async fn create_identity() -> Result { #[frb] pub async fn login_start(nsec_or_hex_privkey: String) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let result = whitenoise.login_start(nsec_or_hex_privkey).await?; Ok(result.into()) } #[frb] pub async fn login_publish_default_relays(pubkey: String) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let result = whitenoise.login_publish_default_relays(&pubkey).await?; Ok(result.into()) @@ -170,7 +170,7 @@ pub async fn login_with_custom_relay( pubkey: String, relay_url: String, ) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let relay_url = RelayUrl::parse(&relay_url)?; let result = whitenoise @@ -181,7 +181,7 @@ pub async fn login_with_custom_relay( #[frb] pub async fn login_cancel(pubkey: String) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; whitenoise.login_cancel(&pubkey).await?; Ok(()) @@ -189,14 +189,14 @@ pub async fn login_cancel(pubkey: String) -> Result<(), ApiError> { #[frb] pub async fn logout(pubkey: String) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; whitenoise.logout(&pubkey).await.map_err(ApiError::from) } #[frb] pub async fn export_account_nsec(pubkey: String) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let account = whitenoise.find_account_by_pubkey(&pubkey).await?; whitenoise @@ -210,11 +210,11 @@ pub async fn update_account_metadata( pubkey: String, metadata: &FlutterMetadata, ) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let account = whitenoise.find_account_by_pubkey(&pubkey).await?; account - .update_metadata(&metadata.into(), whitenoise) + .update_metadata(&metadata.into(), &whitenoise) .await .map_err(ApiError::from) } @@ -226,7 +226,7 @@ pub async fn upload_account_profile_picture( file_path: String, image_type: String, ) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let image_type = ImageType::try_from(image_type).map_err(|error| ApiError::Other { message: error.to_string(), @@ -243,7 +243,7 @@ pub async fn upload_account_profile_picture( #[frb] pub async fn account_relays(pubkey: String, relay_type: RelayType) -> Result, ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let account = whitenoise.find_account_by_pubkey(&pubkey).await?; let relays = account.relays(relay_type, &whitenoise.shared).await?; @@ -252,7 +252,7 @@ pub async fn account_relays(pubkey: String, relay_type: RelayType) -> Result Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let account = whitenoise.find_account_by_pubkey(&pubkey).await?; @@ -262,13 +262,13 @@ pub async fn restore_default_relays(pubkey: String) -> Result<(), ApiError> { .await?; for relay in ¤t_relays { account - .remove_relay(relay, relay_type.clone(), whitenoise) + .remove_relay(relay, relay_type.clone(), &whitenoise) .await?; } for relay_url in default_relay_urls_parsed()? { let relay = whitenoise.find_or_create_relay_by_url(&relay_url).await?; account - .add_relay(&relay, relay_type.clone(), whitenoise) + .add_relay(&relay, relay_type.clone(), &whitenoise) .await?; } } @@ -282,13 +282,13 @@ pub async fn add_account_relay( url: String, relay_type: RelayType, ) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let account = whitenoise.find_account_by_pubkey(&pubkey).await?; let relay_url = RelayUrl::parse(&url)?; let relay = whitenoise.find_or_create_relay_by_url(&relay_url).await?; account - .add_relay(&relay, relay_type, whitenoise) + .add_relay(&relay, relay_type, &whitenoise) .await .map_err(ApiError::from) } @@ -299,20 +299,20 @@ pub async fn remove_account_relay( url: String, relay_type: RelayType, ) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let account = whitenoise.find_account_by_pubkey(&pubkey).await?; let relay_url = RelayUrl::parse(&url)?; let relay = whitenoise.find_or_create_relay_by_url(&relay_url).await?; account - .remove_relay(&relay, relay_type, whitenoise) + .remove_relay(&relay, relay_type, &whitenoise) .await .map_err(ApiError::from) } #[frb] pub async fn account_key_package(pubkey: String) -> Result, ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let user = whitenoise.find_user_by_pubkey(&pubkey).await?; let event = user.key_package_event(&whitenoise.shared).await?; @@ -322,7 +322,7 @@ pub async fn account_key_package(pubkey: String) -> Result, #[frb] pub async fn account_key_packages(account_pubkey: String) -> Result, ApiError> { let pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let key_packages = session.key_packages().fetch_all().await?; Ok(key_packages.into_iter().map(|e| e.into()).collect()) } @@ -330,7 +330,7 @@ pub async fn account_key_packages(account_pubkey: String) -> Result Result<(), ApiError> { let pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; session .key_packages() .publish() @@ -344,7 +344,7 @@ pub async fn delete_account_key_package( key_package_id: String, ) -> Result { let pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let key_package_id = EventId::parse(&key_package_id)?; session .key_packages() @@ -356,7 +356,7 @@ pub async fn delete_account_key_package( #[frb] pub async fn delete_account_key_packages(account_pubkey: String) -> Result { let pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let deleted_count = session.key_packages().delete_legacy().await?; Ok(deleted_count) } @@ -364,7 +364,7 @@ pub async fn delete_account_key_packages(account_pubkey: String) -> Result Result, ApiError> { let pubkey = PublicKey::parse(&pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let follows = session.social().follows().await?; Ok(follows.into_iter().map(|u| u.into()).collect()) } @@ -375,7 +375,7 @@ pub async fn follow_user( user_to_follow_pubkey: String, ) -> Result<(), ApiError> { let pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let user_to_follow_pubkey = PublicKey::parse(&user_to_follow_pubkey)?; session .social() @@ -390,7 +390,7 @@ pub async fn unfollow_user( user_to_unfollow_pubkey: String, ) -> Result<(), ApiError> { let pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let user_to_unfollow_pubkey = PublicKey::parse(&user_to_unfollow_pubkey)?; session .social() @@ -405,7 +405,7 @@ pub async fn is_following_user( user_pubkey: String, ) -> Result { let pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let user_pubkey = PublicKey::parse(&user_pubkey)?; session .social() @@ -430,7 +430,7 @@ impl From for AccountSettings { #[frb] pub async fn account_settings(pubkey: String) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let session = whitenoise .session(&pubkey) @@ -446,7 +446,7 @@ pub async fn update_notifications_enabled( pubkey: String, enabled: bool, ) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let session = whitenoise .session(&pubkey) diff --git a/rust/src/api/chat_list.rs b/rust/src/api/chat_list.rs index eda4219b8..ad8a6ad03 100644 --- a/rust/src/api/chat_list.rs +++ b/rust/src/api/chat_list.rs @@ -137,7 +137,7 @@ pub async fn set_chat_pin_order( let pubkey = PublicKey::parse(&account_pubkey)?; let group_id_bytes = hex::decode(&mls_group_id)?; let group_id = GroupId::from_slice(&group_id_bytes); - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; session .membership() @@ -161,7 +161,7 @@ pub async fn mute_chat( let pubkey = PublicKey::parse(&account_pubkey)?; let group_id_bytes = hex::decode(&mls_group_id)?; let group_id = GroupId::from_slice(&group_id_bytes); - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; session .membership() @@ -180,7 +180,7 @@ pub async fn unmute_chat(account_pubkey: String, mls_group_id: String) -> Result let pubkey = PublicKey::parse(&account_pubkey)?; let group_id_bytes = hex::decode(&mls_group_id)?; let group_id = GroupId::from_slice(&group_id_bytes); - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; session.membership().for_group(&group_id).unmute().await?; @@ -196,7 +196,7 @@ pub async fn unmute_chat(account_pubkey: String, mls_group_id: String) -> Result #[frb] pub async fn get_chat_list(account_pubkey: String) -> Result, ApiError> { let pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let chat_list = session.chat_list().active().await?; Ok(chat_list.into_iter().map(|item| item.into()).collect()) } @@ -213,7 +213,7 @@ pub async fn subscribe_to_chat_list( account_pubkey: String, sink: StreamSink, ) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&account_pubkey)?; let account = whitenoise.find_account_by_pubkey(&pubkey).await?; @@ -237,6 +237,7 @@ pub async fn subscribe_to_chat_list( // Stream real-time updates let mut rx = subscription.updates; + whitenoise.release_lifecycle(); loop { match rx.recv().await { Ok(update) => { @@ -266,7 +267,7 @@ pub async fn subscribe_to_archived_chat_list( account_pubkey: String, sink: StreamSink, ) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&account_pubkey)?; let account = whitenoise.find_account_by_pubkey(&pubkey).await?; @@ -288,6 +289,7 @@ pub async fn subscribe_to_archived_chat_list( } let mut rx = subscription.updates; + whitenoise.release_lifecycle(); loop { match rx.recv().await { Ok(update) => { diff --git a/rust/src/api/chat_summary.rs b/rust/src/api/chat_summary.rs index ca13d6d2c..a99152909 100644 --- a/rust/src/api/chat_summary.rs +++ b/rust/src/api/chat_summary.rs @@ -85,7 +85,7 @@ pub async fn get_chat_summary( mls_group_id: String, ) -> Result { let pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let active = session.chat_list().active().await?; let archived = session.chat_list().archived().await?; let item = active diff --git a/rust/src/api/drafts.rs b/rust/src/api/drafts.rs index ad80c5030..d13c7f2ec 100644 --- a/rust/src/api/drafts.rs +++ b/rust/src/api/drafts.rs @@ -56,7 +56,7 @@ pub async fn save_draft( media_attachments: Vec, ) -> Result { let pubkey = PublicKey::parse(&pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let group_id = group_id_from_string(&group_id)?; let reply_to = reply_to_id.as_deref().map(EventId::parse).transpose()?; let attachments = media_attachments @@ -74,7 +74,7 @@ pub async fn save_draft( #[frb] pub async fn load_draft(pubkey: String, group_id: String) -> Result, ApiError> { let pubkey = PublicKey::parse(&pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let group_id = group_id_from_string(&group_id)?; let draft = session.drafts().load(&group_id).await?; Ok(draft.map(|d| d.into())) @@ -86,7 +86,7 @@ pub async fn load_draft(pubkey: String, group_id: String) -> Result Result<(), ApiError> { let pubkey = PublicKey::parse(&pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let group_id = group_id_from_string(&group_id)?; session.drafts().delete(&group_id).await?; Ok(()) diff --git a/rust/src/api/group_state.rs b/rust/src/api/group_state.rs index 0731c978d..5aefbb49f 100644 --- a/rust/src/api/group_state.rs +++ b/rust/src/api/group_state.rs @@ -45,7 +45,7 @@ pub async fn subscribe_to_group_state( mls_group_id: String, sink: StreamSink, ) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&account_pubkey)?; let group_id = group_id_from_string(&mls_group_id)?; @@ -53,6 +53,7 @@ pub async fn subscribe_to_group_state( .subscribe_to_group_state(&pubkey, &group_id) .await?; let mut rx = subscription.updates; + whitenoise.release_lifecycle(); loop { match rx.recv().await { diff --git a/rust/src/api/groups.rs b/rust/src/api/groups.rs index f94522724..142ebb47e 100644 --- a/rust/src/api/groups.rs +++ b/rust/src/api/groups.rs @@ -92,7 +92,7 @@ impl From for NostrGroupDataUpdate { impl Group { #[frb] pub async fn group_type(&self, account_pubkey: String) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let mls_group_id = group_id_from_string(&self.mls_group_id)?; let parsed_pubkey = PublicKey::parse(&account_pubkey)?; let group_information = whitenoise @@ -103,7 +103,7 @@ impl Group { #[frb] pub async fn is_direct_message_type(&self, account_pubkey: String) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let mls_group_id = group_id_from_string(&self.mls_group_id)?; let parsed_pubkey = PublicKey::parse(&account_pubkey)?; let group_information = whitenoise @@ -114,7 +114,7 @@ impl Group { #[frb] pub async fn is_group_type(&self, account_pubkey: String) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let mls_group_id = group_id_from_string(&self.mls_group_id)?; let parsed_pubkey = PublicKey::parse(&account_pubkey)?; let group_information = whitenoise @@ -131,7 +131,7 @@ impl Group { ) -> Result<(), ApiError> { let mls_group_id = group_id_from_string(&self.mls_group_id)?; let parsed_pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&parsed_pubkey)?; + let session = wn_session(&parsed_pubkey).await?; session .groups() .update_group_data(&mls_group_id, group_data.into()) @@ -219,7 +219,7 @@ impl From for GroupInformation { #[frb] pub async fn active_groups(pubkey: String) -> Result, ApiError> { let pubkey = PublicKey::parse(&pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let groups = session.groups().all(true)?; Ok(groups.into_iter().map(|g| g.into()).collect()) } @@ -228,7 +228,7 @@ pub async fn active_groups(pubkey: String) -> Result, ApiError> { pub async fn group_members(pubkey: String, group_id: String) -> Result, ApiError> { let pubkey = PublicKey::parse(&pubkey)?; let group_id = group_id_from_string(&group_id)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let members = session.groups().members(&group_id)?; Ok(members.into_iter().map(|m| m.to_hex()).collect()) } @@ -237,7 +237,7 @@ pub async fn group_members(pubkey: String, group_id: String) -> Result Result, ApiError> { let pubkey = PublicKey::parse(&pubkey)?; let group_id = group_id_from_string(&group_id)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let admins = session.groups().admins(&group_id)?; Ok(admins.into_iter().map(|a| a.to_hex()).collect()) } @@ -257,7 +257,7 @@ pub async fn create_group( }; let creator_pubkey = PublicKey::parse(&creator_pubkey)?; - let session = wn_session(&creator_pubkey)?; + let session = wn_session(&creator_pubkey).await?; let admin_pubkeys = admin_pubkeys .into_iter() @@ -299,7 +299,7 @@ pub async fn add_members_to_group( ) -> Result<(), ApiError> { let pubkey = PublicKey::parse(&pubkey)?; let group_id = group_id_from_string(&group_id)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let member_pubkeys = member_pubkeys .into_iter() .map(|pk| PublicKey::parse(&pk)) @@ -319,7 +319,7 @@ pub async fn remove_members_from_group( ) -> Result<(), ApiError> { let pubkey = PublicKey::parse(&pubkey)?; let group_id = group_id_from_string(&group_id)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let member_pubkeys = member_pubkeys .into_iter() .map(|pk| PublicKey::parse(&pk)) @@ -339,7 +339,7 @@ pub async fn remove_members_from_group( pub async fn clear_chat(account_pubkey: String, group_id: String) -> Result<(), ApiError> { let pubkey = PublicKey::parse(&account_pubkey)?; let group_id = group_id_from_string(&group_id)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; session .membership() .for_group(&group_id) @@ -355,7 +355,7 @@ pub async fn clear_chat(account_pubkey: String, group_id: String) -> Result<(), /// to exit the group entirely. #[frb] pub async fn delete_chat(account_pubkey: String, group_id: String) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&account_pubkey)?; let group_id = group_id_from_string(&group_id)?; let account = whitenoise.find_account_by_pubkey(&pubkey).await?; @@ -372,11 +372,11 @@ pub async fn leave_and_delete_group( account_pubkey: String, group_id: String, ) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&account_pubkey)?; let group_id = group_id_from_string(&group_id)?; let account = whitenoise.find_account_by_pubkey(&pubkey).await?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; session.groups().leave(&group_id).await?; whitenoise.delete_chat(&account, &group_id).await?; Ok(()) @@ -386,7 +386,7 @@ pub async fn leave_and_delete_group( pub async fn leave_group(pubkey: String, group_id: String) -> Result<(), ApiError> { let pubkey = PublicKey::parse(&pubkey)?; let group_id = group_id_from_string(&group_id)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; session .groups() .leave(&group_id) @@ -398,7 +398,7 @@ pub async fn leave_group(pubkey: String, group_id: String) -> Result<(), ApiErro pub async fn self_demote(pubkey: String, group_id: String) -> Result<(), ApiError> { let pubkey = PublicKey::parse(&pubkey)?; let group_id = group_id_from_string(&group_id)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; session .groups() .self_demote(&group_id) @@ -409,7 +409,7 @@ pub async fn self_demote(pubkey: String, group_id: String) -> Result<(), ApiErro #[frb] pub async fn get_group(account_pubkey: String, group_id: String) -> Result { let pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let group_id = group_id_from_string(&group_id)?; let group = session.groups().get(&group_id)?; Ok(group.into()) @@ -420,7 +420,7 @@ pub async fn group_required_proposals( account_pubkey: String, group_id: String, ) -> Result, ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&account_pubkey)?; let account = whitenoise.find_account_by_pubkey(&pubkey).await?; let group_id = group_id_from_string(&group_id)?; @@ -438,7 +438,7 @@ pub async fn get_group_information( account_pubkey: String, group_id: String, ) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let group_id = group_id_from_string(&group_id)?; let parsed_pubkey = PublicKey::parse(&account_pubkey)?; Ok(whitenoise @@ -452,7 +452,7 @@ pub async fn get_groups_informations( account_pubkey: String, group_ids: Vec, ) -> Result, ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let group_ids = group_ids .into_iter() .map(|id| group_id_from_string(&id)) @@ -491,7 +491,7 @@ pub async fn visible_groups_with_info( account_pubkey: String, ) -> Result, ApiError> { let pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let groups = session.groups().visible_with_info().await?; Ok(groups.into_iter().map(|g| g.into()).collect()) } @@ -514,7 +514,7 @@ pub async fn upload_group_image( ) -> Result { let pubkey = PublicKey::parse(&account_pubkey)?; let group_id = group_id_from_string(&group_id)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let server = Url::parse(&server_url)?; let (encrypted_hash, image_key, image_nonce) = session @@ -554,7 +554,7 @@ pub async fn get_group_image_path( ) -> Result, ApiError> { let pubkey = PublicKey::parse(&account_pubkey)?; let group_id = group_id_from_string(&group_id)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let path = session .groups() .media() @@ -594,7 +594,7 @@ pub async fn get_ratchet_tree_info( account_pubkey: String, group_id: String, ) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&account_pubkey)?; let group_id = group_id_from_string(&group_id)?; let account = whitenoise.find_account_by_pubkey(&pubkey).await?; diff --git a/rust/src/api/media_files.rs b/rust/src/api/media_files.rs index 27fbde825..081722b0d 100644 --- a/rust/src/api/media_files.rs +++ b/rust/src/api/media_files.rs @@ -122,7 +122,7 @@ pub async fn upload_chat_media( file_path: String, ) -> Result { let pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let group_id = group_id_from_string(&group_id)?; let media_file = session @@ -141,7 +141,7 @@ pub async fn download_chat_media( original_file_hash: String, ) -> Result { let pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let group_id = group_id_from_string(&group_id)?; let original_file_hash_bytes = ::hex::decode(&original_file_hash)?; let hash_array: [u8; 32] = diff --git a/rust/src/api/messages.rs b/rust/src/api/messages.rs index 86c4b0fbc..a7fc9a167 100644 --- a/rust/src/api/messages.rs +++ b/rust/src/api/messages.rs @@ -377,7 +377,7 @@ pub async fn send_message_to_group( kind: u16, tags: Option>, ) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let account = whitenoise.find_account_by_pubkey(&pubkey).await?; let group_id = group_id_from_string(&group_id)?; @@ -396,7 +396,7 @@ pub async fn retry_message_publish( group_id: String, event_id: String, ) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let account = whitenoise.find_account_by_pubkey(&pubkey).await?; let group_id = group_id_from_string(&group_id)?; @@ -422,7 +422,7 @@ pub async fn search_messages_in_group( query: String, limit: Option, ) -> Result, ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let group_id = group_id_from_string(&group_id)?; let results = whitenoise @@ -443,7 +443,7 @@ pub async fn search_messages( query: String, limit: Option, ) -> Result, ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let results = whitenoise.search_messages(&pubkey, &query, limit).await?; Ok(results.into_iter().map(|r| r.into()).collect()) @@ -462,7 +462,7 @@ pub async fn fetch_aggregated_messages_for_group( before_message_id: Option, limit: Option, ) -> Result, ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let group_id = group_id_from_string(&group_id)?; let before_ts = before @@ -496,7 +496,7 @@ pub async fn fetch_message_by_id( group_id: String, message_id: String, ) -> Result, ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let group_id = group_id_from_string(&group_id)?; let message = whitenoise @@ -519,7 +519,7 @@ pub async fn fetch_messages_unread_with_minimum( group_id: String, minimum: Option, ) -> Result, ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let group_id = group_id_from_string(&group_id)?; let messages = whitenoise @@ -553,7 +553,7 @@ pub async fn subscribe_to_group_messages( group_id: String, sink: StreamSink, ) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let group_id_str = group_id.clone(); let group_id = group_id_from_string(&group_id)?; @@ -615,6 +615,7 @@ pub async fn subscribe_to_group_messages( // Stream real-time updates let mut rx = subscription.updates; let mut lagged_total: u64 = 0; + whitenoise.release_lifecycle(); loop { match rx.recv().await { Ok(update) => { diff --git a/rust/src/api/mod.rs b/rust/src/api/mod.rs index cd924b5cb..ffe32d4e4 100644 --- a/rust/src/api/mod.rs +++ b/rust/src/api/mod.rs @@ -1,29 +1,104 @@ // Re-export everything from the whitenoise crate use flutter_rust_bridge::frb; +use std::future::Future; +use std::ops::Deref; use std::path::Path; -use std::sync::Arc; -use tokio::sync::OnceCell; +use std::sync::{Arc, LazyLock, RwLock as StdRwLock}; +use std::time::Duration; +use tokio::sync::{Mutex, OwnedRwLockReadGuard, RwLock as TokioRwLock}; +use tokio::time::timeout; pub use whitenoise::{AppSettings, Language, RelayType, ThemeMode, Whitenoise}; -static GLOBAL_WN: OnceCell> = OnceCell::const_new(); +static GLOBAL_WN: StdRwLock>> = StdRwLock::new(None); +static LAST_CONFIG: StdRwLock> = StdRwLock::new(None); +// Serializes lifecycle writers before they queue on WN_LIFECYCLE_LOCK. The +// lifecycle write lock is still the safety barrier against active readers; this +// mutex keeps init/delete/reinit replacement attempts ordered and easy to +// reason about while readers drain. +static GLOBAL_WN_REPLACE_LOCK: Mutex<()> = Mutex::const_new(()); +static WN_LIFECYCLE_LOCK: LazyLock>> = + LazyLock::new(|| Arc::new(TokioRwLock::new(()))); +const REINITIALIZE_WHITENOISE_TIMEOUT: Duration = Duration::from_secs(15); -pub(crate) fn wn() -> Result<&'static Whitenoise, error::ApiError> { - GLOBAL_WN - .get() - .map(|arc| arc.as_ref()) +pub(crate) struct WnHandle { + whitenoise: Arc, + _lifecycle_permit: OwnedRwLockReadGuard<()>, +} + +impl WnHandle { + /// Consumes the handle to drop its lifecycle read permit before entering a + /// long-lived stream receive loop. The subscription receiver is already + /// detached from this handle by then, so delete/reset can proceed instead + /// of waiting on an open UI stream forever. + pub(crate) fn release_lifecycle(self) {} +} + +impl Deref for WnHandle { + type Target = Whitenoise; + + fn deref(&self) -> &Self::Target { + self.whitenoise.as_ref() + } +} + +pub(crate) struct WnSessionHandle { + session: Arc, + // Keeps delete_all_data from closing the shared database while a session + // bridge call is using the session. Long-lived session streams should add + // an explicit release method before waiting on their receive loop. + _wn: WnHandle, +} + +impl Deref for WnSessionHandle { + type Target = whitenoise::whitenoise::session::AccountSession; + + fn deref(&self) -> &Self::Target { + self.session.as_ref() + } +} + +pub(crate) async fn wn() -> Result { + let lifecycle_permit = Arc::clone(&WN_LIFECYCLE_LOCK).read_owned().await; + let whitenoise = GLOBAL_WN + .read() + .map_err(|_| error::ApiError::Whitenoise { + message: "Whitenoise global state lock poisoned".to_string(), + })? + .as_ref() + .map(Arc::clone) .ok_or_else(|| error::ApiError::Whitenoise { message: "Whitenoise not initialized".to_string(), - }) + })?; + Ok(WnHandle { + whitenoise, + _lifecycle_permit: lifecycle_permit, + }) } -pub(crate) fn wn_session( +pub(crate) async fn wn_session( pubkey: &nostr_sdk::PublicKey, -) -> Result, error::ApiError> { - wn()? +) -> Result { + let wn = wn().await?; + let session = wn .session(pubkey) .ok_or_else(|| error::ApiError::Whitenoise { message: "Account session not found".to_string(), - }) + })?; + Ok(WnSessionHandle { session, _wn: wn }) +} + +async fn new_whitenoise_with_timeout( + new_whitenoise: F, + timeout_duration: Duration, +) -> Result, ApiError> +where + F: Future, ApiError>>, +{ + timeout(timeout_duration, new_whitenoise) + .await + .map_err(|_| ApiError::Whitenoise { + message: "Whitenoise reinitialization timed out".to_string(), + })? } // Re-export types that flutter_rust_bridge needs @@ -56,6 +131,14 @@ impl From for WhitenoiseConfig { } } +fn to_core_config(config: &WhitenoiseConfig) -> whitenoise::WhitenoiseConfig { + whitenoise::WhitenoiseConfig::new( + Path::new(&config.data_dir), + Path::new(&config.logs_dir), + "com.whitenoise.app", + ) +} + /// Creates a `WhitenoiseConfig` object from string directory paths. /// /// This function bridges the gap between Flutter's string-based paths and Rust's @@ -141,38 +224,130 @@ pub use zapstore::*; #[frb] pub async fn initialize_whitenoise(config: WhitenoiseConfig) -> Result<(), ApiError> { + if GLOBAL_WN + .read() + .map_err(|_| ApiError::Whitenoise { + message: "Whitenoise global state lock poisoned".to_string(), + })? + .is_some() + { + return Ok(()); + } + + let _replace_guard = GLOBAL_WN_REPLACE_LOCK.lock().await; + let _lifecycle_guard = WN_LIFECYCLE_LOCK.write().await; + if GLOBAL_WN + .read() + .map_err(|_| ApiError::Whitenoise { + message: "Whitenoise global state lock poisoned".to_string(), + })? + .is_some() + { + return Ok(()); + } + let default_relay_urls = config.default_relay_urls.clone(); - let core_config = whitenoise::WhitenoiseConfig::new( - Path::new(&config.data_dir), - Path::new(&config.logs_dir), - "com.whitenoise.app", - ); - GLOBAL_WN - .get_or_try_init(|| async move { - configure_default_relay_urls(default_relay_urls)?; - Whitenoise::ensure_initialized(core_config) - .await - .map_err(ApiError::from) - }) - .await?; + let core_config = to_core_config(&config); + configure_default_relay_urls(default_relay_urls)?; + *LAST_CONFIG.write().map_err(|_| ApiError::Whitenoise { + message: "Whitenoise config lock poisoned".to_string(), + })? = Some(core_config.clone()); + + let whitenoise = Whitenoise::new(core_config.clone()) + .await + .map_err(ApiError::from)?; + *GLOBAL_WN.write().map_err(|_| ApiError::Whitenoise { + message: "Whitenoise global state lock poisoned".to_string(), + })? = Some(whitenoise); + + Ok(()) +} + +#[frb] +pub async fn reinitialize_whitenoise() -> Result<(), ApiError> { + if GLOBAL_WN + .read() + .map_err(|_| ApiError::Whitenoise { + message: "Whitenoise global state lock poisoned".to_string(), + })? + .is_some() + { + return Ok(()); + } + + let _replace_guard = GLOBAL_WN_REPLACE_LOCK.lock().await; + let _lifecycle_guard = WN_LIFECYCLE_LOCK.write().await; + if GLOBAL_WN + .read() + .map_err(|_| ApiError::Whitenoise { + message: "Whitenoise global state lock poisoned".to_string(), + })? + .is_some() + { + return Ok(()); + } + + let core_config = LAST_CONFIG + .read() + .map_err(|_| ApiError::Whitenoise { + message: "Whitenoise config lock poisoned".to_string(), + })? + .as_ref() + .cloned() + .ok_or_else(|| ApiError::Whitenoise { + message: "Whitenoise config not initialized".to_string(), + })?; + + let whitenoise = new_whitenoise_with_timeout( + async move { Whitenoise::new(core_config).await.map_err(ApiError::from) }, + REINITIALIZE_WHITENOISE_TIMEOUT, + ) + .await?; + *GLOBAL_WN.write().map_err(|_| ApiError::Whitenoise { + message: "Whitenoise global state lock poisoned".to_string(), + })? = Some(whitenoise); + Ok(()) } +/// Wipes all on-disk data and clears the process-global Whitenoise instance. +/// The lifecycle write lock waits for in-flight bridge calls before the old +/// database pool is closed, then blocks new bridge calls until the global +/// instance has been cleared. Call `initialize_whitenoise` to install a fresh +/// instance after the reset. #[frb] pub async fn delete_all_data() -> Result<(), ApiError> { - let whitenoise = wn()?; - whitenoise.delete_all_data().await.map_err(ApiError::from) + let _replace_guard = GLOBAL_WN_REPLACE_LOCK.lock().await; + let _lifecycle_guard = WN_LIFECYCLE_LOCK.write().await; + let whitenoise = GLOBAL_WN + .read() + .map_err(|_| ApiError::Whitenoise { + message: "Whitenoise global state lock poisoned".to_string(), + })? + .as_ref() + .map(Arc::clone) + .ok_or_else(|| ApiError::Whitenoise { + message: "Whitenoise not initialized".to_string(), + })?; + let result = whitenoise.delete_all_data().await; + + *GLOBAL_WN.write().map_err(|_| ApiError::Whitenoise { + message: "Whitenoise global state lock poisoned".to_string(), + })? = None; + drop(whitenoise); + + result.map_err(ApiError::from) } #[frb] pub async fn get_app_settings() -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; whitenoise.app_settings().await.map_err(ApiError::from) } #[frb] pub async fn update_theme_mode(theme_mode: ThemeMode) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; whitenoise .update_theme_mode(theme_mode) .await @@ -186,7 +361,7 @@ pub fn app_settings_theme_mode(app_settings: &AppSettings) -> ThemeMode { #[frb] pub async fn update_language(language: Language) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; whitenoise .update_language(language) .await @@ -197,3 +372,89 @@ pub async fn update_language(language: Language) -> Result<(), ApiError> { pub fn app_settings_language(app_settings: &AppSettings) -> Language { app_settings.language.clone() } + +#[cfg(test)] +mod tests { + use super::*; + use std::path::PathBuf; + use tokio::sync::oneshot; + + #[tokio::test] + async fn new_whitenoise_timeout_maps_to_api_error() { + let result = new_whitenoise_with_timeout( + std::future::pending::, ApiError>>(), + Duration::from_millis(1), + ) + .await; + + match result { + Err(ApiError::Whitenoise { message }) => { + assert_eq!(message, "Whitenoise reinitialization timed out"); + } + other => panic!("expected reinitialization timeout ApiError, got {other:?}"), + } + } + + #[tokio::test(flavor = "current_thread")] + async fn lifecycle_write_waits_for_read_permit_to_drop() { + let read_guard = Arc::clone(&WN_LIFECYCLE_LOCK).read_owned().await; + let (acquired_tx, acquired_rx) = oneshot::channel(); + + let writer = tokio::spawn(async move { + let _write_guard = WN_LIFECYCLE_LOCK.write().await; + let _ = acquired_tx.send(()); + }); + + tokio::select! { + _ = acquired_rx => panic!("writer acquired lifecycle lock before read permit dropped"), + _ = tokio::task::yield_now() => {} + } + + drop(read_guard); + writer.await.expect("writer task should complete"); + } + + #[test] + fn stream_receive_loops_release_lifecycle_before_waiting() { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let api_dir = manifest_dir.join("src/api"); + // This guards the current stream pattern: `let mut rx = ...`, + // `release_lifecycle()`, then `rx.recv().await`. Update it if stream + // receiver naming or loop structure changes. + let api_files = std::fs::read_dir(&api_dir) + .unwrap_or_else(|e| panic!("failed to read api directory {api_dir:?}: {e}")); + + for entry in api_files { + let path = entry + .unwrap_or_else(|e| panic!("failed to read api directory entry: {e}")) + .path(); + let file_name = path + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or(""); + if !path.is_file() + || file_name == "mod.rs" + || path.extension().and_then(|ext| ext.to_str()) != Some("rs") + { + continue; + } + + let source = std::fs::read_to_string(&path) + .unwrap_or_else(|e| panic!("failed to read {file_name}: {e}")); + for (offset, segment) in source.split("rx.recv().await").enumerate().skip(1) { + let before = source + .split("rx.recv().await") + .take(offset) + .collect::>() + .join("rx.recv().await"); + let after_last_receiver = before.rsplit("let mut rx =").next().unwrap_or(&before); + assert!( + after_last_receiver.contains("release_lifecycle()"), + "{file_name} has an rx.recv().await loop without release_lifecycle() after \ + its receiver setup; nearby suffix: {:?}", + &segment[..segment.len().min(120)] + ); + } + } + } +} diff --git a/rust/src/api/mute_list.rs b/rust/src/api/mute_list.rs index 65eeba291..903a266bc 100644 --- a/rust/src/api/mute_list.rs +++ b/rust/src/api/mute_list.rs @@ -29,7 +29,7 @@ impl MuteListEntry { #[frb] pub async fn block_user(account_pubkey: String, target_pubkey: String) -> Result<(), ApiError> { let account_pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&account_pubkey)?; + let session = wn_session(&account_pubkey).await?; let target = PublicKey::parse(&target_pubkey)?; session .mute_list() @@ -41,7 +41,7 @@ pub async fn block_user(account_pubkey: String, target_pubkey: String) -> Result #[frb] pub async fn unblock_user(account_pubkey: String, target_pubkey: String) -> Result<(), ApiError> { let account_pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&account_pubkey)?; + let session = wn_session(&account_pubkey).await?; let target = PublicKey::parse(&target_pubkey)?; session .mute_list() @@ -53,7 +53,7 @@ pub async fn unblock_user(account_pubkey: String, target_pubkey: String) -> Resu #[frb] pub async fn get_blocked_users(account_pubkey: String) -> Result, ApiError> { let account_pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&account_pubkey)?; + let session = wn_session(&account_pubkey).await?; let entries = session.mute_list().get_blocked_users().await?; Ok(entries .into_iter() @@ -67,7 +67,7 @@ pub async fn is_user_blocked( target_pubkey: String, ) -> Result { let account_pubkey = PublicKey::parse(&account_pubkey)?; - let session = wn_session(&account_pubkey)?; + let session = wn_session(&account_pubkey).await?; let target = PublicKey::parse(&target_pubkey)?; session .mute_list() diff --git a/rust/src/api/notifications.rs b/rust/src/api/notifications.rs index 7e78388fd..cea7ca2aa 100644 --- a/rust/src/api/notifications.rs +++ b/rust/src/api/notifications.rs @@ -80,9 +80,10 @@ impl From for NotificationUpdate { pub async fn subscribe_to_notifications( sink: StreamSink, ) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let subscription = whitenoise.subscribe_to_notifications(); let mut rx = subscription.updates; + whitenoise.release_lifecycle(); loop { match rx.recv().await { @@ -160,7 +161,7 @@ impl From for PushRegistration { #[frb] pub async fn get_push_registration(pubkey: String) -> Result, ApiError> { let pubkey = PublicKey::parse(&pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let registration = session.push().registration().await?; Ok(registration.map(PushRegistration::from)) } @@ -180,7 +181,7 @@ pub async fn upsert_push_registration( .map(RelayUrl::parse) .transpose() .map_err(ApiError::from)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; let registration = session .push() .upsert_registration(platform.into(), &raw_token, &server_pk, relay.as_ref()) @@ -191,7 +192,7 @@ pub async fn upsert_push_registration( #[frb] pub async fn clear_push_registration(pubkey: String) -> Result<(), ApiError> { let pubkey = PublicKey::parse(&pubkey)?; - let session = wn_session(&pubkey)?; + let session = wn_session(&pubkey).await?; session.push().clear_registration().await?; Ok(()) } diff --git a/rust/src/api/relays.rs b/rust/src/api/relays.rs index 7f0cbb715..7b91b9308 100644 --- a/rust/src/api/relays.rs +++ b/rust/src/api/relays.rs @@ -40,7 +40,7 @@ pub fn relay_type_key_package() -> RelayType { #[frb] pub async fn debug_relay_control_state() -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; whitenoise .debug_relay_control_state() .await @@ -49,7 +49,7 @@ pub async fn debug_relay_control_state() -> Result { #[frb] pub async fn ensure_all_subscriptions() -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; whitenoise .ensure_all_subscriptions() .await diff --git a/rust/src/api/signer.rs b/rust/src/api/signer.rs index 1094affc0..0f6d4a7ce 100644 --- a/rust/src/api/signer.rs +++ b/rust/src/api/signer.rs @@ -158,7 +158,7 @@ pub async fn register_external_signer( nip44_encrypt: impl Fn(String, String) -> DartFnFuture + Send + Sync + 'static, nip44_decrypt: impl Fn(String, String) -> DartFnFuture + Send + Sync + 'static, ) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let signer = DartSigner::new( @@ -194,7 +194,7 @@ pub async fn login_external_signer_start( nip44_encrypt: impl Fn(String, String) -> DartFnFuture + Send + Sync + 'static, nip44_decrypt: impl Fn(String, String) -> DartFnFuture + Send + Sync + 'static, ) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let signer = DartSigner::new( @@ -219,7 +219,7 @@ pub async fn login_external_signer_start( pub async fn login_external_signer_publish_default_relays( pubkey: String, ) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let result = whitenoise .login_external_signer_publish_default_relays(&pubkey) @@ -235,7 +235,7 @@ pub async fn login_external_signer_with_custom_relay( pubkey: String, relay_url: String, ) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let relay_url = nostr_sdk::RelayUrl::parse(&relay_url)?; let result = whitenoise diff --git a/rust/src/api/user_search.rs b/rust/src/api/user_search.rs index 1e1b13898..622a440f1 100644 --- a/rust/src/api/user_search.rs +++ b/rust/src/api/user_search.rs @@ -151,7 +151,7 @@ pub async fn search_users( radius_end: u8, sink: StreamSink, ) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&account_pubkey)?; let params = UserSearchParams { @@ -163,6 +163,7 @@ pub async fn search_users( let subscription = whitenoise.search_users(params).await?; let mut rx = subscription.updates; + whitenoise.release_lifecycle(); loop { match rx.recv().await { diff --git a/rust/src/api/users.rs b/rust/src/api/users.rs index 08b0db89a..80fee21ff 100644 --- a/rust/src/api/users.rs +++ b/rust/src/api/users.rs @@ -97,7 +97,7 @@ pub async fn subscribe_to_user( pubkey: String, sink: StreamSink, ) -> Result<(), ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; let subscription = whitenoise.subscribe_to_user(&pubkey).await?; @@ -111,6 +111,7 @@ pub async fn subscribe_to_user( } let mut rx = subscription.updates; + whitenoise.release_lifecycle(); loop { match rx.recv().await { Ok(update) => { @@ -137,9 +138,9 @@ pub async fn subscribe_to_user( #[frb] pub async fn get_user(pubkey: String, blocking_data_sync: bool) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; - let user = resolve_whitenoise_user(whitenoise, &pubkey, blocking_data_sync).await?; + let user = resolve_whitenoise_user(&whitenoise, &pubkey, blocking_data_sync).await?; Ok(user.into()) } @@ -148,9 +149,9 @@ pub async fn user_metadata( pubkey: String, blocking_data_sync: bool, ) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; - let user = resolve_whitenoise_user(whitenoise, &pubkey, blocking_data_sync).await?; + let user = resolve_whitenoise_user(&whitenoise, &pubkey, blocking_data_sync).await?; Ok(user.metadata.into()) } @@ -160,9 +161,9 @@ pub async fn user_relays( relay_type: RelayType, blocking_data_sync: bool, ) -> Result, ApiError> { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; - let user = resolve_whitenoise_user(whitenoise, &pubkey, blocking_data_sync).await?; + let user = resolve_whitenoise_user(&whitenoise, &pubkey, blocking_data_sync).await?; let relays = user.relays_by_type(relay_type, &whitenoise).await?; Ok(relays.into_iter().map(|r| r.into()).collect()) } @@ -172,9 +173,9 @@ pub async fn user_has_key_package( pubkey: String, blocking_data_sync: bool, ) -> Result { - let whitenoise = wn()?; + let whitenoise = wn().await?; let pubkey = PublicKey::parse(&pubkey)?; - let user = resolve_whitenoise_user(whitenoise, &pubkey, blocking_data_sync).await?; + let user = resolve_whitenoise_user(&whitenoise, &pubkey, blocking_data_sync).await?; match user.key_package_status(&whitenoise.shared).await? { WhitenoiseKeyPackageStatus::Valid(_) => Ok(KeyPackageStatus::Valid), WhitenoiseKeyPackageStatus::NotFound => Ok(KeyPackageStatus::NotFound), diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index 6be73958c..e8db79731 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -1,5 +1,5 @@ // This file is automatically generated, so please do not edit it. -// @generated by `flutter_rust_bridge`@ 2.11.1. +// @generated by `flutter_rust_bridge`@ 2.12.0. #![allow( non_camel_case_types, @@ -20,6 +20,7 @@ clippy::deref_addrof, clippy::explicit_auto_deref, clippy::borrow_deref_ref, + clippy::uninlined_format_args, clippy::needless_borrow )] @@ -43,8 +44,8 @@ flutter_rust_bridge::frb_generated_boilerplate!( default_rust_opaque = RustOpaqueMoi, default_rust_auto_opaque = RustAutoOpaqueMoi, ); -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1"; -pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1064693455; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.12.0"; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -1547864171; // Section: executor @@ -3769,6 +3770,41 @@ fn wire__crate__api__signer__register_external_signer_impl( }, ) } +fn wire__crate__api__reinitialize_whitenoise_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "reinitialize_whitenoise", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, crate::api::error::ApiError>( + (move || async move { + let output_ok = crate::api::reinitialize_whitenoise().await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} fn wire__crate__api__relays__relay_type_inbox_impl( port_: flutter_rust_bridge::for_generated::MessagePort, ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, @@ -6695,7 +6731,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode(deserializer)); } @@ -6707,7 +6743,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode(deserializer)); } @@ -6719,7 +6755,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode(deserializer)); } @@ -6731,7 +6767,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode(deserializer)); } @@ -6743,7 +6779,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -6757,7 +6793,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -6771,7 +6807,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -6785,7 +6821,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -6799,7 +6835,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode(deserializer)); } @@ -6811,7 +6847,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -6825,7 +6861,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode(deserializer)); } @@ -6837,7 +6873,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -6851,7 +6887,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode(deserializer)); } @@ -6863,7 +6899,7 @@ impl SseDecode for Vec> { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(>::sse_decode(deserializer)); } @@ -6875,7 +6911,7 @@ impl SseDecode for Vec> { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(>::sse_decode( deserializer, @@ -6889,7 +6925,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -6903,7 +6939,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -6917,7 +6953,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -6931,7 +6967,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -6945,7 +6981,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -6959,7 +6995,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -6973,7 +7009,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -6987,7 +7023,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -7001,7 +7037,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode(deserializer)); } @@ -7013,7 +7049,7 @@ impl SseDecode for Vec<(String, String)> { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(<(String, String)>::sse_decode(deserializer)); } @@ -7025,7 +7061,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode(deserializer)); } @@ -7037,7 +7073,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -7051,7 +7087,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -7065,7 +7101,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode(deserializer)); } @@ -7077,7 +7113,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -7091,7 +7127,7 @@ impl SseDecode for Vec { // Codec=Sse (Serialization based), see doc to use other codecs fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { let mut len_ = ::sse_decode(deserializer); - let mut ans_ = vec![]; + let mut ans_ = Vec::with_capacity(len_ as usize); for idx_ in 0..len_ { ans_.push(::sse_decode( deserializer, @@ -8432,137 +8468,138 @@ fn pde_ffi_dispatcher_primary_impl( rust_vec_len, data_len, ), - 97 => wire__crate__api__relays__relay_type_inbox_impl(port, ptr, rust_vec_len, data_len), - 98 => { + 97 => wire__crate__api__reinitialize_whitenoise_impl(port, ptr, rust_vec_len, data_len), + 98 => wire__crate__api__relays__relay_type_inbox_impl(port, ptr, rust_vec_len, data_len), + 99 => { wire__crate__api__relays__relay_type_key_package_impl(port, ptr, rust_vec_len, data_len) } - 99 => wire__crate__api__relays__relay_type_nip65_impl(port, ptr, rust_vec_len, data_len), - 100 => { + 100 => wire__crate__api__relays__relay_type_nip65_impl(port, ptr, rust_vec_len, data_len), + 101 => { wire__crate__api__utils__relay_url_from_string_impl(port, ptr, rust_vec_len, data_len) } - 101 => { + 102 => { wire__crate__api__accounts__remove_account_relay_impl(port, ptr, rust_vec_len, data_len) } - 102 => wire__crate__api__groups__remove_members_from_group_impl( + 103 => wire__crate__api__groups__remove_members_from_group_impl( port, ptr, rust_vec_len, data_len, ), - 103 => wire__crate__api__accounts__restore_default_relays_impl( + 104 => wire__crate__api__accounts__restore_default_relays_impl( port, ptr, rust_vec_len, data_len, ), - 104 => wire__crate__api__messages__retry_message_publish_impl( + 105 => wire__crate__api__messages__retry_message_publish_impl( port, ptr, rust_vec_len, data_len, ), - 105 => wire__crate__api__drafts__save_draft_impl(port, ptr, rust_vec_len, data_len), - 106 => wire__crate__api__messages__search_messages_impl(port, ptr, rust_vec_len, data_len), - 107 => wire__crate__api__messages__search_messages_in_group_impl( + 106 => wire__crate__api__drafts__save_draft_impl(port, ptr, rust_vec_len, data_len), + 107 => wire__crate__api__messages__search_messages_impl(port, ptr, rust_vec_len, data_len), + 108 => wire__crate__api__messages__search_messages_in_group_impl( port, ptr, rust_vec_len, data_len, ), - 108 => wire__crate__api__user_search__search_users_impl(port, ptr, rust_vec_len, data_len), - 109 => wire__crate__api__groups__self_demote_impl(port, ptr, rust_vec_len, data_len), - 110 => { + 109 => wire__crate__api__user_search__search_users_impl(port, ptr, rust_vec_len, data_len), + 110 => wire__crate__api__groups__self_demote_impl(port, ptr, rust_vec_len, data_len), + 111 => { wire__crate__api__bug_report__send_bug_report_impl(port, ptr, rust_vec_len, data_len) } - 111 => wire__crate__api__messages__send_message_to_group_impl( + 112 => wire__crate__api__messages__send_message_to_group_impl( port, ptr, rust_vec_len, data_len, ), - 112 => { + 113 => { wire__crate__api__chat_list__set_chat_pin_order_impl(port, ptr, rust_vec_len, data_len) } - 113 => { + 114 => { wire__crate__api__utils__string_from_relay_url_impl(port, ptr, rust_vec_len, data_len) } - 114 => wire__crate__api__chat_list__subscribe_to_archived_chat_list_impl( + 115 => wire__crate__api__chat_list__subscribe_to_archived_chat_list_impl( port, ptr, rust_vec_len, data_len, ), - 115 => wire__crate__api__chat_list__subscribe_to_chat_list_impl( + 116 => wire__crate__api__chat_list__subscribe_to_chat_list_impl( port, ptr, rust_vec_len, data_len, ), - 116 => wire__crate__api__messages__subscribe_to_group_messages_impl( + 117 => wire__crate__api__messages__subscribe_to_group_messages_impl( port, ptr, rust_vec_len, data_len, ), - 117 => wire__crate__api__group_state__subscribe_to_group_state_impl( + 118 => wire__crate__api__group_state__subscribe_to_group_state_impl( port, ptr, rust_vec_len, data_len, ), - 118 => wire__crate__api__notifications__subscribe_to_notifications_impl( + 119 => wire__crate__api__notifications__subscribe_to_notifications_impl( port, ptr, rust_vec_len, data_len, ), - 119 => { + 120 => { wire__crate__api__logs__subscribe_to_rust_logs_impl(port, ptr, rust_vec_len, data_len) } - 120 => wire__crate__api__users__subscribe_to_user_impl(port, ptr, rust_vec_len, data_len), - 121 => wire__crate__api__utils__tag_from_vec_impl(port, ptr, rust_vec_len, data_len), - 126 => { + 121 => wire__crate__api__users__subscribe_to_user_impl(port, ptr, rust_vec_len, data_len), + 122 => wire__crate__api__utils__tag_from_vec_impl(port, ptr, rust_vec_len, data_len), + 127 => { wire__crate__api__account_groups__unarchive_chat_impl(port, ptr, rust_vec_len, data_len) } - 127 => wire__crate__api__mute_list__unblock_user_impl(port, ptr, rust_vec_len, data_len), - 128 => wire__crate__api__accounts__unfollow_user_impl(port, ptr, rust_vec_len, data_len), - 129 => wire__crate__api__chat_list__unmute_chat_impl(port, ptr, rust_vec_len, data_len), - 130 => wire__crate__api__accounts__update_account_metadata_impl( + 128 => wire__crate__api__mute_list__unblock_user_impl(port, ptr, rust_vec_len, data_len), + 129 => wire__crate__api__accounts__unfollow_user_impl(port, ptr, rust_vec_len, data_len), + 130 => wire__crate__api__chat_list__unmute_chat_impl(port, ptr, rust_vec_len, data_len), + 131 => wire__crate__api__accounts__update_account_metadata_impl( port, ptr, rust_vec_len, data_len, ), - 131 => wire__crate__api__update_language_impl(port, ptr, rust_vec_len, data_len), - 132 => wire__crate__api__accounts__update_notifications_enabled_impl( + 132 => wire__crate__api__update_language_impl(port, ptr, rust_vec_len, data_len), + 133 => wire__crate__api__accounts__update_notifications_enabled_impl( port, ptr, rust_vec_len, data_len, ), - 133 => wire__crate__api__update_theme_mode_impl(port, ptr, rust_vec_len, data_len), - 134 => wire__crate__api__accounts__upload_account_profile_picture_impl( + 134 => wire__crate__api__update_theme_mode_impl(port, ptr, rust_vec_len, data_len), + 135 => wire__crate__api__accounts__upload_account_profile_picture_impl( port, ptr, rust_vec_len, data_len, ), - 135 => { + 136 => { wire__crate__api__media_files__upload_chat_media_impl(port, ptr, rust_vec_len, data_len) } - 136 => wire__crate__api__groups__upload_group_image_impl(port, ptr, rust_vec_len, data_len), - 137 => wire__crate__api__notifications__upsert_push_registration_impl( + 137 => wire__crate__api__groups__upload_group_image_impl(port, ptr, rust_vec_len, data_len), + 138 => wire__crate__api__notifications__upsert_push_registration_impl( port, ptr, rust_vec_len, data_len, ), - 138 => { + 139 => { wire__crate__api__users__user_has_key_package_impl(port, ptr, rust_vec_len, data_len) } - 139 => wire__crate__api__users__user_metadata_impl(port, ptr, rust_vec_len, data_len), - 140 => wire__crate__api__users__user_relays_impl(port, ptr, rust_vec_len, data_len), - 141 => wire__crate__api__groups__visible_groups_with_info_impl( + 140 => wire__crate__api__users__user_metadata_impl(port, ptr, rust_vec_len, data_len), + 141 => wire__crate__api__users__user_relays_impl(port, ptr, rust_vec_len, data_len), + 142 => wire__crate__api__groups__visible_groups_with_info_impl( port, ptr, rust_vec_len, @@ -8602,10 +8639,10 @@ fn pde_ffi_dispatcher_sync_impl( 79 => wire__crate__api__utils__language_to_string_impl(ptr, rust_vec_len, data_len), 80 => wire__crate__api__utils__language_turkish_impl(ptr, rust_vec_len, data_len), 94 => wire__crate__api__utils__npub_from_hex_pubkey_impl(ptr, rust_vec_len, data_len), - 122 => wire__crate__api__utils__theme_mode_dark_impl(ptr, rust_vec_len, data_len), - 123 => wire__crate__api__utils__theme_mode_light_impl(ptr, rust_vec_len, data_len), - 124 => wire__crate__api__utils__theme_mode_system_impl(ptr, rust_vec_len, data_len), - 125 => wire__crate__api__utils__theme_mode_to_string_impl(ptr, rust_vec_len, data_len), + 123 => wire__crate__api__utils__theme_mode_dark_impl(ptr, rust_vec_len, data_len), + 124 => wire__crate__api__utils__theme_mode_light_impl(ptr, rust_vec_len, data_len), + 125 => wire__crate__api__utils__theme_mode_system_impl(ptr, rust_vec_len, data_len), + 126 => wire__crate__api__utils__theme_mode_to_string_impl(ptr, rust_vec_len, data_len), _ => unreachable!(), } } @@ -12490,7 +12527,7 @@ impl SseEncode for crate::api::WhitenoiseConfig { #[cfg(not(target_family = "wasm"))] mod io { // This file is automatically generated, so please do not edit it. - // @generated by `flutter_rust_bridge`@ 2.11.1. + // @generated by `flutter_rust_bridge`@ 2.12.0. // Section: imports diff --git a/test/hooks/use_delete_all_data_test.dart b/test/hooks/use_delete_all_data_test.dart index c30baa207..369a59517 100644 --- a/test/hooks/use_delete_all_data_test.dart +++ b/test/hooks/use_delete_all_data_test.dart @@ -1,8 +1,8 @@ import 'dart:async'; - import 'package:flutter_test/flutter_test.dart'; import 'package:whitenoise/hooks/use_delete_all_data.dart'; import 'package:whitenoise/src/rust/frb_generated.dart'; +import 'package:whitenoise/utils/reset_marker.dart'; import '../mocks/mock_wn_api.dart'; import '../test_helpers.dart'; @@ -11,12 +11,23 @@ void main() { late MockWnApi mockApi; setUpAll(() { + mockPathProvider(); mockApi = MockWnApi(); RustLib.initMock(api: mockApi); }); setUp(() { mockApi.reset(); + debugMarkResetPending = () async {}; + debugClearResetPending = () async {}; + addTearDown(() async { + debugMarkResetPending = null; + debugClearResetPending = null; + }); + }); + + tearDown(() async { + await clearResetPending(); }); group('DeleteAllDataState', () { @@ -52,43 +63,34 @@ void main() { }); testWidgets('deleteAllData sets isDeleting to true during operation', (tester) async { - late DeleteAllDataState initialState; - late DeleteAllDataState duringState; - late Future Function() deleteAllData; + late Future Function() deleteAllData; - mockApi.deleteAllDataDelay = const Duration(milliseconds: 100); + mockApi.deleteAllDataCompleter = Completer(); - await mountHook( + final getState = await mountHook( tester, () { final hook = useDeleteAllData(); - initialState = hook.state; deleteAllData = hook.deleteAllData; - return null; + return hook.state; }, ); - expect(initialState.isDeleting, false); + expect(getState().isDeleting, false); - deleteAllData(); + final resultFuture = deleteAllData(); await tester.pump(); - await mountHook( - tester, - () { - final hook = useDeleteAllData(); - duringState = hook.state; - return null; - }, - ); - - expect(duringState.isDeleting, true); + expect(getState().isDeleting, true); - await tester.pumpAndSettle(); + mockApi.deleteAllDataCompleter!.complete(); + await tester.pump(); + await resultFuture; + await tester.pump(); }); testWidgets('deleteAllData calls API successfully', (tester) async { - late Future Function() deleteAllData; + late Future Function() deleteAllData; await mountHook( tester, @@ -100,39 +102,54 @@ void main() { ); await deleteAllData(); - await tester.pumpAndSettle(); + await tester.pump(); expect(mockApi.deleteAllDataCalled, true); + expect(mockApi.reinitializeWhitenoiseCalled, true); }); - testWidgets('deleteAllData sets isDeleting to false after success', (tester) async { - late DeleteAllDataState state; + testWidgets('deleteAllData returns false when reinitialization fails', (tester) async { late Future Function() deleteAllData; + late DeleteAllDataFailure? Function() latestFailure; + + mockApi.reinitializeWhitenoiseShouldFail = true; await mountHook( tester, () { final hook = useDeleteAllData(); - state = hook.state; deleteAllData = hook.deleteAllData; + latestFailure = hook.latestFailure; return null; }, ); final result = await deleteAllData(); - await tester.pumpAndSettle(); + await tester.pump(); - await mountHook( + expect(result, false); + expect(mockApi.deleteAllDataCalled, true); + expect(mockApi.reinitializeWhitenoiseCalled, true); + expect(latestFailure(), DeleteAllDataFailure.reinitializeFailed); + }); + + testWidgets('deleteAllData sets isDeleting to false after success', (tester) async { + late Future Function() deleteAllData; + + final getState = await mountHook( tester, () { final hook = useDeleteAllData(); - state = hook.state; - return null; + deleteAllData = hook.deleteAllData; + return hook.state; }, ); + final result = await deleteAllData(); + await tester.pump(); + expect(result, true); - expect(state.isDeleting, false); + expect(getState().isDeleting, false); }); testWidgets('deleteAllData returns false on failure', (tester) async { @@ -150,12 +167,13 @@ void main() { ); final result = await deleteAllData(); - await tester.pumpAndSettle(); + await tester.pump(); expect(result, false); + expect(mockApi.reinitializeWhitenoiseCalled, false); }); - testWidgets('deleteAllData returns false on timeout', (tester) async { + testWidgets('deleteAllData waits for long-running API calls', (tester) async { late Future Function() deleteAllData; mockApi.deleteAllDataCompleter = Completer(); @@ -163,23 +181,29 @@ void main() { await mountHook( tester, () { - final hook = useDeleteAllData(timeout: const Duration(seconds: 1)); + final hook = useDeleteAllData(); deleteAllData = hook.deleteAllData; return null; }, ); final resultFuture = deleteAllData(); + var completed = false; + unawaited(resultFuture.then((_) => completed = true)); + await tester.pump(const Duration(seconds: 2)); + expect(completed, isFalse); + mockApi.deleteAllDataCompleter!.complete(); + final result = await resultFuture; - await tester.pumpAndSettle(); + await tester.pump(); - expect(result, false); + expect(result, true); expect(mockApi.deleteAllDataCalled, true); }); - testWidgets('deleteAllData sets isDeleting to false after timeout', (tester) async { + testWidgets('deleteAllData keeps isDeleting true while API call is pending', (tester) async { late Future Function() deleteAllData; mockApi.deleteAllDataCompleter = Completer(); @@ -187,7 +211,7 @@ void main() { final getState = await mountHook( tester, () { - final hook = useDeleteAllData(timeout: const Duration(seconds: 1)); + final hook = useDeleteAllData(); deleteAllData = hook.deleteAllData; return hook.state; }, @@ -196,8 +220,11 @@ void main() { final resultFuture = deleteAllData(); await tester.pump(const Duration(seconds: 2)); + expect(getState().isDeleting, true); + mockApi.deleteAllDataCompleter!.complete(); + await resultFuture; - await tester.pumpAndSettle(); + await tester.pump(); expect(getState().isDeleting, false); }); @@ -216,14 +243,14 @@ void main() { mockApi.deleteAllDataShouldFail = true; final failResult = await deleteAllData(); - await tester.pumpAndSettle(); + await tester.pump(); expect(failResult, false); mockApi.deleteAllDataShouldFail = false; final successResult = await deleteAllData(); - await tester.pumpAndSettle(); + await tester.pump(); expect(successResult, true); expect(getState().isDeleting, false); diff --git a/test/mocks/mock_wn_api.dart b/test/mocks/mock_wn_api.dart index f0b98f664..aeb6baf06 100644 --- a/test/mocks/mock_wn_api.dart +++ b/test/mocks/mock_wn_api.dart @@ -76,6 +76,8 @@ class MockWnApi implements RustLibApi { bool deleteAllDataShouldFail = false; Duration deleteAllDataDelay = Duration.zero; Completer? deleteAllDataCompleter; + bool reinitializeWhitenoiseCalled = false; + bool reinitializeWhitenoiseShouldFail = false; LoginResult? loginStartResult; LoginResult? loginExternalSignerStartResult; @@ -720,6 +722,14 @@ class MockWnApi implements RustLibApi { } } + @override + Future crateApiReinitializeWhitenoise() async { + reinitializeWhitenoiseCalled = true; + if (reinitializeWhitenoiseShouldFail) { + throw Exception('Failed to reinitialize Whitenoise'); + } + } + @override Future crateApiAccountsLoginStart({ required String nsecOrHexPrivkey, @@ -950,6 +960,8 @@ class MockWnApi implements RustLibApi { deleteAllDataShouldFail = false; deleteAllDataDelay = Duration.zero; deleteAllDataCompleter = null; + reinitializeWhitenoiseCalled = false; + reinitializeWhitenoiseShouldFail = false; loginStartResult = null; loginExternalSignerStartResult = null; registerExternalSignerCalled = false; diff --git a/test/screens/privacy_security_screen_test.dart b/test/screens/privacy_security_screen_test.dart index a6541419e..eaef103ef 100644 --- a/test/screens/privacy_security_screen_test.dart +++ b/test/screens/privacy_security_screen_test.dart @@ -6,6 +6,7 @@ import 'package:whitenoise/screens/chat_list_screen.dart'; import 'package:whitenoise/screens/home_screen.dart'; import 'package:whitenoise/screens/privacy_security_screen.dart'; import 'package:whitenoise/src/rust/frb_generated.dart'; +import 'package:whitenoise/utils/reset_marker.dart'; import 'package:whitenoise/widgets/wn_button.dart'; import '../mocks/mock_auth_notifier.dart'; @@ -17,12 +18,23 @@ void main() { late MockWnApi mockApi; setUpAll(() { + mockPathProvider(); mockApi = MockWnApi(); RustLib.initMock(api: mockApi); }); setUp(() { mockApi.reset(); + debugMarkResetPending = () async {}; + debugClearResetPending = () async {}; + addTearDown(() async { + debugMarkResetPending = null; + debugClearResetPending = null; + }); + }); + + tearDown(() async { + await clearResetPending(); }); Future pumpPrivacySecurityScreen(WidgetTester tester) async { @@ -92,19 +104,39 @@ void main() { expect(mockApi.deleteAllDataCalled, false); }); - testWidgets('confirming delete all data calls API and navigates to home', (tester) async { + testWidgets('confirming delete all data calls API and navigates to home', ( + tester, + ) async { await pumpPrivacySecurityScreen(tester); await tester.tap(find.byKey(const Key('delete_all_data_button'))); await tester.pumpAndSettle(); await tester.tap(find.byKey(const Key('confirm_button'))); - await tester.pumpAndSettle(); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); expect(mockApi.deleteAllDataCalled, true); expect(find.byType(HomeScreen), findsOneWidget); }); + testWidgets('delete all data shows fatal error when reinitialization fails', (tester) async { + mockApi.reinitializeWhitenoiseShouldFail = true; + + await pumpPrivacySecurityScreen(tester); + + await tester.tap(find.byKey(const Key('delete_all_data_button'))); + await tester.pumpAndSettle(); + + await tester.tap(find.byKey(const Key('confirm_button'))); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + expect(mockApi.deleteAllDataCalled, true); + expect(find.byKey(const Key('fatal_error_callout')), findsOneWidget); + expect(find.byType(PrivacySecurityScreen), findsNothing); + }); + testWidgets('confirm button shows loading during delete operation', (tester) async { mockApi.deleteAllDataDelay = const Duration(seconds: 2); @@ -120,7 +152,7 @@ void main() { final confirmButton = tester.widget(find.byKey(const Key('confirm_button'))); expect(confirmButton.loading, true); - await tester.pumpAndSettle(); + await tester.pump(const Duration(seconds: 3)); }); testWidgets('delete all data shows error when API fails', (tester) async { @@ -134,7 +166,6 @@ void main() { await tester.tap(find.byKey(const Key('confirm_button'))); await tester.pump(); await tester.pump(const Duration(milliseconds: 500)); - await tester.pumpAndSettle(); await tester.pump(const Duration(seconds: 1)); expect(mockApi.deleteAllDataCalled, true); diff --git a/test/test_helpers.dart b/test/test_helpers.dart index adffa070e..fdcd561e0 100644 --- a/test/test_helpers.dart +++ b/test/test_helpers.dart @@ -124,8 +124,8 @@ final testImageProvider = MemoryImage( void mockPathProvider() { final binding = TestWidgetsFlutterBinding.ensureInitialized(); const channel = MethodChannel('plugins.flutter.io/path_provider'); + final dir = Directory.systemTemp.createTempSync('wn_test_'); binding.defaultBinaryMessenger.setMockMethodCallHandler(channel, (MethodCall methodCall) async { - final dir = await Directory.systemTemp.createTemp('wn_test_'); return dir.path; }); } diff --git a/test/utils/reset_marker_test.dart b/test/utils/reset_marker_test.dart new file mode 100644 index 000000000..76e2bddeb --- /dev/null +++ b/test/utils/reset_marker_test.dart @@ -0,0 +1,141 @@ +// ignore_for_file: avoid_slow_async_io + +import 'dart:io'; + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:whitenoise/utils/reset_marker.dart'; + +import '../test_helpers.dart'; + +void main() { + setUpAll(() { + mockPathProvider(); + }); + + setUp(() { + final markerDir = Directory.systemTemp.createTempSync('wn_reset_marker_'); + debugResetPendingMarkerFile = () async => File('${markerDir.path}/reset_pending'); + addTearDown(() async { + debugResetPendingMarkerFile = null; + if (await markerDir.exists()) { + await markerDir.delete(recursive: true); + } + }); + }); + + tearDown(() async { + await clearResetPending(); + debugMarkResetPending = null; + debugClearResetPending = null; + }); + + test('markResetPending creates marker and clearResetPending removes it', () async { + final marker = await resetPendingMarkerFile(); + + await markResetPending(); + + expect(await marker.exists(), true); + expect(await marker.parent.exists(), true); + expect(await marker.readAsString(), isNotEmpty); + + await clearResetPending(); + + expect(await marker.exists(), false); + }); + + test('markResetPending and clearResetPending use debug overrides when provided', () async { + var markCalled = false; + var clearCalled = false; + debugMarkResetPending = () async => markCalled = true; + debugClearResetPending = () async => clearCalled = true; + + await markResetPending(); + await clearResetPending(); + + expect(markCalled, true); + expect(clearCalled, true); + }); + + test('recoverPendingReset does nothing when no marker exists', () async { + var clearedSecureStorage = false; + var clearedForegroundTaskData = false; + final dataDir = await Directory.systemTemp.createTemp('wn_data_'); + final logsDir = await Directory.systemTemp.createTemp('wn_logs_'); + + await recoverPendingReset( + dataDir: dataDir.path, + logsDir: logsDir.path, + clearSecureStorage: () async => clearedSecureStorage = true, + clearForegroundTaskData: () async => clearedForegroundTaskData = true, + ); + + expect(await dataDir.exists(), true); + expect(await logsDir.exists(), true); + expect(clearedSecureStorage, false); + expect(clearedForegroundTaskData, false); + + await dataDir.delete(recursive: true); + await logsDir.delete(recursive: true); + }); + + test('recoverPendingReset clears local reset surface and marker', () async { + var clearedSecureStorage = false; + var clearedForegroundTaskData = false; + final dataDir = await Directory.systemTemp.createTemp('wn_data_'); + final logsDir = await Directory.systemTemp.createTemp('wn_logs_'); + await File('${dataDir.path}/data.sqlite').writeAsString('data'); + await File('${logsDir.path}/app.log').writeAsString('logs'); + + await markResetPending(); + expect(await (await resetPendingMarkerFile()).exists(), true); + + await recoverPendingReset( + dataDir: dataDir.path, + logsDir: logsDir.path, + clearSecureStorage: () async => clearedSecureStorage = true, + clearForegroundTaskData: () async => clearedForegroundTaskData = true, + ); + + expect(await dataDir.exists(), false); + expect(await logsDir.exists(), false); + expect(clearedSecureStorage, true); + expect(clearedForegroundTaskData, true); + expect(await (await resetPendingMarkerFile()).exists(), false); + }); + + test('recoverPendingReset uses FlutterSecureStorage.deleteAll by default', () async { + final secureStorageCalls = []; + const secureStorageChannel = MethodChannel( + 'plugins.it_nomads.com/flutter_secure_storage', + ); + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + secureStorageChannel, + (call) async { + secureStorageCalls.add(call.method); + return null; + }, + ); + addTearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger.setMockMethodCallHandler( + secureStorageChannel, + null, + ); + }); + + var clearedForegroundTaskData = false; + final dataDir = await Directory.systemTemp.createTemp('wn_data_'); + final logsDir = await Directory.systemTemp.createTemp('wn_logs_'); + await markResetPending(); + + await recoverPendingReset( + dataDir: dataDir.path, + logsDir: logsDir.path, + clearForegroundTaskData: () async => clearedForegroundTaskData = true, + ); + + expect(secureStorageCalls, contains('deleteAll')); + expect(clearedForegroundTaskData, true); + expect(await (await resetPendingMarkerFile()).exists(), false); + }); +} diff --git a/widgetbook/pubspec.lock b/widgetbook/pubspec.lock index a10849f07..3c2c0cb99 100644 --- a/widgetbook/pubspec.lock +++ b/widgetbook/pubspec.lock @@ -447,10 +447,10 @@ packages: dependency: transitive description: name: flutter_rust_bridge - sha256: "37ef40bc6f863652e865f0b2563ea07f0d3c58d8efad803cc01933a4b2ee067e" + sha256: e87d6b9ee934dcd24a128ccb2bd91905d2d5fe5c06245d6a8f5477d4907a437a url: "https://pub.dev" source: hosted - version: "2.11.1" + version: "2.12.0" flutter_screenutil: dependency: "direct main" description: