diff --git a/CHANGELOG.md b/CHANGELOG.md index fd9e1fc9..40292fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.2.30 + +- Fix: Changed minimum threshold for the user discovery to 3 + ## 0.2.28 - Improved: Design of some UI components diff --git a/lib/core/bridge/callbacks.dart b/lib/core/bridge/callbacks.dart index c7bdf17e..ad534436 100644 --- a/lib/core/bridge/callbacks.dart +++ b/lib/core/bridge/callbacks.dart @@ -9,8 +9,10 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; // These functions are ignored because they are not marked as `pub`: `get_callbacks` // These types are ignored because they are neither used by any `pub` functions nor (for structs and enums) marked `#[frb(unignore)]`: `FlutterCallbacks`, `Logging`, `UserDiscoveryCallbacks` +// 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`, `clone`, `clone` Future initFlutterCallbacks({ + required int callbackId, required FutureOr> Function() loggingGetStreamSink, required FutureOr Function(Uint8List) userDiscoverySignData, required FutureOr Function(Uint8List, Uint8List, Uint8List) @@ -39,6 +41,7 @@ Future initFlutterCallbacks({ required FutureOr Function(PlatformInt64) userDiscoveryGetContactPromotion, }) => RustLib.instance.api.crateBridgeCallbacksInitFlutterCallbacks( + callbackId: callbackId, loggingGetStreamSink: loggingGetStreamSink, userDiscoverySignData: userDiscoverySignData, userDiscoveryVerifySignature: userDiscoveryVerifySignature, diff --git a/lib/core/bridge/wrapper/user_discovery.dart b/lib/core/bridge/wrapper/user_discovery.dart index dab9c080..bff42c1d 100644 --- a/lib/core/bridge/wrapper/user_discovery.dart +++ b/lib/core/bridge/wrapper/user_discovery.dart @@ -9,36 +9,45 @@ import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; class FlutterUserDiscovery { const FlutterUserDiscovery(); - static Future getCurrentVersion() => RustLib.instance.api - .crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion(); + static Future getCurrentVersion({required int callbackId}) => + RustLib.instance.api + .crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion( + callbackId: callbackId, + ); static Future> getNewMessages({ + required int callbackId, required PlatformInt64 contactId, required List receivedVersion, }) => RustLib.instance.api .crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetNewMessages( + callbackId: callbackId, contactId: contactId, receivedVersion: receivedVersion, ); static Future handleNewMessages({ + required int callbackId, required PlatformInt64 contactId, PlatformInt64? publicKeyVerifiedTimestamp, required List messages, }) => RustLib.instance.api .crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleNewMessages( + callbackId: callbackId, contactId: contactId, publicKeyVerifiedTimestamp: publicKeyVerifiedTimestamp, messages: messages, ); static Future initializeOrUpdate({ + required int callbackId, required int threshold, required PlatformInt64 userId, required List publicKey, required bool sharePromotion, }) => RustLib.instance.api .crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryInitializeOrUpdate( + callbackId: callbackId, threshold: threshold, userId: userId, publicKey: publicKey, @@ -46,19 +55,23 @@ class FlutterUserDiscovery { ); static Future shouldRequestNewMessages({ + required int callbackId, required PlatformInt64 contactId, required List version, }) => RustLib.instance.api .crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryShouldRequestNewMessages( + callbackId: callbackId, contactId: contactId, version: version, ); static Future updateVerificationStateForUser({ + required int callbackId, required PlatformInt64 contactId, PlatformInt64? publicKeyVerifiedTimestamp, }) => RustLib.instance.api .crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryUpdateVerificationStateForUser( + callbackId: callbackId, contactId: contactId, publicKeyVerifiedTimestamp: publicKeyVerifiedTimestamp, ); diff --git a/lib/core/frb_generated.dart b/lib/core/frb_generated.dart index ae786047..8570e08f 100644 --- a/lib/core/frb_generated.dart +++ b/lib/core/frb_generated.dart @@ -87,16 +87,20 @@ class RustLib extends BaseEntrypoint { abstract class RustLibApi extends BaseApi { Future - crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion(); + crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion({ + required int callbackId, + }); Future> crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetNewMessages({ + required int callbackId, required PlatformInt64 contactId, required List receivedVersion, }); Future crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleNewMessages({ + required int callbackId, required PlatformInt64 contactId, PlatformInt64? publicKeyVerifiedTimestamp, required List messages, @@ -104,6 +108,7 @@ abstract class RustLibApi extends BaseApi { Future crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryInitializeOrUpdate({ + required int callbackId, required int threshold, required PlatformInt64 userId, required List publicKey, @@ -112,17 +117,20 @@ abstract class RustLibApi extends BaseApi { Future crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryShouldRequestNewMessages({ + required int callbackId, required PlatformInt64 contactId, required List version, }); Future crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryUpdateVerificationStateForUser({ + required int callbackId, required PlatformInt64 contactId, PlatformInt64? publicKeyVerifiedTimestamp, }); Future crateBridgeCallbacksInitFlutterCallbacks({ + required int callbackId, required FutureOr> Function() loggingGetStreamSink, required FutureOr Function(Uint8List) userDiscoverySignData, required FutureOr Function(Uint8List, Uint8List, Uint8List) @@ -242,11 +250,14 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { @override Future - crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion() { + crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersion({ + required int callbackId, + }) { return handler.executeNormal( NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_u_32(callbackId, serializer); pdeCallFfi( generalizedFrbRustBinding, serializer, @@ -260,7 +271,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ), constMeta: kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersionConstMeta, - argValues: [], + argValues: [callbackId], apiImpl: this, ), ); @@ -270,12 +281,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { get kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetCurrentVersionConstMeta => const TaskConstMeta( debugName: "flutter_user_discovery_get_current_version", - argNames: [], + argNames: ["callbackId"], ); @override Future> crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetNewMessages({ + required int callbackId, required PlatformInt64 contactId, required List receivedVersion, }) { @@ -283,6 +295,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_u_32(callbackId, serializer); sse_encode_i_64(contactId, serializer); sse_encode_list_prim_u_8_loose(receivedVersion, serializer); pdeCallFfi( @@ -298,7 +311,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ), constMeta: kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetNewMessagesConstMeta, - argValues: [contactId, receivedVersion], + argValues: [callbackId, contactId, receivedVersion], apiImpl: this, ), ); @@ -308,12 +321,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { get kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryGetNewMessagesConstMeta => const TaskConstMeta( debugName: "flutter_user_discovery_get_new_messages", - argNames: ["contactId", "receivedVersion"], + argNames: ["callbackId", "contactId", "receivedVersion"], ); @override Future crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleNewMessages({ + required int callbackId, required PlatformInt64 contactId, PlatformInt64? publicKeyVerifiedTimestamp, required List messages, @@ -322,6 +336,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_u_32(callbackId, serializer); sse_encode_i_64(contactId, serializer); sse_encode_opt_box_autoadd_i_64( publicKeyVerifiedTimestamp, @@ -341,7 +356,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ), constMeta: kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleNewMessagesConstMeta, - argValues: [contactId, publicKeyVerifiedTimestamp, messages], + argValues: [ + callbackId, + contactId, + publicKeyVerifiedTimestamp, + messages, + ], apiImpl: this, ), ); @@ -351,12 +371,18 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { get kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryHandleNewMessagesConstMeta => const TaskConstMeta( debugName: "flutter_user_discovery_handle_new_messages", - argNames: ["contactId", "publicKeyVerifiedTimestamp", "messages"], + argNames: [ + "callbackId", + "contactId", + "publicKeyVerifiedTimestamp", + "messages", + ], ); @override Future crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryInitializeOrUpdate({ + required int callbackId, required int threshold, required PlatformInt64 userId, required List publicKey, @@ -366,6 +392,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_u_32(callbackId, serializer); sse_encode_u_8(threshold, serializer); sse_encode_i_64(userId, serializer); sse_encode_list_prim_u_8_loose(publicKey, serializer); @@ -383,7 +410,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ), constMeta: kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryInitializeOrUpdateConstMeta, - argValues: [threshold, userId, publicKey, sharePromotion], + argValues: [callbackId, threshold, userId, publicKey, sharePromotion], apiImpl: this, ), ); @@ -393,12 +420,19 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { get kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryInitializeOrUpdateConstMeta => const TaskConstMeta( debugName: "flutter_user_discovery_initialize_or_update", - argNames: ["threshold", "userId", "publicKey", "sharePromotion"], + argNames: [ + "callbackId", + "threshold", + "userId", + "publicKey", + "sharePromotion", + ], ); @override Future crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryShouldRequestNewMessages({ + required int callbackId, required PlatformInt64 contactId, required List version, }) { @@ -406,6 +440,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_u_32(callbackId, serializer); sse_encode_i_64(contactId, serializer); sse_encode_list_prim_u_8_loose(version, serializer); pdeCallFfi( @@ -421,7 +456,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ), constMeta: kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryShouldRequestNewMessagesConstMeta, - argValues: [contactId, version], + argValues: [callbackId, contactId, version], apiImpl: this, ), ); @@ -431,12 +466,13 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { get kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryShouldRequestNewMessagesConstMeta => const TaskConstMeta( debugName: "flutter_user_discovery_should_request_new_messages", - argNames: ["contactId", "version"], + argNames: ["callbackId", "contactId", "version"], ); @override Future crateBridgeWrapperUserDiscoveryFlutterUserDiscoveryUpdateVerificationStateForUser({ + required int callbackId, required PlatformInt64 contactId, PlatformInt64? publicKeyVerifiedTimestamp, }) { @@ -444,6 +480,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_u_32(callbackId, serializer); sse_encode_i_64(contactId, serializer); sse_encode_opt_box_autoadd_i_64( publicKeyVerifiedTimestamp, @@ -462,7 +499,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ), constMeta: kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryUpdateVerificationStateForUserConstMeta, - argValues: [contactId, publicKeyVerifiedTimestamp], + argValues: [callbackId, contactId, publicKeyVerifiedTimestamp], apiImpl: this, ), ); @@ -472,11 +509,12 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { get kCrateBridgeWrapperUserDiscoveryFlutterUserDiscoveryUpdateVerificationStateForUserConstMeta => const TaskConstMeta( debugName: "flutter_user_discovery_update_verification_state_for_user", - argNames: ["contactId", "publicKeyVerifiedTimestamp"], + argNames: ["callbackId", "contactId", "publicKeyVerifiedTimestamp"], ); @override Future crateBridgeCallbacksInitFlutterCallbacks({ + required int callbackId, required FutureOr> Function() loggingGetStreamSink, required FutureOr Function(Uint8List) userDiscoverySignData, required FutureOr Function(Uint8List, Uint8List, Uint8List) @@ -513,6 +551,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { NormalTask( callFfi: (port_) { final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_u_32(callbackId, serializer); sse_encode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException( loggingGetStreamSink, serializer, @@ -586,6 +625,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { ), constMeta: kCrateBridgeCallbacksInitFlutterCallbacksConstMeta, argValues: [ + callbackId, loggingGetStreamSink, userDiscoverySignData, userDiscoveryVerifySignature, @@ -611,6 +651,7 @@ class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { const TaskConstMeta( debugName: "init_flutter_callbacks", argNames: [ + "callbackId", "loggingGetStreamSink", "userDiscoverySignData", "userDiscoveryVerifySignature", diff --git a/lib/globals.dart b/lib/globals.dart index 35de1911..8ec8015a 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -1,8 +1,11 @@ import 'dart:async'; +import 'dart:math'; import 'package:camera/camera.dart'; import 'package:path_provider/path_provider.dart'; import 'package:twonly/src/utils/log.dart'; +final int isolateCallbackId = Random().nextInt(0x7FFFFFFF); + class AppEnvironment { static late String cacheDir; static late String supportDir; diff --git a/lib/src/callbacks/callbacks.dart b/lib/src/callbacks/callbacks.dart index 07862ace..37da7a30 100644 --- a/lib/src/callbacks/callbacks.dart +++ b/lib/src/callbacks/callbacks.dart @@ -1,9 +1,11 @@ import 'package:twonly/core/bridge/callbacks.dart'; +import 'package:twonly/globals.dart'; import 'package:twonly/src/callbacks/logging.callbacks.dart'; import 'package:twonly/src/callbacks/user_discovery.callbacks.dart'; Future initFlutterCallbacksForRust() async { await initFlutterCallbacks( + callbackId: isolateCallbackId, loggingGetStreamSink: LoggingCallbacks.getStreamSink, userDiscoverySetShares: UserDiscoveryCallbacks.setShares, userDiscoveryGetShareForContact: diff --git a/lib/src/database/daos/key_verification.dao.dart b/lib/src/database/daos/key_verification.dao.dart index caa8272c..93e3e007 100644 --- a/lib/src/database/daos/key_verification.dao.dart +++ b/lib/src/database/daos/key_verification.dao.dart @@ -1,6 +1,7 @@ import 'package:clock/clock.dart'; import 'package:drift/drift.dart'; import 'package:twonly/core/bridge/wrapper/user_discovery.dart'; +import 'package:twonly/globals.dart'; import 'package:twonly/locator.dart'; import 'package:twonly/src/database/tables/contacts.table.dart'; import 'package:twonly/src/database/tables/groups.table.dart'; @@ -216,6 +217,7 @@ class KeyVerificationDao extends DatabaseAccessor ); if (userService.currentUser.isUserDiscoveryEnabled) { await FlutterUserDiscovery.updateVerificationStateForUser( + callbackId: isolateCallbackId, contactId: contactId, publicKeyVerifiedTimestamp: clock.now().millisecondsSinceEpoch, ); @@ -232,6 +234,7 @@ class KeyVerificationDao extends DatabaseAccessor )..where((kv) => kv.contactId.equals(contactId))).go(); if (userService.currentUser.isUserDiscoveryEnabled) { await FlutterUserDiscovery.updateVerificationStateForUser( + callbackId: isolateCallbackId, contactId: contactId, ); } @@ -251,6 +254,7 @@ class KeyVerificationDao extends DatabaseAccessor final remaining = await getContactVerification(contactId); if (remaining.isEmpty && userService.currentUser.isUserDiscoveryEnabled) { await FlutterUserDiscovery.updateVerificationStateForUser( + callbackId: isolateCallbackId, contactId: contactId, ); } diff --git a/lib/src/model/json/userdata.model.dart b/lib/src/model/json/userdata.model.dart index c5b4ce2b..a1005444 100644 --- a/lib/src/model/json/userdata.model.dart +++ b/lib/src/model/json/userdata.model.dart @@ -114,8 +114,8 @@ class UserData { @JsonKey(defaultValue: 4) int requiredSendImages = 4; - @JsonKey(defaultValue: 2) - int userDiscoveryThreshold = 2; + @JsonKey(defaultValue: 3) + int userDiscoveryThreshold = 3; @JsonKey(defaultValue: false) bool userDiscoveryRequiresManualApproval = false; diff --git a/lib/src/model/json/userdata.model.g.dart b/lib/src/model/json/userdata.model.g.dart index 9cec2fea..cb8ee42b 100644 --- a/lib/src/model/json/userdata.model.g.dart +++ b/lib/src/model/json/userdata.model.g.dart @@ -62,7 +62,7 @@ UserData _$UserDataFromJson(Map json) => ), ) ..storeMediaFilesInGallery = - json['storeMediaFilesInGallery'] as bool? ?? false + json['storeMediaFilesInGallery'] as bool? ?? true ..autoStoreAllSendUnlimitedMediaFiles = json['autoStoreAllSendUnlimitedMediaFiles'] as bool? ?? false ..typingIndicators = json['typingIndicators'] as bool? ?? true @@ -78,7 +78,7 @@ UserData _$UserDataFromJson(Map json) => json['isUserDiscoveryEnabled'] as bool? ?? false ..requiredSendImages = (json['requiredSendImages'] as num?)?.toInt() ?? 4 ..userDiscoveryThreshold = - (json['userDiscoveryThreshold'] as num?)?.toInt() ?? 2 + (json['userDiscoveryThreshold'] as num?)?.toInt() ?? 3 ..userDiscoveryRequiresManualApproval = json['userDiscoveryRequiresManualApproval'] as bool? ?? false ..userDiscoverySharePromotion = diff --git a/lib/src/services/migrations.service.dart b/lib/src/services/migrations.service.dart index 979cb260..6e2bf460 100644 --- a/lib/src/services/migrations.service.dart +++ b/lib/src/services/migrations.service.dart @@ -14,6 +14,7 @@ import 'package:twonly/src/database/twonly.db.dart'; import 'package:twonly/src/model/json/signal_identity.model.dart'; import 'package:twonly/src/services/api/mediafiles/download.api.dart'; import 'package:twonly/src/services/user.service.dart'; +import 'package:twonly/src/services/user_discovery.service.dart'; import 'package:twonly/src/utils/log.dart'; import 'package:twonly/src/utils/secure_storage.dart'; import 'package:twonly/src/visual/views/onboarding/setup.view.dart'; @@ -144,6 +145,19 @@ Future runMigrations() async { } } + if (userService.currentUser.appVersion < 116) { + if (userService.currentUser.userDiscoveryThreshold == 2) { + if (userService.currentUser.isUserDiscoveryEnabled) { + await UserDiscoveryService.initializeOrUpdate( + threshold: 3, + sharePromotion: userService.currentUser.userDiscoverySharePromotion, + ); + } else { + await UserService.update((u) => u..userDiscoveryThreshold = 3); + } + } + await UserService.update((u) => u.appVersion = 116); + } if (kDebugMode) { assert( AppState.latestAppVersionId == 116, diff --git a/lib/src/services/user_discovery.service.dart b/lib/src/services/user_discovery.service.dart index 11d41f1f..7dfff07f 100644 --- a/lib/src/services/user_discovery.service.dart +++ b/lib/src/services/user_discovery.service.dart @@ -84,6 +84,7 @@ class UserDiscoveryService { final publicKey = await getUserPublicKey(); Log.info('UserDiscoveryService: initializing Rust bridge'); await FlutterUserDiscovery.initializeOrUpdate( + callbackId: isolateCallbackId, threshold: threshold, userId: userId, publicKey: publicKey, @@ -104,7 +105,7 @@ class UserDiscoveryService { static Future getCurrentVersion() async { try { - return await FlutterUserDiscovery.getCurrentVersion() + return await FlutterUserDiscovery.getCurrentVersion(callbackId: isolateCallbackId) .timeout(const Duration(seconds: 5)); } catch (e) { Log.error(e); @@ -139,6 +140,7 @@ class UserDiscoveryService { ) async { try { return await FlutterUserDiscovery.shouldRequestNewMessages( + callbackId: isolateCallbackId, contactId: fromUserId, version: receivedVersion, ).timeout(const Duration(seconds: 5)); @@ -154,6 +156,7 @@ class UserDiscoveryService { ) async { try { return await FlutterUserDiscovery.getNewMessages( + callbackId: isolateCallbackId, contactId: fromUserId, receivedVersion: receivedVersion, ).timeout(const Duration(seconds: 5)); @@ -172,6 +175,7 @@ class UserDiscoveryService { .getContactVerification(fromUserId); return await FlutterUserDiscovery.handleNewMessages( + callbackId: isolateCallbackId, contactId: fromUserId, messages: messages, publicKeyVerifiedTimestamp: diff --git a/lib/src/visual/views/settings/privacy/user_discovery/components/user_discovery_setup.comp.dart b/lib/src/visual/views/settings/privacy/user_discovery/components/user_discovery_setup.comp.dart index 56a1a897..57e66462 100644 --- a/lib/src/visual/views/settings/privacy/user_discovery/components/user_discovery_setup.comp.dart +++ b/lib/src/visual/views/settings/privacy/user_discovery/components/user_discovery_setup.comp.dart @@ -108,7 +108,8 @@ class UserDiscoverySetupComp extends StatelessWidget { @override Widget build(BuildContext context) { final showShareYourFriends = - showOnlySpecificPage == UserDiscoveryPages.all || showOnlySpecificPage == UserDiscoveryPages.shareYourFriends; + showOnlySpecificPage == UserDiscoveryPages.all || + showOnlySpecificPage == UserDiscoveryPages.shareYourFriends; final showLetYourFriendsFindYou = showOnlySpecificPage == UserDiscoveryPages.all || showOnlySpecificPage == UserDiscoveryPages.letYourFriendsFindYou; @@ -336,7 +337,9 @@ class UserDiscoverySetupComp extends StatelessWidget { ), ), subtitle: Text( - context.lang.userDiscoverySettingsManualApprovalDesc, + context + .lang + .userDiscoverySettingsManualApprovalDesc, style: TextStyle( fontSize: 11, color: context.color.onSurfaceVariant, @@ -350,13 +353,16 @@ class UserDiscoverySetupComp extends StatelessWidget { ), ], ), - crossFadeState: state.isUserDiscoveryEnabled ? CrossFadeState.showSecond : CrossFadeState.showFirst, + crossFadeState: state.isUserDiscoveryEnabled + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, duration: const Duration(milliseconds: 300), ), ], ), ), - if (showOnlySpecificPage == UserDiscoveryPages.all) const SizedBox(height: 48), + if (showOnlySpecificPage == UserDiscoveryPages.all) + const SizedBox(height: 48), ], if (showLetYourFriendsFindYou) ...[ Text( @@ -587,7 +593,9 @@ class UserDiscoverySetupComp extends StatelessWidget { children: [ Expanded( child: Text( - context.lang.userDiscoverySettingsMutualFriends, + context + .lang + .userDiscoverySettingsMutualFriends, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, @@ -603,9 +611,10 @@ class UserDiscoverySetupComp extends StatelessWidget { color: context.color.surface, borderRadius: BorderRadius.circular(8), border: Border.all( - color: context.color.outlineVariant.withValues( - alpha: 0.5, - ), + color: context.color.outlineVariant + .withValues( + alpha: 0.5, + ), ), ), child: DropdownButtonHideUnderline( @@ -616,9 +625,9 @@ class UserDiscoverySetupComp extends StatelessWidget { fontWeight: FontWeight.bold, ), items: List.generate( - 9, + 8, (index) { - final value = index + 2; + final value = index + 3; return DropdownMenuItem( value: value, child: Text('$value'), @@ -640,7 +649,9 @@ class UserDiscoverySetupComp extends StatelessWidget { ), ], ), - crossFadeState: state.sharePromotion ? CrossFadeState.showSecond : CrossFadeState.showFirst, + crossFadeState: state.sharePromotion + ? CrossFadeState.showSecond + : CrossFadeState.showFirst, duration: const Duration(milliseconds: 300), ), ], diff --git a/lib/src/visual/views/settings/privacy/user_discovery/user_discovery_settings.view.dart b/lib/src/visual/views/settings/privacy/user_discovery/user_discovery_settings.view.dart index 06b926ad..f4b2c246 100644 --- a/lib/src/visual/views/settings/privacy/user_discovery/user_discovery_settings.view.dart +++ b/lib/src/visual/views/settings/privacy/user_discovery/user_discovery_settings.view.dart @@ -26,7 +26,7 @@ class _UserDiscoverySettingsViewState extends State { isUserDiscoveryEnabled: u.isUserDiscoveryEnabled, sharePromotion: u.userDiscoverySharePromotion, isManualApprovalEnabled: u.userDiscoveryRequiresManualApproval, - threshold: u.userDiscoveryThreshold, + threshold: u.userDiscoveryThreshold < 3 ? 3 : u.userDiscoveryThreshold, ); } diff --git a/rust/src/bridge/callbacks.rs b/rust/src/bridge/callbacks.rs index 6aaaafcc..1b94d68f 100644 --- a/rust/src/bridge/callbacks.rs +++ b/rust/src/bridge/callbacks.rs @@ -9,7 +9,13 @@ use crate::error::{Result, TwonlyError}; use crate::{callback_generator, frb_generated::StreamSink}; use std::sync::Arc; -static FLUTTER_CALLBACKS: std::sync::RwLock> = +use std::collections::HashMap; + +tokio::task_local! { + pub(crate) static CURRENT_CALLBACK_ID: u32; +} + +pub(crate) static FLUTTER_CALLBACKS: std::sync::RwLock>> = std::sync::RwLock::new(None); // This will also generate the function init_flutter_callbacks which MUST be called from Flutter to initialize the callbacks @@ -41,9 +47,24 @@ callback_generator! { } pub(crate) fn get_callbacks() -> Result { - FLUTTER_CALLBACKS - .read() - .unwrap() - .clone() - .ok_or(TwonlyError::MissingCallbackInitialization) + let caller_opt = CURRENT_CALLBACK_ID.try_with(|&c| c).ok(); + + let lock = FLUTTER_CALLBACKS.read().unwrap(); + let map = lock.as_ref().ok_or(TwonlyError::MissingCallbackInitialization)?; + + if let Some(id) = caller_opt { + if let Some(cb) = map.get(&id) { + return Ok(cb.clone()); + } + } + + // Fallback: if not in a scoped tokio task or if the specific callback_id isn't found, + // we pick the first available callbacks from the map. This gracefully handles + // tracing initialization which happens outside of any scoped task. + if let Some((_, cb)) = map.iter().next() { + tracing::error!("FlutterCallbacks fallback used: No CURRENT_CALLBACK_ID scope was found, or the ID was missing from the map. Using an arbitrary callback. This may lead to race conditions if multiple isolates are active."); + return Ok(cb.clone()); + } + + Err(TwonlyError::MissingCallbackInitialization) } diff --git a/rust/src/bridge/callbacks/macros.rs b/rust/src/bridge/callbacks/macros.rs index 61700f88..02b1d8d8 100644 --- a/rust/src/bridge/callbacks/macros.rs +++ b/rust/src/bridge/callbacks/macros.rs @@ -32,6 +32,7 @@ macro_rules! callback_generator { // 3. Generate the Automated Init Function paste::paste! { pub fn init_flutter_callbacks( + callback_id: u32, $( $( // Parameters: sub-struct_field + _ + fn_name @@ -49,9 +50,11 @@ macro_rules! callback_generator { )* }; - // Use the static global strictly named FLUTTER_CALLBACKS let mut lock = FLUTTER_CALLBACKS.write().unwrap(); - *lock = Some(callbacks); + if lock.is_none() { + *lock = Some(std::collections::HashMap::new()); + } + lock.as_mut().unwrap().insert(callback_id, callbacks); } } }; diff --git a/rust/src/bridge/wrapper/user_discovery.rs b/rust/src/bridge/wrapper/user_discovery.rs index 4ecb59c2..c19ec41f 100644 --- a/rust/src/bridge/wrapper/user_discovery.rs +++ b/rust/src/bridge/wrapper/user_discovery.rs @@ -1,3 +1,4 @@ +use crate::bridge::callbacks::CURRENT_CALLBACK_ID; use crate::bridge::get_twonly_flutter; use crate::error::Result; @@ -5,78 +6,95 @@ pub struct FlutterUserDiscovery {} impl FlutterUserDiscovery { pub async fn initialize_or_update( + callback_id: u32, threshold: u8, user_id: i64, public_key: Vec, share_promotion: bool, ) -> Result<()> { - tracing::info!("Rust bridge: initialize_or_update started"); - let twonly = get_twonly_flutter()?; - tracing::info!("Rust bridge: getting user_discovery lock"); - let user_discovery = twonly.user_discovery.get().await; - tracing::info!("Rust bridge: calling initialize_or_update on protocols"); - let res = user_discovery - .initialize_or_update(threshold, user_id, public_key, share_promotion) - .await; - tracing::info!("Rust bridge: initialize_or_update on protocols finished"); - Ok(res?) + CURRENT_CALLBACK_ID.scope(callback_id, async move { + tracing::info!("Rust bridge: initialize_or_update started"); + let twonly = get_twonly_flutter()?; + tracing::info!("Rust bridge: getting user_discovery lock"); + let user_discovery = twonly.user_discovery.get().await; + tracing::info!("Rust bridge: calling initialize_or_update on protocols"); + let res = user_discovery + .initialize_or_update(threshold, user_id, public_key, share_promotion) + .await; + tracing::info!("Rust bridge: initialize_or_update on protocols finished"); + Ok(res?) + }).await } - pub async fn get_current_version() -> Result> { - Ok(get_twonly_flutter()? - .user_discovery - .get() - .await - .get_current_version() - .await?) + pub async fn get_current_version(callback_id: u32) -> Result> { + CURRENT_CALLBACK_ID.scope(callback_id, async move { + Ok(get_twonly_flutter()? + .user_discovery + .get() + .await + .get_current_version() + .await?) + }).await } pub async fn get_new_messages( + callback_id: u32, contact_id: i64, received_version: &[u8], ) -> Result>> { - Ok(get_twonly_flutter()? - .user_discovery - .get() - .await - .get_new_messages(contact_id, received_version) - .await?) + CURRENT_CALLBACK_ID.scope(callback_id, async move { + Ok(get_twonly_flutter()? + .user_discovery + .get() + .await + .get_new_messages(contact_id, received_version) + .await?) + }).await } pub async fn should_request_new_messages( + callback_id: u32, contact_id: i64, version: &[u8], ) -> Result>> { - Ok(get_twonly_flutter()? - .user_discovery - .get() - .await - .should_request_new_messages(contact_id, version) - .await?) + CURRENT_CALLBACK_ID.scope(callback_id, async move { + Ok(get_twonly_flutter()? + .user_discovery + .get() + .await + .should_request_new_messages(contact_id, version) + .await?) + }).await } pub async fn handle_new_messages( + callback_id: u32, contact_id: i64, public_key_verified_timestamp: Option, messages: Vec>, ) -> Result<()> { - Ok(get_twonly_flutter()? - .user_discovery - .get() - .await - .handle_new_messages(contact_id, public_key_verified_timestamp, messages) - .await?) + CURRENT_CALLBACK_ID.scope(callback_id, async move { + Ok(get_twonly_flutter()? + .user_discovery + .get() + .await + .handle_new_messages(contact_id, public_key_verified_timestamp, messages) + .await?) + }).await } pub async fn update_verification_state_for_user( + callback_id: u32, contact_id: i64, public_key_verified_timestamp: Option, ) -> Result<()> { - Ok(get_twonly_flutter()? - .user_discovery - .get() - .await - .update_verification_state_for_user(contact_id, public_key_verified_timestamp) - .await?) + CURRENT_CALLBACK_ID.scope(callback_id, async move { + Ok(get_twonly_flutter()? + .user_discovery + .get() + .await + .update_verification_state_for_user(contact_id, public_key_verified_timestamp) + .await?) + }).await } } diff --git a/rust/src/frb_generated.rs b/rust/src/frb_generated.rs index 0be3e628..c63d3a4b 100644 --- a/rust/src/frb_generated.rs +++ b/rust/src/frb_generated.rs @@ -55,9 +55,9 @@ fn wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_curr FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "flutter_user_discovery_get_current_version", 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 { + let api_callback_id = ::sse_decode(&mut deserializer);deserializer.end(); move |context| async move { transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { - let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::get_current_version().await?; Ok(output_ok) + let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::get_current_version(api_callback_id).await?; Ok(output_ok) })().await) } }) } @@ -70,10 +70,11 @@ fn wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_get_new_ FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "flutter_user_discovery_get_new_messages", 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); - let api_contact_id = ::sse_decode(&mut deserializer); + let api_callback_id = ::sse_decode(&mut deserializer); +let api_contact_id = ::sse_decode(&mut deserializer); let api_received_version = >::sse_decode(&mut deserializer);deserializer.end(); move |context| async move { transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { - let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::get_new_messages(api_contact_id, &api_received_version).await?; Ok(output_ok) + let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::get_new_messages(api_callback_id, api_contact_id, &api_received_version).await?; Ok(output_ok) })().await) } }) } @@ -86,11 +87,12 @@ fn wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_handle_n FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "flutter_user_discovery_handle_new_messages", 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); - let api_contact_id = ::sse_decode(&mut deserializer); + let api_callback_id = ::sse_decode(&mut deserializer); +let api_contact_id = ::sse_decode(&mut deserializer); let api_public_key_verified_timestamp = >::sse_decode(&mut deserializer); let api_messages = >>::sse_decode(&mut deserializer);deserializer.end(); move |context| async move { transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { - let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::handle_new_messages(api_contact_id, api_public_key_verified_timestamp, api_messages).await?; Ok(output_ok) + let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::handle_new_messages(api_callback_id, api_contact_id, api_public_key_verified_timestamp, api_messages).await?; Ok(output_ok) })().await) } }) } @@ -103,12 +105,13 @@ fn wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_initiali FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "flutter_user_discovery_initialize_or_update", 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); - let api_threshold = ::sse_decode(&mut deserializer); + let api_callback_id = ::sse_decode(&mut deserializer); +let api_threshold = ::sse_decode(&mut deserializer); let api_user_id = ::sse_decode(&mut deserializer); let api_public_key = >::sse_decode(&mut deserializer); let api_share_promotion = ::sse_decode(&mut deserializer);deserializer.end(); move |context| async move { transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { - let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::initialize_or_update(api_threshold, api_user_id, api_public_key, api_share_promotion).await?; Ok(output_ok) + let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::initialize_or_update(api_callback_id, api_threshold, api_user_id, api_public_key, api_share_promotion).await?; Ok(output_ok) })().await) } }) } @@ -121,10 +124,11 @@ fn wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_should_r FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "flutter_user_discovery_should_request_new_messages", 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); - let api_contact_id = ::sse_decode(&mut deserializer); + let api_callback_id = ::sse_decode(&mut deserializer); +let api_contact_id = ::sse_decode(&mut deserializer); let api_version = >::sse_decode(&mut deserializer);deserializer.end(); move |context| async move { transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { - let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::should_request_new_messages(api_contact_id, &api_version).await?; Ok(output_ok) + let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::should_request_new_messages(api_callback_id, api_contact_id, &api_version).await?; Ok(output_ok) })().await) } }) } @@ -137,10 +141,11 @@ fn wire__crate__bridge__wrapper__user_discovery__flutter_user_discovery_update_v FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "flutter_user_discovery_update_verification_state_for_user", 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); - let api_contact_id = ::sse_decode(&mut deserializer); + let api_callback_id = ::sse_decode(&mut deserializer); +let api_contact_id = ::sse_decode(&mut deserializer); let api_public_key_verified_timestamp = >::sse_decode(&mut deserializer);deserializer.end(); move |context| async move { transform_result_sse::<_, flutter_rust_bridge::for_generated::anyhow::Error>((move || async move { - let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::update_verification_state_for_user(api_contact_id, api_public_key_verified_timestamp).await?; Ok(output_ok) + let output_ok = crate::bridge::wrapper::user_discovery::FlutterUserDiscovery::update_verification_state_for_user(api_callback_id, api_contact_id, api_public_key_verified_timestamp).await?; Ok(output_ok) })().await) } }) } @@ -153,7 +158,8 @@ fn wire__crate__bridge__callbacks__init_flutter_callbacks_impl( FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::(flutter_rust_bridge::for_generated::TaskInfo{ debug_name: "init_flutter_callbacks", 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); - let api_logging_get_stream_sink = decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(::sse_decode(&mut deserializer)); + let api_callback_id = ::sse_decode(&mut deserializer); +let api_logging_get_stream_sink = decode_DartFn_Inputs__Output_StreamSink_String_Sse_AnyhowException(::sse_decode(&mut deserializer)); let api_user_discovery_sign_data = decode_DartFn_Inputs_list_prim_u_8_strict_Output_opt_list_prim_u_8_strict_AnyhowException(::sse_decode(&mut deserializer)); let api_user_discovery_verify_signature = decode_DartFn_Inputs_list_prim_u_8_strict_list_prim_u_8_strict_list_prim_u_8_strict_Output_bool_AnyhowException(::sse_decode(&mut deserializer)); let api_user_discovery_verify_stored_pubkey = decode_DartFn_Inputs_i_64_list_prim_u_8_strict_Output_bool_AnyhowException(::sse_decode(&mut deserializer)); @@ -169,7 +175,7 @@ let api_user_discovery_set_contact_version = decode_DartFn_Inputs_i_64_list_prim let api_user_discovery_push_new_user_relation = decode_DartFn_Inputs_i_64_announced_user_opt_box_autoadd_i_64_Output_bool_AnyhowException(::sse_decode(&mut deserializer)); let api_user_discovery_get_contact_promotion = decode_DartFn_Inputs_i_64_Output_opt_list_prim_u_8_strict_AnyhowException(::sse_decode(&mut deserializer));deserializer.end(); move |context| { transform_result_sse::<_, ()>((move || { - let output_ok = Result::<_,()>::Ok({ crate::bridge::callbacks::init_flutter_callbacks(api_logging_get_stream_sink, api_user_discovery_sign_data, api_user_discovery_verify_signature, api_user_discovery_verify_stored_pubkey, api_user_discovery_set_shares, api_user_discovery_get_share_for_contact, api_user_discovery_push_own_promotion_and_clear_old_version, api_user_discovery_get_own_promotions_after_version, api_user_discovery_store_other_promotion, api_user_discovery_get_other_promotions_by_public_id, api_user_discovery_get_announced_user_by_public_id, api_user_discovery_get_contact_version, api_user_discovery_set_contact_version, api_user_discovery_push_new_user_relation, api_user_discovery_get_contact_promotion); })?; Ok(output_ok) + let output_ok = Result::<_,()>::Ok({ crate::bridge::callbacks::init_flutter_callbacks(api_callback_id, api_logging_get_stream_sink, api_user_discovery_sign_data, api_user_discovery_verify_signature, api_user_discovery_verify_stored_pubkey, api_user_discovery_set_shares, api_user_discovery_get_share_for_contact, api_user_discovery_push_own_promotion_and_clear_old_version, api_user_discovery_get_own_promotions_after_version, api_user_discovery_store_other_promotion, api_user_discovery_get_other_promotions_by_public_id, api_user_discovery_get_announced_user_by_public_id, api_user_discovery_get_contact_version, api_user_discovery_set_contact_version, api_user_discovery_push_new_user_relation, api_user_discovery_get_contact_promotion); })?; Ok(output_ok) })()) } }) }