diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart index d0729a4766..8b8bf681db 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/board/presentation/board_page.dart @@ -382,7 +382,8 @@ Widget? _buildHeaderIcon(GroupData customData) { case FieldType.Checkbox: final group = customData.asCheckboxGroup()!; if (group.isCheck) { - widget = const FlowySvg(FlowySvgs.check_filled_s); + widget = + const FlowySvg(FlowySvgs.check_filled_s, blendMode: BlendMode.dst,); } else { widget = const FlowySvg(FlowySvgs.uncheck_s); } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/checkbox_card_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/checkbox_card_cell.dart index ffca3048b5..7e5f464610 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/checkbox_card_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/card/cells/checkbox_card_cell.dart @@ -41,7 +41,10 @@ class _CheckboxCardCellState extends State { previous.isSelected != current.isSelected, builder: (context, state) { final icon = state.isSelected - ? const FlowySvg(FlowySvgs.check_filled_s) + ? const FlowySvg( + FlowySvgs.check_filled_s, + blendMode: BlendMode.dst, + ) : const FlowySvg(FlowySvgs.uncheck_s); return Align( alignment: Alignment.centerLeft, diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checkbox_cell/checkbox_cell.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checkbox_cell/checkbox_cell.dart index 7df3979198..e064b39b9b 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checkbox_cell/checkbox_cell.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checkbox_cell/checkbox_cell.dart @@ -89,7 +89,10 @@ class CheckboxCellCheck extends StatelessWidget { @override Widget build(BuildContext context) { - return const FlowySvg(FlowySvgs.check_filled_s); + return const FlowySvg( + FlowySvgs.check_filled_s, + blendMode: BlendMode.dst, + ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart index 0a57d1b185..65d59fc5ef 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart @@ -109,7 +109,10 @@ class _ChecklistOptionCellState extends State<_ChecklistOptionCell> { @override Widget build(BuildContext context) { final icon = widget.option.isSelected - ? const FlowySvg(FlowySvgs.check_filled_s) + ? const FlowySvg( + FlowySvgs.check_filled_s, + blendMode: BlendMode.dst, + ) : const FlowySvg(FlowySvgs.uncheck_s); return _wrapPopover( SizedBox( diff --git a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart index df71768dcb..091f9bf6c3 100644 --- a/frontend/appflowy_flutter/lib/startup/deps_resolver.dart +++ b/frontend/appflowy_flutter/lib/startup/deps_resolver.dart @@ -21,7 +21,6 @@ import 'package:appflowy/workspace/application/user/prelude.dart'; import 'package:appflowy/workspace/application/workspace/prelude.dart'; import 'package:appflowy/workspace/application/edit_panel/edit_panel_bloc.dart'; import 'package:appflowy/workspace/application/view/prelude.dart'; -import 'package:appflowy/workspace/application/menu/prelude.dart'; import 'package:appflowy/workspace/application/settings/prelude.dart'; import 'package:appflowy/user/application/prelude.dart'; import 'package:appflowy/user/presentation/router.dart'; @@ -116,7 +115,6 @@ void _resolveHomeDeps(GetIt getIt) { getIt.registerFactoryParam( (user, _) => WelcomeBloc( userService: UserBackendService(userId: user.id), - userWorkspaceListener: UserWorkspaceListener(userProfile: user), ), ); @@ -141,10 +139,6 @@ void _resolveFolderDeps(GetIt getIt) { ), ); - getIt.registerFactoryParam( - (user, _) => MenuUserBloc(user), - ); - //Settings getIt.registerFactoryParam( (user, _) => SettingsDialogBloc(user), diff --git a/frontend/appflowy_flutter/lib/startup/tasks/supabase_task.dart b/frontend/appflowy_flutter/lib/startup/tasks/supabase_task.dart index 26d6014faf..c77c516d74 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/supabase_task.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/supabase_task.dart @@ -40,6 +40,11 @@ class InitSupabaseTask extends LaunchTask { debug: kDebugMode, localStorage: const SupabaseLocalStorage(), ); + + if (realtimeService != null) { + await realtimeService?.dispose(); + realtimeService = null; + } realtimeService = SupbaseRealtimeService(supabase: initializedSupabase); supabase = initializedSupabase; diff --git a/frontend/appflowy_flutter/lib/user/application/auth/supabase_auth_service.dart b/frontend/appflowy_flutter/lib/user/application/auth/supabase_auth_service.dart index 3335d06341..16e5f6f0aa 100644 --- a/frontend/appflowy_flutter/lib/user/application/auth/supabase_auth_service.dart +++ b/frontend/appflowy_flutter/lib/user/application/auth/supabase_auth_service.dart @@ -108,7 +108,9 @@ class SupabaseAuthService implements AuthService { return _appFlowyAuthService.signUpWithOAuth(platform: platform); } // Before signing in, sign out any existing users. Otherwise, the callback will be triggered even if the user doesn't click the 'Sign In' button on the website - await _auth.signOut(); + if (_auth.currentUser != null) { + await _auth.signOut(); + } final provider = platform.toProvider(); final completer = supabaseLoginCompleter( diff --git a/frontend/appflowy_flutter/lib/user/application/supabase_realtime.dart b/frontend/appflowy_flutter/lib/user/application/supabase_realtime.dart index 09f79fe97a..266bd64c08 100644 --- a/frontend/appflowy_flutter/lib/user/application/supabase_realtime.dart +++ b/frontend/appflowy_flutter/lib/user/application/supabase_realtime.dart @@ -1,12 +1,16 @@ import 'dart:async'; import 'dart:convert'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/user/application/user_auth_listener.dart'; import 'package:appflowy/user/application/user_service.dart'; import 'package:appflowy_backend/dispatch/dispatch.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; +import 'auth/auth_service.dart'; + /// A service to manage realtime interactions with Supabase. /// /// `SupbaseRealtimeService` handles subscribing to table changes in Supabase @@ -15,53 +19,73 @@ import 'package:supabase_flutter/supabase_flutter.dart'; /// accordingly. class SupbaseRealtimeService { final Supabase supabase; + final _authStateListener = UserAuthStateListener(); + + bool isLoggingOut = false; + RealtimeChannel? channel; StreamSubscription? authStateSubscription; SupbaseRealtimeService({required this.supabase}) { _subscribeAuthState(); + _subscribeTablesChanges(); + + _authStateListener.start( + didSignIn: () { + _subscribeTablesChanges(); + isLoggingOut = false; + }, + onForceLogout: (message) async { + await getIt().signOut(); + channel?.unsubscribe(); + channel = null; + if (!isLoggingOut) { + await runAppFlowy(); + } + }, + ); } void _subscribeAuthState() { final auth = Supabase.instance.client.auth; authStateSubscription = auth.onAuthStateChange.listen((state) async { - switch (state.event) { - case AuthChangeEvent.signedIn: - _subscribeTablesChanges(); - break; - case AuthChangeEvent.signedOut: - channel?.unsubscribe(); - break; - case AuthChangeEvent.tokenRefreshed: - _subscribeTablesChanges(); - break; - default: - break; - } + Log.info("Supabase auth state change: ${state.event}"); }); } Future _subscribeTablesChanges() async { final result = await UserBackendService.getCurrentUserProfile(); result.fold((l) => null, (userProfile) { - Log.info("Start listening to table changes"); + Log.info("Start listening supabase table changes"); // https://supabase.com/docs/guides/realtime/postgres-changes - final filters = [ + final List filters = [ "document", "folder", "database", "database_row", "w_database", - ].map( - (name) => ChannelFilter( - event: 'INSERT', + ] + .map( + (name) => ChannelFilter( + event: 'INSERT', + schema: 'public', + table: "af_collab_update_$name", + filter: 'uid=eq.${userProfile.id}', + ), + ) + .toList(); + + filters.add( + ChannelFilter( + event: 'UPDATE', schema: 'public', - table: "af_collab_update_$name", + table: "af_user", filter: 'uid=eq.${userProfile.id}', ), ); const ops = RealtimeChannelConfig(ack: true); + channel?.unsubscribe(); channel = supabase.client.channel("table-db-changes", opts: ops); for (final filter in filters) { channel?.on( @@ -88,4 +112,10 @@ class SupbaseRealtimeService { ); }); } + + Future dispose() async { + await _authStateListener.stop(); + await authStateSubscription?.cancel(); + await channel?.unsubscribe(); + } } diff --git a/frontend/appflowy_flutter/lib/user/application/user_auth_listener.dart b/frontend/appflowy_flutter/lib/user/application/user_auth_listener.dart new file mode 100644 index 0000000000..20d282849b --- /dev/null +++ b/frontend/appflowy_flutter/lib/user/application/user_auth_listener.dart @@ -0,0 +1,68 @@ +import 'dart:async'; +import 'package:appflowy/core/notification/user_notification.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/auth.pb.dart'; +import 'package:dartz/dartz.dart'; +import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; +import 'dart:typed_data'; +import 'package:appflowy_backend/protobuf/flowy-notification/protobuf.dart'; +import 'package:appflowy_backend/protobuf/flowy-user/notification.pb.dart' + as user; +import 'package:appflowy_backend/rust_stream.dart'; + +class UserAuthStateListener { + void Function(String)? _onForceLogout; + void Function()? _didSignIn; + StreamSubscription? _subscription; + UserNotificationParser? _userParser; + + void start({ + void Function(String)? onForceLogout, + void Function()? didSignIn, + }) { + _onForceLogout = onForceLogout; + _didSignIn = didSignIn; + + _userParser = UserNotificationParser( + id: "auth_state_change_notification", + callback: _userNotificationCallback, + ); + _subscription = RustStreamReceiver.listen((observable) { + _userParser?.parse(observable); + }); + } + + Future stop() async { + _userParser = null; + await _subscription?.cancel(); + _onForceLogout = null; + } + + void _userNotificationCallback( + user.UserNotification ty, + Either result, + ) { + switch (ty) { + case user.UserNotification.UserAuthStateChanged: + result.fold( + (payload) { + final pb = AuthStateChangedPB.fromBuffer(payload); + switch (pb.state) { + case AuthStatePB.AuthStateSignIn: + _didSignIn?.call(); + break; + case AuthStatePB.AuthStateForceSignOut: + _onForceLogout?.call(""); + break; + default: + break; + } + }, + (r) => Log.error(r), + ); + break; + default: + break; + } + } +} diff --git a/frontend/appflowy_flutter/lib/user/application/user_listener.dart b/frontend/appflowy_flutter/lib/user/application/user_listener.dart index a06e398bc5..b744d91808 100644 --- a/frontend/appflowy_flutter/lib/user/application/user_listener.dart +++ b/frontend/appflowy_flutter/lib/user/application/user_listener.dart @@ -18,7 +18,6 @@ typedef AuthNotifyValue = Either; class UserListener { StreamSubscription? _subscription; - PublishNotifier? _authNotifier = PublishNotifier(); PublishNotifier? _profileNotifier = PublishNotifier(); UserNotificationParser? _userParser; @@ -28,17 +27,12 @@ class UserListener { }) : _userProfile = userProfile; void start({ - void Function(AuthNotifyValue)? onAuthChanged, void Function(UserProfileNotifyValue)? onProfileUpdated, }) { if (onProfileUpdated != null) { _profileNotifier?.addPublishListener(onProfileUpdated); } - if (onAuthChanged != null) { - _authNotifier?.addPublishListener(onAuthChanged); - } - _userParser = UserNotificationParser( id: _userProfile.id.toString(), callback: _userNotificationCallback, @@ -53,9 +47,6 @@ class UserListener { await _subscription?.cancel(); _profileNotifier?.dispose(); _profileNotifier = null; - - _authNotifier?.dispose(); - _authNotifier = null; } void _userNotificationCallback( @@ -76,13 +67,9 @@ class UserListener { } } -typedef WorkspaceListNotifyValue = Either, FlowyError>; typedef WorkspaceSettingNotifyValue = Either; class UserWorkspaceListener { - PublishNotifier? _authNotifier = PublishNotifier(); - PublishNotifier? _workspacesChangedNotifier = - PublishNotifier(); PublishNotifier? _settingChangedNotifier = PublishNotifier(); @@ -93,18 +80,8 @@ class UserWorkspaceListener { }); void start({ - void Function(AuthNotifyValue)? onAuthChanged, - void Function(WorkspaceListNotifyValue)? onWorkspacesUpdated, void Function(WorkspaceSettingNotifyValue)? onSettingUpdated, }) { - if (onAuthChanged != null) { - _authNotifier?.addPublishListener(onAuthChanged); - } - - if (onWorkspacesUpdated != null) { - _workspacesChangedNotifier?.addPublishListener(onWorkspacesUpdated); - } - if (onSettingUpdated != null) { _settingChangedNotifier?.addPublishListener(onSettingUpdated); } @@ -122,7 +99,6 @@ class UserWorkspaceListener { Either result, ) { switch (ty) { - case FolderNotification.DidCreateWorkspace: case FolderNotification.DidUpdateWorkspaceSetting: result.fold( (payload) => _settingChangedNotifier?.value = @@ -137,13 +113,8 @@ class UserWorkspaceListener { Future stop() async { await _listener?.stop(); - _workspacesChangedNotifier?.dispose(); - _workspacesChangedNotifier = null; _settingChangedNotifier?.dispose(); _settingChangedNotifier = null; - - _authNotifier?.dispose(); - _authNotifier = null; } } diff --git a/frontend/appflowy_flutter/lib/workspace/application/home/home_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/home/home_bloc.dart index 219910403b..cbc8f56d87 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/home/home_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/home/home_bloc.dart @@ -1,24 +1,21 @@ import 'package:appflowy/user/application/user_listener.dart'; import 'package:flowy_infra/time/duration.dart'; import 'package:appflowy_backend/log.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/code.pb.dart'; -import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart' show WorkspaceSettingPB; import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:dartz/dartz.dart'; part 'home_bloc.freezed.dart'; class HomeBloc extends Bloc { - final UserWorkspaceListener _listener; + final UserWorkspaceListener _workspaceListener; HomeBloc( UserProfilePB user, WorkspaceSettingPB workspaceSetting, - ) : _listener = UserWorkspaceListener(userProfile: user), + ) : _workspaceListener = UserWorkspaceListener(userProfile: user), super(HomeState.initial(workspaceSetting)) { on( (event, emit) async { @@ -30,8 +27,7 @@ class HomeBloc extends Bloc { } }); - _listener.start( - onAuthChanged: (result) => _authDidChanged(result), + _workspaceListener.start( onSettingUpdated: (result) { result.fold( (setting) => @@ -56,9 +52,6 @@ class HomeBloc extends Bloc { ), ); }, - unauthorized: (_Unauthorized value) { - emit(state.copyWith(unauthorized: true)); - }, ); }, ); @@ -66,17 +59,9 @@ class HomeBloc extends Bloc { @override Future close() async { - await _listener.stop(); + await _workspaceListener.stop(); return super.close(); } - - void _authDidChanged(Either errorOrNothing) { - errorOrNothing.fold((_) {}, (error) { - if (error.code == ErrorCode.UserUnauthorized.value) { - add(HomeEvent.unauthorized(error.msg)); - } - }); - } } enum MenuResizeType { @@ -102,7 +87,6 @@ class HomeEvent with _$HomeEvent { const factory HomeEvent.didReceiveWorkspaceSetting( WorkspaceSettingPB setting, ) = _DidReceiveWorkspaceSetting; - const factory HomeEvent.unauthorized(String msg) = _Unauthorized; } @freezed @@ -111,13 +95,11 @@ class HomeState with _$HomeState { required bool isLoading, required WorkspaceSettingPB workspaceSetting, ViewPB? latestView, - required bool unauthorized, }) = _HomeState; factory HomeState.initial(WorkspaceSettingPB workspaceSetting) => HomeState( isLoading: false, workspaceSetting: workspaceSetting, latestView: null, - unauthorized: false, ); } diff --git a/frontend/appflowy_flutter/lib/workspace/application/menu/menu_user_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/menu/menu_user_bloc.dart index 88ad170e91..91ff439f27 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/menu/menu_user_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/menu/menu_user_bloc.dart @@ -26,9 +26,6 @@ class MenuUserBloc extends Bloc { await event.when( initial: () async { _userListener.start(onProfileUpdated: _profileUpdated); - _userWorkspaceListener.start( - onWorkspacesUpdated: _workspaceListUpdated, - ); await _initUser(); }, fetchWorkspaces: () async { @@ -62,18 +59,16 @@ class MenuUserBloc extends Bloc { } void _profileUpdated(Either userProfileOrFailed) { + if (isClosed) { + return; + } userProfileOrFailed.fold( - (newUserProfile) => - add(MenuUserEvent.didReceiveUserProfile(newUserProfile)), + (newUserProfile) => add( + MenuUserEvent.didReceiveUserProfile(newUserProfile), + ), (err) => Log.error(err), ); } - - void _workspaceListUpdated( - Either, FlowyError> workspacesOrFailed, - ) { - // Do nothing by now - } } @freezed diff --git a/frontend/appflowy_flutter/lib/workspace/application/user/settings_user_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/user/settings_user_bloc.dart index 6ee570851b..14cd44ebec 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/user/settings_user_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/user/settings_user_bloc.dart @@ -21,9 +21,8 @@ class SettingsUserViewBloc extends Bloc { on((event, emit) async { await event.when( initial: () async { + _loadUserProfile(); _userListener.start(onProfileUpdated: _profileUpdated); - await _initUser(); - _loadHistoricalUsers(); }, didReceiveUserProfile: (UserProfilePB newUserProfile) { emit(state.copyWith(userProfile: newUserProfile)); @@ -68,26 +67,25 @@ class SettingsUserViewBloc extends Bloc { super.close(); } - Future _initUser() async { - final result = await _userService.initUser(); - result.fold((l) => null, (error) => Log.error(error)); - } + void _loadUserProfile() { + UserBackendService.getCurrentUserProfile().then((result) { + if (isClosed) { + return; + } - Future _loadHistoricalUsers() async { - final result = await UserBackendService.loadHistoricalUsers(); - result.fold( - (historicalUsers) { - add(SettingsUserEvent.didLoadHistoricalUsers(historicalUsers)); - }, - (error) => Log.error(error), - ); + result.fold( + (err) => Log.error(err), + (userProfile) => add( + SettingsUserEvent.didReceiveUserProfile(userProfile), + ), + ); + }); } void _profileUpdated(Either userProfileOrFailed) { userProfileOrFailed.fold( (newUserProfile) { add(SettingsUserEvent.didReceiveUserProfile(newUserProfile)); - _loadHistoricalUsers(); }, (err) => Log.error(err), ); diff --git a/frontend/appflowy_flutter/lib/workspace/application/workspace/welcome_bloc.dart b/frontend/appflowy_flutter/lib/workspace/application/workspace/welcome_bloc.dart index 88c273c45d..79372cee71 100644 --- a/frontend/appflowy_flutter/lib/workspace/application/workspace/welcome_bloc.dart +++ b/frontend/appflowy_flutter/lib/workspace/application/workspace/welcome_bloc.dart @@ -1,4 +1,3 @@ -import 'package:appflowy/user/application/user_listener.dart'; import 'package:appflowy/user/application/user_service.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart'; @@ -11,18 +10,11 @@ part 'welcome_bloc.freezed.dart'; class WelcomeBloc extends Bloc { final UserBackendService userService; - final UserWorkspaceListener userWorkspaceListener; - WelcomeBloc({required this.userService, required this.userWorkspaceListener}) - : super(WelcomeState.initial()) { + WelcomeBloc({required this.userService}) : super(WelcomeState.initial()) { on( (event, emit) async { await event.map( initial: (e) async { - userWorkspaceListener.start( - onWorkspacesUpdated: (result) => - add(WelcomeEvent.workspacesReveived(result)), - ); - // await _fetchWorkspaces(emit); }, openWorkspace: (e) async { @@ -47,12 +39,6 @@ class WelcomeBloc extends Bloc { ); } - @override - Future close() async { - await userWorkspaceListener.stop(); - super.close(); - } - Future _fetchWorkspaces(Emitter emit) async { final workspacesOrFailed = await userService.getWorkspaces(); emit( diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/home_screen.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/home_screen.dart index 6e02a28def..42ae0eb98d 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/home_screen.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/home_screen.dart @@ -61,16 +61,6 @@ class _HomeScreenState extends State { child: Scaffold( body: MultiBlocListener( listeners: [ - BlocListener( - listenWhen: (p, c) => p.unauthorized != c.unauthorized, - listener: (context, state) { - if (state.unauthorized) { - Log.error( - "Push to login screen when user token was invalid", - ); - } - }, - ), BlocListener( listenWhen: (p, c) => p.latestView != c.latestView, listener: (context, state) { diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_user.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_user.dart index 810a458a57..f9c38ecbd2 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_user.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/sidebar/sidebar_user.dart @@ -26,7 +26,7 @@ class SidebarUser extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( - create: (context) => getIt(param1: user) + create: (context) => MenuUserBloc(user) ..add( const MenuUserEvent.initial(), ), @@ -34,25 +34,23 @@ class SidebarUser extends StatelessWidget { builder: (context, state) => Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ - _buildAvatar(context), + _buildAvatar(context, state), const HSpace(10), Expanded( - child: _buildUserName(context), + child: _buildUserName(context, state), ), - _buildSettingsButton(context), + _buildSettingsButton(context, state), ], ), ), ); } - Widget _buildAvatar(BuildContext context) { - String iconUrl = context.read().state.userProfile.iconUrl; + Widget _buildAvatar(BuildContext context, MenuUserState state) { + String iconUrl = state.userProfile.iconUrl; if (iconUrl.isEmpty) { iconUrl = defaultUserAvatar; - final String name = _userName( - context.read().state.userProfile, - ); + final String name = _userName(state.userProfile); final Color color = ColorGenerator().generateColorFromString(name); const initialsCount = 2; // Taking the first letters of the name components and limiting to 2 elements @@ -92,10 +90,8 @@ class SidebarUser extends StatelessWidget { ); } - Widget _buildUserName(BuildContext context) { - final String name = _userName( - context.read().state.userProfile, - ); + Widget _buildUserName(BuildContext context, MenuUserState state) { + final String name = _userName(state.userProfile); return FlowyText.medium( name, overflow: TextOverflow.ellipsis, @@ -103,8 +99,8 @@ class SidebarUser extends StatelessWidget { ); } - Widget _buildSettingsButton(BuildContext context) { - final userProfile = context.read().state.userProfile; + Widget _buildSettingsButton(BuildContext context, MenuUserState state) { + final userProfile = state.userProfile; return Tooltip( message: LocaleKeys.settings_menu_open.tr(), child: IconButton( diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.lock b/frontend/appflowy_tauri/src-tauri/Cargo.lock index 8530acc68f..578a148921 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.lock +++ b/frontend/appflowy_tauri/src-tauri/Cargo.lock @@ -17,6 +17,41 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "aes-gcm" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.7.6" @@ -98,14 +133,14 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "appflowy-integrate" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ba963f#ba963fa299d294e5b2cafd940b9eaa8520280b7b" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079" dependencies = [ "anyhow", "collab", @@ -218,324 +253,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "aws-config" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcdcf0d683fe9c23d32cf5b53c9918ea0a500375a9fb20109802552658e576c9" -dependencies = [ - "aws-credential-types", - "aws-http", - "aws-sdk-sso", - "aws-sdk-sts", - "aws-smithy-async", - "aws-smithy-client", - "aws-smithy-http", - "aws-smithy-http-tower", - "aws-smithy-json", - "aws-smithy-types", - "aws-types", - "bytes", - "fastrand", - "hex", - "http", - "hyper", - "ring", - "time 0.3.22", - "tokio", - "tower", - "tracing", - "zeroize", -] - -[[package]] -name = "aws-credential-types" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcdb2f7acbc076ff5ad05e7864bdb191ca70a6fd07668dc3a1a8bcd051de5ae" -dependencies = [ - "aws-smithy-async", - "aws-smithy-types", - "fastrand", - "tokio", - "tracing", - "zeroize", -] - -[[package]] -name = "aws-endpoint" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cce1c41a6cfaa726adee9ebb9a56fcd2bbfd8be49fd8a04c5e20fd968330b04" -dependencies = [ - "aws-smithy-http", - "aws-smithy-types", - "aws-types", - "http", - "regex", - "tracing", -] - -[[package]] -name = "aws-http" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aadbc44e7a8f3e71c8b374e03ecd972869eb91dd2bc89ed018954a52ba84bc44" -dependencies = [ - "aws-credential-types", - "aws-smithy-http", - "aws-smithy-types", - "aws-types", - "bytes", - "http", - "http-body", - "lazy_static", - "percent-encoding", - "pin-project-lite", - "tracing", -] - -[[package]] -name = "aws-sdk-dynamodb" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67fb64867fe098cffee7e34352b01bbfa2beb3aa1b2ff0e0a7bf9ff293557852" -dependencies = [ - "aws-credential-types", - "aws-endpoint", - "aws-http", - "aws-sig-auth", - "aws-smithy-async", - "aws-smithy-client", - "aws-smithy-http", - "aws-smithy-http-tower", - "aws-smithy-json", - "aws-smithy-types", - "aws-types", - "bytes", - "fastrand", - "http", - "regex", - "tokio-stream", - "tower", - "tracing", -] - -[[package]] -name = "aws-sdk-sso" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8b812340d86d4a766b2ca73f740dfd47a97c2dff0c06c8517a16d88241957e4" -dependencies = [ - "aws-credential-types", - "aws-endpoint", - "aws-http", - "aws-sig-auth", - "aws-smithy-async", - "aws-smithy-client", - "aws-smithy-http", - "aws-smithy-http-tower", - "aws-smithy-json", - "aws-smithy-types", - "aws-types", - "bytes", - "http", - "regex", - "tokio-stream", - "tower", - "tracing", -] - -[[package]] -name = "aws-sdk-sts" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "265fac131fbfc188e5c3d96652ea90ecc676a934e3174eaaee523c6cec040b3b" -dependencies = [ - "aws-credential-types", - "aws-endpoint", - "aws-http", - "aws-sig-auth", - "aws-smithy-async", - "aws-smithy-client", - "aws-smithy-http", - "aws-smithy-http-tower", - "aws-smithy-json", - "aws-smithy-query", - "aws-smithy-types", - "aws-smithy-xml", - "aws-types", - "bytes", - "http", - "regex", - "tower", - "tracing", -] - -[[package]] -name = "aws-sig-auth" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b94acb10af0c879ecd5c7bdf51cda6679a0a4f4643ce630905a77673bfa3c61" -dependencies = [ - "aws-credential-types", - "aws-sigv4", - "aws-smithy-http", - "aws-types", - "http", - "tracing", -] - -[[package]] -name = "aws-sigv4" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d2ce6f507be68e968a33485ced670111d1cbad161ddbbab1e313c03d37d8f4c" -dependencies = [ - "aws-smithy-http", - "form_urlencoded", - "hex", - "hmac", - "http", - "once_cell", - "percent-encoding", - "regex", - "sha2", - "time 0.3.22", - "tracing", -] - -[[package]] -name = "aws-smithy-async" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13bda3996044c202d75b91afeb11a9afae9db9a721c6a7a427410018e286b880" -dependencies = [ - "futures-util", - "pin-project-lite", - "tokio", - "tokio-stream", -] - -[[package]] -name = "aws-smithy-client" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a86aa6e21e86c4252ad6a0e3e74da9617295d8d6e374d552be7d3059c41cedd" -dependencies = [ - "aws-smithy-async", - "aws-smithy-http", - "aws-smithy-http-tower", - "aws-smithy-types", - "bytes", - "fastrand", - "http", - "http-body", - "hyper", - "hyper-rustls 0.23.2", - "lazy_static", - "pin-project-lite", - "rustls 0.20.8", - "tokio", - "tower", - "tracing", -] - -[[package]] -name = "aws-smithy-http" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b3b693869133551f135e1f2c77cb0b8277d9e3e17feaf2213f735857c4f0d28" -dependencies = [ - "aws-smithy-types", - "bytes", - "bytes-utils", - "futures-core", - "http", - "http-body", - "hyper", - "once_cell", - "percent-encoding", - "pin-project-lite", - "pin-utils", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "aws-smithy-http-tower" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae4f6c5798a247fac98a867698197d9ac22643596dc3777f0c76b91917616b9" -dependencies = [ - "aws-smithy-http", - "aws-smithy-types", - "bytes", - "http", - "http-body", - "pin-project-lite", - "tower", - "tracing", -] - -[[package]] -name = "aws-smithy-json" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f9f42fbfa96d095194a632fbac19f60077748eba536eb0b9fecc28659807f8" -dependencies = [ - "aws-smithy-types", -] - -[[package]] -name = "aws-smithy-query" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98819eb0b04020a1c791903533b638534ae6c12e2aceda3e6e6fba015608d51d" -dependencies = [ - "aws-smithy-types", - "urlencoding", -] - -[[package]] -name = "aws-smithy-types" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16a3d0bf4f324f4ef9793b86a1701d9700fbcdbd12a846da45eed104c634c6e8" -dependencies = [ - "base64-simd", - "itoa 1.0.6", - "num-integer", - "ryu", - "time 0.3.22", -] - -[[package]] -name = "aws-smithy-xml" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1b9d12875731bd07e767be7baad95700c3137b56730ec9ddeedb52a5e5ca63b" -dependencies = [ - "xmlparser", -] - -[[package]] -name = "aws-types" -version = "0.55.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd209616cc8d7bfb82f87811a5c655dc97537f592689b18743bddf5dc5c4829" -dependencies = [ - "aws-credential-types", - "aws-smithy-async", - "aws-smithy-client", - "aws-smithy-http", - "aws-smithy-types", - "http", - "rustc_version", - "tracing", -] - [[package]] name = "backtrace" version = "0.3.67" @@ -563,16 +280,6 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" -[[package]] -name = "base64-simd" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" -dependencies = [ - "outref", - "vsimd", -] - [[package]] name = "bincode" version = "1.3.3" @@ -776,16 +483,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bytes-utils" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e47d3a8076e283f3acd27400535992edb3ba4b5bb72f8891ad8fbe7932a7d4b9" -dependencies = [ - "bytes", - "either", -] - [[package]] name = "bzip2-sys" version = "0.1.11+1.0.8" @@ -951,6 +648,16 @@ dependencies = [ "phf_codegen 0.11.2", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + [[package]] name = "clang-sys" version = "1.6.1" @@ -1021,7 +728,7 @@ dependencies = [ [[package]] name = "collab" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ba963f#ba963fa299d294e5b2cafd940b9eaa8520280b7b" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079" dependencies = [ "anyhow", "bytes", @@ -1039,7 +746,7 @@ dependencies = [ [[package]] name = "collab-client-ws" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ba963f#ba963fa299d294e5b2cafd940b9eaa8520280b7b" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079" dependencies = [ "bytes", "collab-sync", @@ -1057,7 +764,7 @@ dependencies = [ [[package]] name = "collab-database" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ba963f#ba963fa299d294e5b2cafd940b9eaa8520280b7b" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079" dependencies = [ "anyhow", "async-trait", @@ -1081,10 +788,18 @@ dependencies = [ "uuid", ] +[[package]] +name = "collab-define" +version = "0.1.0" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079" +dependencies = [ + "uuid", +] + [[package]] name = "collab-derive" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ba963f#ba963fa299d294e5b2cafd940b9eaa8520280b7b" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079" dependencies = [ "proc-macro2", "quote", @@ -1096,7 +811,7 @@ dependencies = [ [[package]] name = "collab-document" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ba963f#ba963fa299d294e5b2cafd940b9eaa8520280b7b" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079" dependencies = [ "anyhow", "collab", @@ -1115,7 +830,7 @@ dependencies = [ [[package]] name = "collab-folder" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ba963f#ba963fa299d294e5b2cafd940b9eaa8520280b7b" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079" dependencies = [ "anyhow", "chrono", @@ -1135,7 +850,7 @@ dependencies = [ [[package]] name = "collab-persistence" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ba963f#ba963fa299d294e5b2cafd940b9eaa8520280b7b" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079" dependencies = [ "bincode", "chrono", @@ -1155,15 +870,13 @@ dependencies = [ [[package]] name = "collab-plugins" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ba963f#ba963fa299d294e5b2cafd940b9eaa8520280b7b" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079" dependencies = [ "anyhow", "async-trait", - "aws-config", - "aws-credential-types", - "aws-sdk-dynamodb", "collab", "collab-client-ws", + "collab-define", "collab-persistence", "collab-sync", "futures-util", @@ -1178,6 +891,7 @@ dependencies = [ "tokio-retry", "tokio-stream", "tracing", + "uuid", "y-sync", "yrs", ] @@ -1185,7 +899,7 @@ dependencies = [ [[package]] name = "collab-sync" version = "0.1.0" -source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=ba963f#ba963fa299d294e5b2cafd940b9eaa8520280b7b" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079" dependencies = [ "bytes", "collab", @@ -1204,6 +918,21 @@ dependencies = [ "yrs", ] +[[package]] +name = "collab-user" +version = "0.1.0" +source = "git+https://github.com/AppFlowy-IO/AppFlowy-Collab?rev=cff1b9#cff1b99f4ed51f65dab73492eac4da8e7907f079" +dependencies = [ + "anyhow", + "collab", + "parking_lot 0.12.1", + "serde", + "serde_json", + "tokio", + "tokio-stream", + "tracing", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -1361,6 +1090,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] @@ -1422,6 +1152,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + [[package]] name = "darling" version = "0.20.1" @@ -1951,6 +1690,19 @@ dependencies = [ "uuid", ] +[[package]] +name = "flowy-encrypt" +version = "0.1.0" +dependencies = [ + "aes-gcm", + "anyhow", + "base64 0.21.2", + "hmac", + "pbkdf2", + "rand 0.8.5", + "sha2", +] + [[package]] name = "flowy-error" version = "0.1.0" @@ -2050,6 +1802,7 @@ dependencies = [ "config", "flowy-database-deps", "flowy-document-deps", + "flowy-encrypt", "flowy-error", "flowy-folder-deps", "flowy-server-config", @@ -2122,11 +1875,13 @@ dependencies = [ "collab", "collab-document", "collab-folder", + "collab-user", "diesel", "diesel_derives", "fancy-regex 0.11.0", "flowy-codegen", "flowy-derive", + "flowy-encrypt", "flowy-error", "flowy-notification", "flowy-server-config", @@ -2156,11 +1911,13 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", + "collab-define", "flowy-error", "lib-infra", "serde", "serde_json", "serde_repr", + "tokio", "uuid", ] @@ -2461,6 +2218,16 @@ dependencies = [ "wasi 0.11.0+wasi-snapshot-preview1", ] +[[package]] +name = "ghash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "gimli" version = "0.27.3" @@ -2813,21 +2580,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-rustls" -version = "0.23.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" -dependencies = [ - "http", - "hyper", - "log", - "rustls 0.20.8", - "rustls-native-certs", - "tokio", - "tokio-rustls 0.23.4", -] - [[package]] name = "hyper-rustls" version = "0.24.0" @@ -2836,9 +2588,9 @@ checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" dependencies = [ "http", "hyper", - "rustls 0.21.2", + "rustls", "tokio", - "tokio-rustls 0.24.1", + "tokio-rustls", ] [[package]] @@ -2963,6 +2715,15 @@ dependencies = [ "cfb", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -3655,6 +3416,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + [[package]] name = "open" version = "3.2.0" @@ -3719,12 +3486,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "outref" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" - [[package]] name = "overload" version = "0.1.1" @@ -3819,6 +3580,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "peeking_take_while" version = "0.1.2" @@ -4087,6 +3858,18 @@ dependencies = [ "miniz_oxide 0.7.1", ] +[[package]] +name = "polyval" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52cff9d1d4dee5fe6d03729099f4a310a41179e0a10dbf542039873f2e826fb" +dependencies = [ + "cfg-if", + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "postgres-protocol" version = "0.6.5" @@ -4547,7 +4330,7 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-rustls 0.24.0", + "hyper-rustls", "hyper-tls", "ipnet", "js-sys", @@ -4557,14 +4340,14 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls 0.21.2", + "rustls", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", "tokio-native-tls", - "tokio-rustls 0.24.1", + "tokio-rustls", "tower-service", "url", "wasm-bindgen", @@ -4708,18 +4491,6 @@ dependencies = [ "windows-sys 0.48.0", ] -[[package]] -name = "rustls" -version = "0.20.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" -dependencies = [ - "log", - "ring", - "sct", - "webpki", -] - [[package]] name = "rustls" version = "0.21.2" @@ -4732,18 +4503,6 @@ dependencies = [ "sct", ] -[[package]] -name = "rustls-native-certs" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" -dependencies = [ - "openssl-probe", - "rustls-pemfile", - "schannel", - "security-framework", -] - [[package]] name = "rustls-pemfile" version = "1.0.2" @@ -5849,24 +5608,13 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls 0.20.8", - "tokio", - "webpki", -] - [[package]] name = "tokio-rustls" version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" dependencies = [ - "rustls 0.21.2", + "rustls", "tokio", ] @@ -5951,28 +5699,6 @@ dependencies = [ "winnow", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - [[package]] name = "tower-service" version = "0.3.2" @@ -6240,6 +5966,16 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.7.1" @@ -6258,12 +5994,6 @@ dependencies = [ "serde", ] -[[package]] -name = "urlencoding" -version = "2.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" - [[package]] name = "utf-8" version = "0.7.6" @@ -6325,12 +6055,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" -[[package]] -name = "vsimd" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" - [[package]] name = "vswhom" version = "0.1.0" @@ -6923,12 +6647,6 @@ dependencies = [ "libc", ] -[[package]] -name = "xmlparser" -version = "0.13.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d25c75bf9ea12c4040a97f829154768bbbce366287e2dc044af160cd79a13fd" - [[package]] name = "y-sync" version = "0.3.1" diff --git a/frontend/appflowy_tauri/src/appflowy_app/components/user/application/notifications/user_listener.ts b/frontend/appflowy_tauri/src/appflowy_app/components/user/application/notifications/user_listener.ts index 94e0043108..d63963eaf0 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/components/user/application/notifications/user_listener.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/components/user/application/notifications/user_listener.ts @@ -4,18 +4,11 @@ import { UserNotificationParser } from './parser'; import { Ok, Result } from 'ts-results'; declare type OnUserProfileUpdate = (result: Result) => void; -declare type OnUserSignIn = (result: Result) => void; export class UserNotificationListener extends AFNotificationObserver { onProfileUpdate?: OnUserProfileUpdate; - onUserSignIn?: OnUserSignIn; - constructor(params: { - userId?: string; - onUserSignIn?: OnUserSignIn; - onProfileUpdate?: OnUserProfileUpdate; - onError?: OnNotificationError; - }) { + constructor(params: { userId?: string; onProfileUpdate?: OnUserProfileUpdate; onError?: OnNotificationError }) { const parser = new UserNotificationParser({ callback: (notification, result) => { switch (notification) { @@ -26,13 +19,6 @@ export class UserNotificationListener extends AFNotificationObserver, - device_id: Mutex, + device_id: Arc>, providers: RwLock>>, enable_sync: RwLock, encryption: RwLock>, store_preferences: Weak, + cache_user_service: RwLock>>, } impl AppFlowyServerProvider { @@ -86,11 +86,12 @@ impl AppFlowyServerProvider { enable_sync: RwLock::new(true), encryption: RwLock::new(Arc::new(encryption)), store_preferences, + cache_user_service: Default::default(), } } pub fn set_sync_device(&self, device_id: &str) { - *self.device_id.lock() = device_id.to_string(); + *self.device_id.write() = device_id.to_string(); } pub fn provider_type(&self) -> ServerProviderType { @@ -134,11 +135,11 @@ impl AppFlowyServerProvider { Ok::, FlowyError>(Arc::new(SupabaseServer::new( config, *self.enable_sync.read(), + self.device_id.clone(), encryption, ))) }, }?; - server.set_sync_device_id(&self.device_id.lock()); self .providers @@ -146,13 +147,6 @@ impl AppFlowyServerProvider { .insert(provider_type.clone(), server.clone()); Ok(server) } - - pub fn handle_realtime_event(&self, json: Value) { - let provider_type = self.provider_type.read().clone(); - if let Some(server) = self.providers.read().get(&provider_type) { - server.handle_realtime_event(json); - } - } } impl UserCloudServiceProvider for AppFlowyServerProvider { @@ -195,17 +189,27 @@ impl UserCloudServiceProvider for AppFlowyServerProvider { } fn set_device_id(&self, device_id: &str) { - *self.device_id.lock() = device_id.to_string(); + *self.device_id.write() = device_id.to_string(); } /// Returns the [UserService] base on the current [ServerProviderType]. /// Creates a new [AppFlowyServer] if it doesn't exist. fn get_user_service(&self) -> Result, FlowyError> { - Ok( - self - .get_provider(&self.provider_type.read())? - .user_service(), - ) + if let Some(user_service) = self + .cache_user_service + .read() + .get(&self.provider_type.read()) + { + return Ok(user_service.clone()); + } + + let provider_type = self.provider_type.read().clone(); + let user_service = self.get_provider(&provider_type)?.user_service(); + self + .cache_user_service + .write() + .insert(provider_type, user_service.clone()); + Ok(user_service) } fn service_name(&self) -> String { diff --git a/frontend/rust-lib/flowy-core/src/lib.rs b/frontend/rust-lib/flowy-core/src/lib.rs index fbaefece71..09a68b6baf 100644 --- a/frontend/rust-lib/flowy-core/src/lib.rs +++ b/frontend/rust-lib/flowy-core/src/lib.rs @@ -11,7 +11,6 @@ use std::{ }; use appflowy_integrate::collab_builder::{AppFlowyCollabBuilder, CollabStorageType}; -use serde_json::Value; use tokio::sync::RwLock; use flowy_database2::DatabaseManager; @@ -268,12 +267,12 @@ fn mk_user_session( collab_builder: Weak, ) -> Arc { let user_config = UserSessionConfig::new(&config.name, &config.storage_path); - Arc::new(UserManager::new( + UserManager::new( user_config, user_cloud_service_provider, storage_preference.clone(), collab_builder, - )) + ) } struct UserStatusCallbackImpl { @@ -439,10 +438,6 @@ impl UserStatusCallback for UserStatusCallbackImpl { fn did_update_network(&self, reachable: bool) { self.collab_builder.update_network(reachable); } - - fn receive_realtime_event(&self, json: Value) { - self.server_provider.handle_realtime_event(json); - } } impl From for CollabStorageType { diff --git a/frontend/rust-lib/flowy-server/src/lib.rs b/frontend/rust-lib/flowy-server/src/lib.rs index 90fad79652..d8e1e5b7f0 100644 --- a/frontend/rust-lib/flowy-server/src/lib.rs +++ b/frontend/rust-lib/flowy-server/src/lib.rs @@ -2,7 +2,6 @@ use std::sync::Arc; use collab_plugins::cloud_storage::{CollabObject, RemoteCollabStorage}; use parking_lot::RwLock; -use serde_json::Value; use flowy_database_deps::cloud::DatabaseCloudService; use flowy_document_deps::cloud::DocumentCloudService; @@ -36,13 +35,11 @@ where pub trait AppFlowyServer: Send + Sync + 'static { fn set_enable_sync(&self, _enable: bool) {} - fn set_sync_device_id(&self, _device_id: &str) {} fn user_service(&self) -> Arc; fn folder_service(&self) -> Arc; fn database_service(&self) -> Arc; fn document_service(&self) -> Arc; fn collab_storage(&self, collab_object: &CollabObject) -> Option>; - fn handle_realtime_event(&self, _json: Value) {} } pub struct EncryptionImpl { diff --git a/frontend/rust-lib/flowy-server/src/supabase/api/request.rs b/frontend/rust-lib/flowy-server/src/supabase/api/request.rs index b3e01acae9..f46f2fa6dd 100644 --- a/frontend/rust-lib/flowy-server/src/supabase/api/request.rs +++ b/frontend/rust-lib/flowy-server/src/supabase/api/request.rs @@ -16,7 +16,8 @@ use flowy_database_deps::cloud::{CollabObjectUpdate, CollabObjectUpdateByOid}; use lib_infra::util::md5; use crate::supabase::api::util::{ - ExtendedResponse, InsertParamsBuilder, SupabaseBinaryColumnDecoder, SupabaseBinaryColumnEncoder, + BinaryColumnDecoder, ExtendedResponse, InsertParamsBuilder, SupabaseBinaryColumnDecoder, + SupabaseBinaryColumnEncoder, }; use crate::supabase::api::PostgresWrapper; use crate::supabase::define::*; @@ -220,7 +221,8 @@ fn parser_snapshot( .and_then(|value| value.as_str()), ) { (Some(encrypt), Some(value)) => { - SupabaseBinaryColumnDecoder::decode(value, encrypt as i32, secret).ok() + SupabaseBinaryColumnDecoder::decode::<_, BinaryColumnDecoder>(value, encrypt as i32, secret) + .ok() }, _ => None, }?; @@ -364,7 +366,11 @@ fn parser_update_from_json( json.get("value").and_then(|value| value.as_str()), ) { (Some(encrypt), Some(value)) => { - match SupabaseBinaryColumnDecoder::decode(value, encrypt as i32, encryption_secret) { + match SupabaseBinaryColumnDecoder::decode::<_, BinaryColumnDecoder>( + value, + encrypt as i32, + encryption_secret, + ) { Ok(value) => Some(value), Err(err) => { tracing::error!("Decode value column failed: {:?}", err); diff --git a/frontend/rust-lib/flowy-server/src/supabase/api/user.rs b/frontend/rust-lib/flowy-server/src/supabase/api/user.rs index 9890e5f477..5d627d4163 100644 --- a/frontend/rust-lib/flowy-server/src/supabase/api/user.rs +++ b/frontend/rust-lib/flowy-server/src/supabase/api/user.rs @@ -1,8 +1,10 @@ use std::str::FromStr; -use std::sync::Arc; +use std::sync::{Arc, Weak}; use anyhow::Error; use collab_plugins::cloud_storage::CollabObject; +use parking_lot::RwLock; +use serde_json::Value; use tokio::sync::oneshot::channel; use uuid::Uuid; @@ -13,20 +15,34 @@ use lib_infra::box_any::BoxAny; use lib_infra::future::FutureResult; use crate::supabase::api::request::FetchObjectUpdateAction; -use crate::supabase::api::util::{ExtendedResponse, InsertParamsBuilder}; +use crate::supabase::api::util::{ + ExtendedResponse, InsertParamsBuilder, RealtimeBinaryColumnDecoder, SupabaseBinaryColumnDecoder, +}; use crate::supabase::api::{send_update, PostgresWrapper, SupabaseServerService}; use crate::supabase::define::*; -use crate::supabase::entities::GetUserProfileParams; -use crate::supabase::entities::UidResponse; use crate::supabase::entities::UserProfileResponse; +use crate::supabase::entities::{GetUserProfileParams, RealtimeUserEvent}; +use crate::supabase::entities::{RealtimeCollabUpdateEvent, RealtimeEvent, UidResponse}; +use crate::supabase::CollabUpdateSenderByOid; +use crate::AppFlowyEncryption; pub struct SupabaseUserServiceImpl { server: T, + realtime_event_handlers: Vec>, + user_update_tx: Option, } impl SupabaseUserServiceImpl { - pub fn new(server: T) -> Self { - Self { server } + pub fn new( + server: T, + realtime_event_handlers: Vec>, + user_update_tx: Option, + ) -> Self { + Self { + server, + realtime_event_handlers, + user_update_tx, + } } } @@ -67,7 +83,11 @@ where } // Query the user profile and workspaces - tracing::debug!("user uuid: {}", params.uuid); + tracing::debug!( + "user uuid: {}, device_id: {}", + params.uuid, + params.device_id + ); let user_profile = get_user_profile(postgrest.clone(), GetUserProfileParams::Uuid(params.uuid)) .await? @@ -226,6 +246,26 @@ where FutureResult::new(async { rx.await? }) } + fn receive_realtime_event(&self, json: Value) { + match serde_json::from_value::(json) { + Ok(event) => { + tracing::trace!("Realtime event: {}", event); + for handler in &self.realtime_event_handlers { + if event.table.as_str().starts_with(handler.table_name()) { + handler.handler_event(&event); + } + } + }, + Err(e) => { + tracing::error!("parser realtime event error: {}", e); + }, + } + } + + fn subscribe_user_update(&self) -> Option { + self.user_update_tx.as_ref().map(|tx| tx.subscribe()) + } + fn create_collab_object( &self, collab_object: &CollabObject, @@ -384,3 +424,95 @@ async fn check_user( } Ok(()) } + +pub trait RealtimeEventHandler: Send + Sync + 'static { + fn table_name(&self) -> &str; + + fn handler_event(&self, event: &RealtimeEvent); +} + +pub struct RealtimeUserHandler(pub UserUpdateSender); +impl RealtimeEventHandler for RealtimeUserHandler { + fn table_name(&self) -> &str { + "af_user" + } + + fn handler_event(&self, event: &RealtimeEvent) { + if let Ok(user_event) = serde_json::from_value::(event.new.clone()) { + let _ = self.0.send(UserUpdate { + uid: user_event.uid, + name: user_event.name, + email: user_event.email, + encryption_sign: user_event.encryption_sign, + }); + } + } +} + +pub struct RealtimeCollabUpdateHandler { + sender_by_oid: Weak, + device_id: Arc>, + encryption: Weak, +} + +impl RealtimeCollabUpdateHandler { + pub fn new( + sender_by_oid: Weak, + device_id: Arc>, + encryption: Weak, + ) -> Self { + Self { + sender_by_oid, + device_id, + encryption, + } + } +} +impl RealtimeEventHandler for RealtimeCollabUpdateHandler { + fn table_name(&self) -> &str { + "af_collab_update" + } + + fn handler_event(&self, event: &RealtimeEvent) { + if let Ok(collab_update) = + serde_json::from_value::(event.new.clone()) + { + if let Some(sender_by_oid) = self.sender_by_oid.upgrade() { + if let Some(sender) = sender_by_oid.read().get(collab_update.oid.as_str()) { + tracing::trace!( + "current device: {}, event device: {}", + self.device_id.read(), + collab_update.did.as_str() + ); + if *self.device_id.read() != collab_update.did.as_str() { + let encryption_secret = self + .encryption + .upgrade() + .and_then(|encryption| encryption.get_secret()); + + tracing::trace!( + "Parse collab update with len: {}, encrypt: {}", + collab_update.value.len(), + collab_update.encrypt, + ); + + match SupabaseBinaryColumnDecoder::decode::<_, RealtimeBinaryColumnDecoder>( + collab_update.value.as_str(), + collab_update.encrypt, + &encryption_secret, + ) { + Ok(value) => { + if let Err(e) = sender.send(value) { + tracing::debug!("send realtime update error: {}", e); + } + }, + Err(err) => { + tracing::error!("decode collab update error: {}", err); + }, + } + } + } + } + } + } +} diff --git a/frontend/rust-lib/flowy-server/src/supabase/api/util.rs b/frontend/rust-lib/flowy-server/src/supabase/api/util.rs index b8c956103b..5a438b36b4 100644 --- a/frontend/rust-lib/flowy-server/src/supabase/api/util.rs +++ b/frontend/rust-lib/flowy-server/src/supabase/api/util.rs @@ -171,7 +171,7 @@ impl SupabaseBinaryColumnDecoder { /// # Returns /// Returns an `Option` containing the decoded binary data if decoding is successful. /// Otherwise, returns `None`. - pub fn decode>( + pub fn decode, D: HexDecoder>( value: T, encrypt: i32, encryption_secret: &Option, @@ -182,7 +182,7 @@ impl SupabaseBinaryColumnDecoder { .ok_or(anyhow::anyhow!("Value is not start with: \\x",))?; if encrypt == 0 { - let bytes = hex::decode(s)?; + let bytes = D::decode(s)?; Ok(bytes) } else { match encryption_secret { @@ -190,7 +190,7 @@ impl SupabaseBinaryColumnDecoder { "encryption_secret is None, but encrypt is 1" )), Some(encryption_secret) => { - let encrypt_data = hex::decode(s)?; + let encrypt_data = D::decode(s)?; decrypt_bytes(encrypt_data, encryption_secret) }, } @@ -198,15 +198,24 @@ impl SupabaseBinaryColumnDecoder { } } -/// A decoder specifically tailored for realtime event binary columns in Supabase. -/// -pub struct SupabaseRealtimeEventBinaryColumnDecoder; +pub trait HexDecoder { + fn decode>(data: T) -> Result, Error>; +} -impl SupabaseRealtimeEventBinaryColumnDecoder { - /// The realtime event binary column string is encoded twice. So it needs to be decoded twice. - pub fn decode>(value: T) -> Option> { - let s = value.as_ref().strip_prefix("\\x")?; - let bytes = hex::decode(s).ok()?; - hex::decode(bytes).ok() +pub struct RealtimeBinaryColumnDecoder; +impl HexDecoder for RealtimeBinaryColumnDecoder { + fn decode>(data: T) -> Result, Error> { + // The realtime event binary column string is encoded twice. So it needs to be decoded twice. + let bytes = hex::decode(data)?; + let bytes = hex::decode(bytes)?; + Ok(bytes) + } +} + +pub struct BinaryColumnDecoder; +impl HexDecoder for BinaryColumnDecoder { + fn decode>(data: T) -> Result, Error> { + let bytes = hex::decode(data)?; + Ok(bytes) } } diff --git a/frontend/rust-lib/flowy-server/src/supabase/entities.rs b/frontend/rust-lib/flowy-server/src/supabase/entities.rs index d6288e68d8..d50374caed 100644 --- a/frontend/rust-lib/flowy-server/src/supabase/entities.rs +++ b/frontend/rust-lib/flowy-server/src/supabase/entities.rs @@ -1,11 +1,10 @@ use std::fmt; use std::fmt::Display; -use serde::de::{Error, Visitor}; -use serde::{Deserialize, Deserializer}; +use serde::Deserialize; +use serde_json::Value; use uuid::Uuid; -use crate::supabase::api::util::SupabaseRealtimeEventBinaryColumnDecoder; use crate::util::deserialize_null_or_default; pub enum GetUserProfileParams { @@ -40,16 +39,14 @@ pub(crate) struct UidResponse { } #[derive(Debug, Deserialize)] -pub struct RealtimeCollabUpdateEvent { +pub struct RealtimeEvent { pub schema: String, pub table: String, #[serde(rename = "eventType")] pub event_type: String, - #[serde(rename = "new")] - pub payload: RealtimeCollabUpdate, + pub new: Value, } - -impl Display for RealtimeCollabUpdateEvent { +impl Display for RealtimeEvent { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, @@ -60,43 +57,23 @@ impl Display for RealtimeCollabUpdateEvent { } #[derive(Debug, Deserialize)] -pub struct RealtimeCollabUpdate { +pub struct RealtimeCollabUpdateEvent { pub oid: String, pub uid: i64, pub key: i64, pub did: String, - #[serde(deserialize_with = "deserialize_value")] - pub value: Vec, + pub value: String, #[serde(default)] pub encrypt: i32, } -pub fn deserialize_value<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - struct ValueVisitor(); - - impl<'de> Visitor<'de> for ValueVisitor { - type Value = Vec; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("Expect NodeBody") - } - - fn visit_str(self, v: &str) -> Result - where - E: Error, - { - Ok(SupabaseRealtimeEventBinaryColumnDecoder::decode(v).unwrap_or_default()) - } - - fn visit_string(self, v: String) -> Result - where - E: Error, - { - Ok(SupabaseRealtimeEventBinaryColumnDecoder::decode(v).unwrap_or_default()) - } - } - deserializer.deserialize_any(ValueVisitor()) +#[derive(Debug, Deserialize)] +pub struct RealtimeUserEvent { + pub uid: i64, + #[serde(deserialize_with = "deserialize_null_or_default")] + pub name: String, + #[serde(deserialize_with = "deserialize_null_or_default")] + pub email: String, + #[serde(deserialize_with = "deserialize_null_or_default")] + pub encryption_sign: String, } diff --git a/frontend/rust-lib/flowy-server/src/supabase/server.rs b/frontend/rust-lib/flowy-server/src/supabase/server.rs index c325692b9b..0bd641d7ee 100644 --- a/frontend/rust-lib/flowy-server/src/supabase/server.rs +++ b/frontend/rust-lib/flowy-server/src/supabase/server.rs @@ -2,22 +2,19 @@ use std::collections::HashMap; use std::sync::{Arc, Weak}; use collab_plugins::cloud_storage::{CollabObject, RemoteCollabStorage, RemoteUpdateSender}; -use parking_lot::{Mutex, RwLock}; -use serde_json::Value; +use parking_lot::RwLock; use flowy_database_deps::cloud::DatabaseCloudService; use flowy_document_deps::cloud::DocumentCloudService; -use flowy_encrypt::decrypt_bytes; use flowy_folder_deps::cloud::FolderCloudService; use flowy_server_config::supabase_config::SupabaseConfiguration; use flowy_user_deps::cloud::UserService; use crate::supabase::api::{ - RESTfulPostgresServer, SupabaseCollabStorageImpl, SupabaseDatabaseServiceImpl, - SupabaseDocumentServiceImpl, SupabaseFolderServiceImpl, SupabaseServerServiceImpl, - SupabaseUserServiceImpl, + RESTfulPostgresServer, RealtimeCollabUpdateHandler, RealtimeEventHandler, RealtimeUserHandler, + SupabaseCollabStorageImpl, SupabaseDatabaseServiceImpl, SupabaseDocumentServiceImpl, + SupabaseFolderServiceImpl, SupabaseServerServiceImpl, SupabaseUserServiceImpl, }; -use crate::supabase::entities::RealtimeCollabUpdateEvent; use crate::{AppFlowyEncryption, AppFlowyServer}; /// https://www.pgbouncer.org/features.html @@ -53,14 +50,16 @@ impl PgPoolMode { matches!(self, PgPoolMode::Session) } } + +pub type CollabUpdateSenderByOid = RwLock>; /// Supabase server is used to provide the implementation of the [AppFlowyServer] trait. /// It contains the configuration of the supabase server and the postgres server. pub struct SupabaseServer { #[allow(dead_code)] config: SupabaseConfiguration, /// did represents as the device id is used to identify the device that is currently using the app. - did: Mutex, - update_tx: RwLock>, + device_id: Arc>, + collab_update_sender: Arc, restful_postgres: Arc>>>, encryption: Weak, } @@ -69,9 +68,10 @@ impl SupabaseServer { pub fn new( config: SupabaseConfiguration, enable_sync: bool, + device_id: Arc>, encryption: Weak, ) -> Self { - let update_tx = RwLock::new(HashMap::new()); + let collab_update_sender = Default::default(); let restful_postgres = if enable_sync { Some(Arc::new(RESTfulPostgresServer::new( config.clone(), @@ -82,8 +82,8 @@ impl SupabaseServer { }; Self { config, - did: Default::default(), - update_tx, + device_id, + collab_update_sender, restful_postgres: Arc::new(RwLock::new(restful_postgres)), encryption, } @@ -108,14 +108,25 @@ impl AppFlowyServer for SupabaseServer { self.set_enable_sync(enable); } - fn set_sync_device_id(&self, device_id: &str) { - *self.did.lock() = device_id.to_string(); - } - fn user_service(&self) -> Arc { - Arc::new(SupabaseUserServiceImpl::new(SupabaseServerServiceImpl( - self.restful_postgres.clone(), - ))) + // handle the realtime collab update event. + let (user_update_tx, _) = tokio::sync::broadcast::channel(100); + + let collab_update_handler = Box::new(RealtimeCollabUpdateHandler::new( + Arc::downgrade(&self.collab_update_sender), + self.device_id.clone(), + self.encryption.clone(), + )); + + // handle the realtime user event. + let user_handler = Box::new(RealtimeUserHandler(user_update_tx.clone())); + + let handlers: Vec> = vec![collab_update_handler, user_handler]; + Arc::new(SupabaseUserServiceImpl::new( + SupabaseServerServiceImpl(self.restful_postgres.clone()), + handlers, + Some(user_update_tx), + )) } fn folder_service(&self) -> Arc { @@ -139,53 +150,14 @@ impl AppFlowyServer for SupabaseServer { fn collab_storage(&self, collab_object: &CollabObject) -> Option> { let (tx, rx) = tokio::sync::mpsc::unbounded_channel(); self - .update_tx + .collab_update_sender .write() .insert(collab_object.object_id.clone(), tx); + Some(Arc::new(SupabaseCollabStorageImpl::new( SupabaseServerServiceImpl(self.restful_postgres.clone()), Some(rx), self.encryption.clone(), ))) } - - fn handle_realtime_event(&self, json: Value) { - match serde_json::from_value::(json) { - Ok(event) => { - if let Some(tx) = self.update_tx.read().get(event.payload.oid.as_str()) { - tracing::trace!( - "current device: {}, event device: {}", - self.did.lock().as_str(), - event.payload.did.as_str() - ); - - if self.did.lock().as_str() != event.payload.did.as_str() { - tracing::trace!("Did receive realtime event: {}", event); - let value = if event.payload.encrypt == 1 { - match self - .encryption - .upgrade() - .and_then(|encryption| encryption.get_secret()) - { - None => vec![], - Some(secret) => decrypt_bytes(event.payload.value, &secret).unwrap_or_default(), - } - } else { - event.payload.value - }; - - if !value.is_empty() { - tracing::trace!("Parse payload with len: {} success", value.len()); - if let Err(e) = tx.send(value) { - tracing::trace!("send realtime update error: {}", e); - } - } - } - } - }, - Err(e) => { - tracing::error!("parser realtime event error: {}", e); - }, - } - } } diff --git a/frontend/rust-lib/flowy-server/tests/supabase_test/util.rs b/frontend/rust-lib/flowy-server/tests/supabase_test/util.rs index b32e55c595..9fd5546129 100644 --- a/frontend/rust-lib/flowy-server/tests/supabase_test/util.rs +++ b/frontend/rust-lib/flowy-server/tests/supabase_test/util.rs @@ -48,7 +48,7 @@ pub fn database_service() -> Arc { pub fn user_auth_service() -> Arc { let (server, _encryption_impl) = appflowy_server(None); - Arc::new(SupabaseUserServiceImpl::new(server)) + Arc::new(SupabaseUserServiceImpl::new(server, vec![], None)) } pub fn folder_service() -> Arc { diff --git a/frontend/rust-lib/flowy-test/tests/util.rs b/frontend/rust-lib/flowy-test/tests/util.rs index 1c4c194881..57d703b1a7 100644 --- a/frontend/rust-lib/flowy-test/tests/util.rs +++ b/frontend/rust-lib/flowy-test/tests/util.rs @@ -112,7 +112,7 @@ pub fn database_service() -> Arc { pub fn user_auth_service() -> Arc { let (server, _encryption_impl) = appflowy_server(None); - Arc::new(SupabaseUserServiceImpl::new(server)) + Arc::new(SupabaseUserServiceImpl::new(server, vec![], None)) } pub fn folder_service() -> Arc { diff --git a/frontend/rust-lib/flowy-user-deps/Cargo.toml b/frontend/rust-lib/flowy-user-deps/Cargo.toml index 95285e7093..86cb572c86 100644 --- a/frontend/rust-lib/flowy-user-deps/Cargo.toml +++ b/frontend/rust-lib/flowy-user-deps/Cargo.toml @@ -15,3 +15,4 @@ serde_json = {version = "1.0"} serde_repr = "0.1" chrono = { version = "0.4.22", default-features = false, features = ["clock"] } anyhow = "1.0.71" +tokio = { version = "1.26", features = ["sync"] } diff --git a/frontend/rust-lib/flowy-user-deps/src/cloud.rs b/frontend/rust-lib/flowy-user-deps/src/cloud.rs index 7620cbf73b..8400a865f4 100644 --- a/frontend/rust-lib/flowy-user-deps/src/cloud.rs +++ b/frontend/rust-lib/flowy-user-deps/src/cloud.rs @@ -5,6 +5,7 @@ use std::str::FromStr; use anyhow::Error; use collab_define::CollabObject; use serde::{Deserialize, Serialize}; +use serde_json::Value; use uuid::Uuid; use flowy_error::{ErrorCode, FlowyError}; @@ -103,6 +104,12 @@ pub trait UserService: Send + Sync { fn get_user_awareness_updates(&self, uid: i64) -> FutureResult>, Error>; + fn receive_realtime_event(&self, _json: Value) {} + + fn subscribe_user_update(&self) -> Option { + None + } + fn create_collab_object( &self, collab_object: &CollabObject, @@ -110,6 +117,16 @@ pub trait UserService: Send + Sync { ) -> FutureResult<(), Error>; } +pub type UserUpdateReceiver = tokio::sync::broadcast::Receiver; +pub type UserUpdateSender = tokio::sync::broadcast::Sender; +#[derive(Debug, Clone)] +pub struct UserUpdate { + pub uid: i64, + pub name: String, + pub email: String, + pub encryption_sign: String, +} + pub fn third_party_params_from_box_any(any: BoxAny) -> Result { let map: HashMap = any.unbox_or_error()?; let uuid = uuid_from_map(&map)?; diff --git a/frontend/rust-lib/flowy-user-deps/src/entities.rs b/frontend/rust-lib/flowy-user-deps/src/entities.rs index 930e183ccb..7a3883fe20 100644 --- a/frontend/rust-lib/flowy-user-deps/src/entities.rs +++ b/frontend/rust-lib/flowy-user-deps/src/entities.rs @@ -211,15 +211,20 @@ impl EncryptionType { EncryptionType::SelfEncryption(sign.to_owned()) } } -} -impl EncryptionType { pub fn is_need_encrypt_secret(&self) -> bool { match self { EncryptionType::NoEncryption => false, EncryptionType::SelfEncryption(sign) => !sign.is_empty(), } } + + pub fn sign(&self) -> String { + match self { + EncryptionType::NoEncryption => "".to_owned(), + EncryptionType::SelfEncryption(sign) => sign.to_owned(), + } + } } impl FromStr for EncryptionType { diff --git a/frontend/rust-lib/flowy-user/src/entities/auth.rs b/frontend/rust-lib/flowy-user/src/entities/auth.rs index 091e026295..31bb18972f 100644 --- a/frontend/rust-lib/flowy-user/src/entities/auth.rs +++ b/frontend/rust-lib/flowy-user/src/entities/auth.rs @@ -154,3 +154,24 @@ pub struct UserStatePB { #[pb(index = 1)] pub auth_type: AuthTypePB, } + +#[derive(ProtoBuf, Debug, Default, Clone)] +pub struct AuthStateChangedPB { + #[pb(index = 1)] + pub state: AuthStatePB, +} + +#[derive(ProtoBuf_Enum, Debug, Clone)] +pub enum AuthStatePB { + // adding AuthState prefix to avoid conflict with other enums + AuthStateUnknown = 0, + AuthStateSignIn = 1, + AuthStateSignOut = 2, + AuthStateForceSignOut = 3, +} + +impl Default for AuthStatePB { + fn default() -> Self { + Self::AuthStateUnknown + } +} diff --git a/frontend/rust-lib/flowy-user/src/event_handler.rs b/frontend/rust-lib/flowy-user/src/event_handler.rs index e21372af11..848ba73859 100644 --- a/frontend/rust-lib/flowy-user/src/event_handler.rs +++ b/frontend/rust-lib/flowy-user/src/event_handler.rs @@ -95,8 +95,9 @@ pub async fn get_user_profile_handler( ) -> DataResult { let manager = upgrade_manager(manager)?; let uid = manager.get_session()?.user_id; - let user_profile: UserProfilePB = manager.get_user_profile(uid, true).await?.into(); - data_result_ok(user_profile) + let user_profile = manager.get_user_profile(uid).await?; + let _ = manager.refresh_user_profile(&user_profile).await; + data_result_ok(user_profile.into()) } #[tracing::instrument(level = "debug", skip(manager))] @@ -222,7 +223,7 @@ pub async fn check_encrypt_secret_handler( ) -> DataResult { let manager = upgrade_manager(manager)?; let uid = manager.get_session()?.user_id; - let profile = manager.get_user_profile(uid, false).await?; + let profile = manager.get_user_profile(uid).await?; let is_need_secret = match profile.encryption_type { EncryptionType::NoEncryption => false, diff --git a/frontend/rust-lib/flowy-user/src/event_map.rs b/frontend/rust-lib/flowy-user/src/event_map.rs index 17e82e9ae7..56a0ce909c 100644 --- a/frontend/rust-lib/flowy-user/src/event_map.rs +++ b/frontend/rust-lib/flowy-user/src/event_map.rs @@ -1,7 +1,6 @@ use std::sync::{Arc, Weak}; use collab_folder::core::FolderData; -use serde_json::Value; use strum_macros::Display; use flowy_derive::{Flowy_Event, ProtoBuf_Enum}; @@ -97,7 +96,6 @@ pub trait UserStatusCallback: Send + Sync + 'static { fn did_expired(&self, token: &str, user_id: i64) -> Fut>; fn open_workspace(&self, user_id: i64, user_workspace: &UserWorkspace) -> Fut>; fn did_update_network(&self, _reachable: bool) {} - fn receive_realtime_event(&self, _json: Value) {} } /// The user cloud service provider. diff --git a/frontend/rust-lib/flowy-user/src/manager.rs b/frontend/rust-lib/flowy-user/src/manager.rs index c8d37ceee1..766658e5ee 100644 --- a/frontend/rust-lib/flowy-user/src/manager.rs +++ b/frontend/rust-lib/flowy-user/src/manager.rs @@ -14,10 +14,11 @@ use flowy_sqlite::kv::StorePreferences; use flowy_sqlite::schema::user_table; use flowy_sqlite::ConnectionPool; use flowy_sqlite::{query_dsl::*, DBConnection, ExpressionMethods}; +use flowy_user_deps::cloud::UserUpdate; use flowy_user_deps::entities::*; use lib_infra::box_any::BoxAny; -use crate::entities::{UserProfilePB, UserSettingPB}; +use crate::entities::{AuthStateChangedPB, AuthStatePB, UserProfilePB, UserSettingPB}; use crate::event_map::{ DefaultUserStatusCallback, SignUpContext, UserCloudServiceProvider, UserStatusCallback, }; @@ -61,6 +62,7 @@ pub struct UserManager { pub(crate) user_status_callback: RwLock>, pub(crate) collab_builder: Weak, resumable_sign_up: Mutex>, + current_session: parking_lot::RwLock>, } impl UserManager { @@ -69,11 +71,12 @@ impl UserManager { cloud_services: Arc, store_preferences: Arc, collab_builder: Weak, - ) -> Self { + ) -> Arc { let database = UserDB::new(&session_config.root_dir); let user_status_callback: RwLock> = RwLock::new(Arc::new(DefaultUserStatusCallback)); - Self { + + let user_manager = Arc::new(Self { database, session_config, cloud_services, @@ -82,7 +85,25 @@ impl UserManager { user_status_callback, collab_builder, resumable_sign_up: Default::default(), + current_session: Default::default(), + }); + + let weak_user_manager = Arc::downgrade(&user_manager); + if let Ok(user_service) = user_manager.cloud_services.get_user_service() { + if let Some(mut rx) = user_service.subscribe_user_update() { + tokio::spawn(async move { + while let Ok(update) = rx.recv().await { + if let Some(user_manager) = weak_user_manager.upgrade() { + if let Err(err) = user_manager.handler_user_update(update).await { + tracing::error!("handler_user_update failed: {:?}", err); + } + } + } + }); + } } + + user_manager } pub fn get_store_preferences(&self) -> Weak { @@ -121,6 +142,7 @@ impl UserManager { self .initialize_user_awareness(&session, UserAwarenessDataSource::Local) .await; + let cloud_config = get_cloud_config(session.user_id, &self.store_preferences); if let Err(e) = user_status_callback .did_init( @@ -191,9 +213,10 @@ impl UserManager { { tracing::error!("Failed to call did_sign_in callback: {:?}", e); } - send_sign_in_notification() - .payload::(user_profile.clone().into()) - .send(); + send_auth_state_notification(AuthStateChangedPB { + state: AuthStatePB::AuthStateSignIn, + }) + .send(); Ok(user_profile) } @@ -322,6 +345,11 @@ impl UserManager { self .save_auth_data(&response, auth_type, &new_session) .await?; + + send_auth_state_notification(AuthStateChangedPB { + state: AuthStatePB::AuthStateSignIn, + }) + .send(); Ok(()) } @@ -329,7 +357,7 @@ impl UserManager { pub async fn sign_out(&self) -> Result<(), FlowyError> { let session = self.get_session()?; self.database.close(session.user_id)?; - self.set_current_session(None)?; + self.set_session(None)?; let server = self.cloud_services.get_user_service()?; tokio::spawn(async move { @@ -352,27 +380,10 @@ impl UserManager { &self, params: UpdateUserProfileParams, ) -> Result<(), FlowyError> { - let old_user_profile = self.get_user_profile(params.uid, false).await?; - let auth_type = old_user_profile.auth_type.clone(); - let session = self.get_session()?; let changeset = UserTableChangeset::new(params.clone()); - diesel_update_table!( - user_table, - changeset, - &*self.db_connection(session.user_id)? - ); - let session = self.get_session()?; - let new_user_profile = self.get_user_profile(session.user_id, false).await?; - send_notification( - &session.user_id.to_string(), - UserNotification::DidUpdateUserProfile, - ) - .payload(UserProfilePB::from(new_user_profile)) - .send(); - self - .update_user(&auth_type, session.user_id, None, params) - .await?; + save_user_profile_change(session.user_id, self.db_pool(session.user_id)?, changeset)?; + self.update_user(session.user_id, None, params).await?; Ok(()) } @@ -396,44 +407,38 @@ impl UserManager { } /// Fetches the user profile for the given user ID. - /// - /// This function retrieves the user profile from the local database. If the `refresh` flag is set to `true`, - /// it also attempts to update the user profile from a cloud service, and then sends a notification about the - /// profile update. - pub async fn get_user_profile(&self, uid: i64, refresh: bool) -> Result { - let user_id = uid.to_string(); - let user = user_table::dsl::user_table - .filter(user_table::id.eq(&user_id)) - .first::(&*(self.db_connection(uid)?))?; + pub async fn get_user_profile(&self, uid: i64) -> Result { + let user: UserProfile = user_table::dsl::user_table + .filter(user_table::id.eq(&uid.to_string())) + .first::(&*(self.db_connection(uid)?))? + .into(); - if refresh { - let weak_auth_service = Arc::downgrade(&self.cloud_services.get_user_service()?); - let weak_pool = Arc::downgrade(&self.database.get_pool(uid)?); - tokio::spawn(async move { - if let (Some(auth_service), Some(pool)) = (weak_auth_service.upgrade(), weak_pool.upgrade()) - { - if let Ok(Some(user_profile)) = auth_service - .get_user_profile(UserCredentials::from_uid(uid)) - .await - { - let changeset = UserTableChangeset::from_user_profile(user_profile.clone()); - if let Ok(conn) = pool.get() { - let filter = - user_table::dsl::user_table.filter(user_table::dsl::id.eq(changeset.id.clone())); - let _ = diesel::update(filter).set(changeset).execute(&*conn); + Ok(user) + } - // Send notification to the client - let user_profile_pb: UserProfilePB = user_profile.into(); - send_notification(&uid.to_string(), UserNotification::DidUpdateUserProfile) - .payload(user_profile_pb) - .send(); - } - } - } - }); + #[tracing::instrument(level = "info", skip_all)] + pub async fn refresh_user_profile( + &self, + old_user_profile: &UserProfile, + ) -> FlowyResult { + let uid = old_user_profile.uid; + let new_user_profile: UserProfile = self + .cloud_services + .get_user_service()? + .get_user_profile(UserCredentials::from_uid(uid)) + .await? + .ok_or_else(|| FlowyError::new(ErrorCode::RecordNotFound, "User not found"))?; + + if !is_user_encryption_sign_valid(old_user_profile, &new_user_profile.encryption_type.sign()) { + return Err(FlowyError::new( + ErrorCode::InvalidEncryptSecret, + "Invalid encryption sign", + )); } - Ok(user.into()) + let changeset = UserTableChangeset::from_user_profile(new_user_profile.clone()); + let _ = save_user_profile_change(uid, self.database.get_pool(uid)?, changeset); + Ok(new_user_profile) } pub fn user_dir(&self, uid: i64) -> String { @@ -458,7 +463,6 @@ impl UserManager { async fn update_user( &self, - _auth_type: &AuthType, uid: i64, token: Option, params: UpdateUserProfileParams, @@ -490,32 +494,18 @@ impl UserManager { Ok(()) } - pub(crate) fn set_current_session(&self, session: Option) -> Result<(), FlowyError> { - tracing::debug!("Set current user: {:?}", session); - match &session { - None => self - .store_preferences - .remove(&self.session_config.session_cache_key), - Some(session) => { - self - .store_preferences - .set_object(&self.session_config.session_cache_key, session.clone()) - .map_err(internal_error)?; - }, - } - Ok(()) - } - pub async fn receive_realtime_event(&self, json: Value) { - self - .user_status_callback - .read() - .await - .receive_realtime_event(json); + if let Ok(user_service) = self.cloud_services.get_user_service() { + user_service.receive_realtime_event(json) + } } /// Returns the current user session. pub fn get_session(&self) -> Result { + if let Some(session) = (self.current_session.read()).clone() { + return Ok(session); + } + match self .store_preferences .get_object::(&self.session_config.session_cache_key) @@ -524,10 +514,33 @@ impl UserManager { ErrorCode::RecordNotFound, "User is not logged in", )), - Some(session) => Ok(session), + Some(session) => { + self.current_session.write().replace(session.clone()); + Ok(session) + }, } } + pub(crate) fn set_session(&self, session: Option) -> Result<(), FlowyError> { + tracing::debug!("Set current user: {:?}", session); + match &session { + None => { + self.current_session.write().take(); + self + .store_preferences + .remove(&self.session_config.session_cache_key) + }, + Some(session) => { + self.current_session.write().replace(session.clone()); + self + .store_preferences + .set_object(&self.session_config.session_cache_key, session.clone()) + .map_err(internal_error)?; + }, + } + Ok(()) + } + async fn save_auth_data( &self, response: &impl UserAuthResponse, @@ -547,7 +560,7 @@ impl UserManager { self .save_user(uid, (user_profile, auth_type.clone()).into()) .await?; - self.set_current_session(Some(session.clone()))?; + self.set_session(Some(session.clone()))?; Ok(()) } @@ -558,6 +571,27 @@ impl UserManager { self.cloud_services.set_device_id(&session.device_id); } + async fn handler_user_update(&self, user_update: UserUpdate) -> FlowyResult<()> { + let session = self.get_session()?; + if session.user_id == user_update.uid { + tracing::debug!("Receive user update: {:?}", user_update); + let user_profile = self.get_user_profile(user_update.uid).await?; + + if !is_user_encryption_sign_valid(&user_profile, &user_update.encryption_sign) { + return Ok(()); + } + + // Save the user profile change + save_user_profile_change( + user_update.uid, + self.db_pool(user_update.uid)?, + UserTableChangeset::from(user_update), + )?; + } + + Ok(()) + } + async fn migrate_local_user_to_cloud( &self, old_user: &MigrationUser, @@ -575,3 +609,33 @@ impl UserManager { Ok(folder_data) } } + +fn is_user_encryption_sign_valid(user_profile: &UserProfile, encryption_sign: &str) -> bool { + // If the local user profile's encryption sign is not equal to the user update's encryption sign, + // which means the user enable encryption in another device, we should logout the current user. + let is_valid = user_profile.encryption_type.sign() == encryption_sign; + if !is_valid { + send_auth_state_notification(AuthStateChangedPB { + state: AuthStatePB::AuthStateForceSignOut, + }) + .send(); + } + is_valid +} + +fn save_user_profile_change( + uid: i64, + pool: Arc, + changeset: UserTableChangeset, +) -> FlowyResult<()> { + let conn = pool.get()?; + diesel_update_table!(user_table, changeset, &*conn); + let user: UserProfile = user_table::dsl::user_table + .filter(user_table::id.eq(&uid.to_string())) + .first::(&*conn)? + .into(); + send_notification(&uid.to_string(), UserNotification::DidUpdateUserProfile) + .payload(UserProfilePB::from(user)) + .send(); + Ok(()) +} diff --git a/frontend/rust-lib/flowy-user/src/notification.rs b/frontend/rust-lib/flowy-user/src/notification.rs index 675f60384d..38083ea197 100644 --- a/frontend/rust-lib/flowy-user/src/notification.rs +++ b/frontend/rust-lib/flowy-user/src/notification.rs @@ -1,13 +1,15 @@ use flowy_derive::ProtoBuf_Enum; use flowy_notification::NotificationBuilder; +use crate::entities::AuthStateChangedPB; + const USER_OBSERVABLE_SOURCE: &str = "User"; #[derive(ProtoBuf_Enum, Debug, Default)] pub(crate) enum UserNotification { #[default] Unknown = 0, - DidUserSignIn = 1, + UserAuthStateChanged = 1, DidUpdateUserProfile = 2, DidUpdateUserWorkspaces = 3, DidUpdateCloudConfig = 4, @@ -23,6 +25,11 @@ pub(crate) fn send_notification(id: &str, ty: UserNotification) -> NotificationB NotificationBuilder::new(id, ty, USER_OBSERVABLE_SOURCE) } -pub(crate) fn send_sign_in_notification() -> NotificationBuilder { - NotificationBuilder::new("", UserNotification::DidUserSignIn, USER_OBSERVABLE_SOURCE) +pub(crate) fn send_auth_state_notification(payload: AuthStateChangedPB) -> NotificationBuilder { + NotificationBuilder::new( + "auth_state_change_notification", + UserNotification::UserAuthStateChanged, + USER_OBSERVABLE_SOURCE, + ) + .payload(payload) } diff --git a/frontend/rust-lib/flowy-user/src/services/historical_user.rs b/frontend/rust-lib/flowy-user/src/services/historical_user.rs index c40c6dca55..4e40ae5467 100644 --- a/frontend/rust-lib/flowy-user/src/services/historical_user.rs +++ b/frontend/rust-lib/flowy-user/src/services/historical_user.rs @@ -17,7 +17,7 @@ impl UserManager { // Only migrate the data if the user is login in as a guest and sign up as a new user if the current // auth type is not [AuthType::Local]. let session = self.get_session().ok()?; - let user_profile = self.get_user_profile(session.user_id, false).await.ok()?; + let user_profile = self.get_user_profile(session.user_id).await.ok()?; if user_profile.auth_type == AuthType::Local && !auth_type.is_local() { Some(MigrationUser { user_profile, @@ -100,7 +100,7 @@ impl UserManager { device_id, user_workspace, }; - self.set_current_session(Some(session))?; + self.set_session(Some(session))?; Ok(()) } } diff --git a/frontend/rust-lib/flowy-user/src/services/user_sql.rs b/frontend/rust-lib/flowy-user/src/services/user_sql.rs index c99228ac72..b5ae98c222 100644 --- a/frontend/rust-lib/flowy-user/src/services/user_sql.rs +++ b/frontend/rust-lib/flowy-user/src/services/user_sql.rs @@ -1,6 +1,7 @@ use std::str::FromStr; use flowy_sqlite::schema::user_table; +use flowy_user_deps::cloud::UserUpdate; use flowy_user_deps::entities::*; /// The order of the fields in the struct must be the same as the order of the fields in the table. @@ -102,3 +103,14 @@ impl UserTableChangeset { } } } + +impl From for UserTableChangeset { + fn from(value: UserUpdate) -> Self { + UserTableChangeset { + id: value.uid.to_string(), + name: Some(value.name), + email: Some(value.email), + ..Default::default() + } + } +}