mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: realtime user event (#3241)
* feat: update user profile after receiving realtime user event * chore: logout if other deivce enable encyrption * test: fix test * chore: fix checkbox UI * chore: fix tauri build * chore: fix device id * chore: fix duplicate run appflowy
This commit is contained in:
parent
c5719be7ae
commit
a1647bee78
@ -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);
|
||||
}
|
||||
|
@ -41,7 +41,10 @@ class _CheckboxCardCellState extends State<CheckboxCardCell> {
|
||||
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,
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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<WelcomeBloc, UserProfilePB, void>(
|
||||
(user, _) => WelcomeBloc(
|
||||
userService: UserBackendService(userId: user.id),
|
||||
userWorkspaceListener: UserWorkspaceListener(userProfile: user),
|
||||
),
|
||||
);
|
||||
|
||||
@ -141,10 +139,6 @@ void _resolveFolderDeps(GetIt getIt) {
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<MenuUserBloc, UserProfilePB, void>(
|
||||
(user, _) => MenuUserBloc(user),
|
||||
);
|
||||
|
||||
//Settings
|
||||
getIt.registerFactoryParam<SettingsDialogBloc, UserProfilePB, void>(
|
||||
(user, _) => SettingsDialogBloc(user),
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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(
|
||||
|
@ -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<AuthState>? authStateSubscription;
|
||||
|
||||
SupbaseRealtimeService({required this.supabase}) {
|
||||
_subscribeAuthState();
|
||||
_subscribeTablesChanges();
|
||||
|
||||
_authStateListener.start(
|
||||
didSignIn: () {
|
||||
_subscribeTablesChanges();
|
||||
isLoggingOut = false;
|
||||
},
|
||||
onForceLogout: (message) async {
|
||||
await getIt<AuthService>().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<void> _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<ChannelFilter> 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<void> dispose() async {
|
||||
await _authStateListener.stop();
|
||||
await authStateSubscription?.cancel();
|
||||
await channel?.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
@ -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<SubscribeObject>? _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<void> stop() async {
|
||||
_userParser = null;
|
||||
await _subscription?.cancel();
|
||||
_onForceLogout = null;
|
||||
}
|
||||
|
||||
void _userNotificationCallback(
|
||||
user.UserNotification ty,
|
||||
Either<Uint8List, FlowyError> 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -18,7 +18,6 @@ typedef AuthNotifyValue = Either<Unit, FlowyError>;
|
||||
|
||||
class UserListener {
|
||||
StreamSubscription<SubscribeObject>? _subscription;
|
||||
PublishNotifier<AuthNotifyValue>? _authNotifier = PublishNotifier();
|
||||
PublishNotifier<UserProfileNotifyValue>? _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<List<WorkspacePB>, FlowyError>;
|
||||
typedef WorkspaceSettingNotifyValue = Either<WorkspaceSettingPB, FlowyError>;
|
||||
|
||||
class UserWorkspaceListener {
|
||||
PublishNotifier<AuthNotifyValue>? _authNotifier = PublishNotifier();
|
||||
PublishNotifier<WorkspaceListNotifyValue>? _workspacesChangedNotifier =
|
||||
PublishNotifier();
|
||||
PublishNotifier<WorkspaceSettingNotifyValue>? _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<Uint8List, FlowyError> result,
|
||||
) {
|
||||
switch (ty) {
|
||||
case FolderNotification.DidCreateWorkspace:
|
||||
case FolderNotification.DidUpdateWorkspaceSetting:
|
||||
result.fold(
|
||||
(payload) => _settingChangedNotifier?.value =
|
||||
@ -137,13 +113,8 @@ class UserWorkspaceListener {
|
||||
|
||||
Future<void> stop() async {
|
||||
await _listener?.stop();
|
||||
_workspacesChangedNotifier?.dispose();
|
||||
_workspacesChangedNotifier = null;
|
||||
|
||||
_settingChangedNotifier?.dispose();
|
||||
_settingChangedNotifier = null;
|
||||
|
||||
_authNotifier?.dispose();
|
||||
_authNotifier = null;
|
||||
}
|
||||
}
|
||||
|
@ -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<HomeEvent, HomeState> {
|
||||
final UserWorkspaceListener _listener;
|
||||
final UserWorkspaceListener _workspaceListener;
|
||||
|
||||
HomeBloc(
|
||||
UserProfilePB user,
|
||||
WorkspaceSettingPB workspaceSetting,
|
||||
) : _listener = UserWorkspaceListener(userProfile: user),
|
||||
) : _workspaceListener = UserWorkspaceListener(userProfile: user),
|
||||
super(HomeState.initial(workspaceSetting)) {
|
||||
on<HomeEvent>(
|
||||
(event, emit) async {
|
||||
@ -30,8 +27,7 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
}
|
||||
});
|
||||
|
||||
_listener.start(
|
||||
onAuthChanged: (result) => _authDidChanged(result),
|
||||
_workspaceListener.start(
|
||||
onSettingUpdated: (result) {
|
||||
result.fold(
|
||||
(setting) =>
|
||||
@ -56,9 +52,6 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
),
|
||||
);
|
||||
},
|
||||
unauthorized: (_Unauthorized value) {
|
||||
emit(state.copyWith(unauthorized: true));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -66,17 +59,9 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _listener.stop();
|
||||
await _workspaceListener.stop();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void _authDidChanged(Either<Unit, FlowyError> 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,
|
||||
);
|
||||
}
|
||||
|
@ -26,9 +26,6 @@ class MenuUserBloc extends Bloc<MenuUserEvent, MenuUserState> {
|
||||
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<MenuUserEvent, MenuUserState> {
|
||||
}
|
||||
|
||||
void _profileUpdated(Either<UserProfilePB, FlowyError> userProfileOrFailed) {
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
userProfileOrFailed.fold(
|
||||
(newUserProfile) =>
|
||||
add(MenuUserEvent.didReceiveUserProfile(newUserProfile)),
|
||||
(newUserProfile) => add(
|
||||
MenuUserEvent.didReceiveUserProfile(newUserProfile),
|
||||
),
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
|
||||
void _workspaceListUpdated(
|
||||
Either<List<WorkspacePB>, FlowyError> workspacesOrFailed,
|
||||
) {
|
||||
// Do nothing by now
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
|
@ -21,9 +21,8 @@ class SettingsUserViewBloc extends Bloc<SettingsUserEvent, SettingsUserState> {
|
||||
on<SettingsUserEvent>((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<SettingsUserEvent, SettingsUserState> {
|
||||
super.close();
|
||||
}
|
||||
|
||||
Future<void> _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<void> _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<UserProfilePB, FlowyError> userProfileOrFailed) {
|
||||
userProfileOrFailed.fold(
|
||||
(newUserProfile) {
|
||||
add(SettingsUserEvent.didReceiveUserProfile(newUserProfile));
|
||||
_loadHistoricalUsers();
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
|
@ -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<WelcomeEvent, WelcomeState> {
|
||||
final UserBackendService userService;
|
||||
final UserWorkspaceListener userWorkspaceListener;
|
||||
WelcomeBloc({required this.userService, required this.userWorkspaceListener})
|
||||
: super(WelcomeState.initial()) {
|
||||
WelcomeBloc({required this.userService}) : super(WelcomeState.initial()) {
|
||||
on<WelcomeEvent>(
|
||||
(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<WelcomeEvent, WelcomeState> {
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await userWorkspaceListener.stop();
|
||||
super.close();
|
||||
}
|
||||
|
||||
Future<void> _fetchWorkspaces(Emitter<WelcomeState> emit) async {
|
||||
final workspacesOrFailed = await userService.getWorkspaces();
|
||||
emit(
|
||||
|
@ -61,16 +61,6 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
child: Scaffold(
|
||||
body: MultiBlocListener(
|
||||
listeners: [
|
||||
BlocListener<HomeBloc, HomeState>(
|
||||
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<HomeBloc, HomeState>(
|
||||
listenWhen: (p, c) => p.latestView != c.latestView,
|
||||
listener: (context, state) {
|
||||
|
@ -26,7 +26,7 @@ class SidebarUser extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider<MenuUserBloc>(
|
||||
create: (context) => getIt<MenuUserBloc>(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<MenuUserBloc>().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<MenuUserBloc>().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<MenuUserBloc>().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<MenuUserBloc>().state.userProfile;
|
||||
Widget _buildSettingsButton(BuildContext context, MenuUserState state) {
|
||||
final userProfile = state.userProfile;
|
||||
return Tooltip(
|
||||
message: LocaleKeys.settings_menu_open.tr(),
|
||||
child: IconButton(
|
||||
|
628
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
628
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -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"
|
||||
|
@ -4,18 +4,11 @@ import { UserNotificationParser } from './parser';
|
||||
import { Ok, Result } from 'ts-results';
|
||||
|
||||
declare type OnUserProfileUpdate = (result: Result<UserProfilePB, FlowyError>) => void;
|
||||
declare type OnUserSignIn = (result: Result<UserProfilePB, FlowyError>) => void;
|
||||
|
||||
export class UserNotificationListener extends AFNotificationObserver<UserNotification> {
|
||||
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<UserNotific
|
||||
this.onProfileUpdate?.(result);
|
||||
}
|
||||
break;
|
||||
case UserNotification.DidUserSignIn:
|
||||
if (result.ok) {
|
||||
this.onUserSignIn?.(Ok(UserProfilePB.deserializeBinary(result.val)));
|
||||
} else {
|
||||
this.onUserSignIn?.(result);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -42,6 +28,5 @@ export class UserNotificationListener extends AFNotificationObserver<UserNotific
|
||||
});
|
||||
super(parser);
|
||||
this.onProfileUpdate = params.onProfileUpdate;
|
||||
this.onUserSignIn = params.onUserSignIn;
|
||||
}
|
||||
}
|
||||
|
1
frontend/rust-lib/Cargo.lock
generated
1
frontend/rust-lib/Cargo.lock
generated
@ -1791,6 +1791,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_repr",
|
||||
"tokio",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
@ -4,8 +4,7 @@ use std::sync::{Arc, Weak};
|
||||
|
||||
use appflowy_integrate::collab_builder::{CollabStorageProvider, CollabStorageType};
|
||||
use appflowy_integrate::{CollabObject, CollabType, RemoteCollabStorage, YrsDocAction};
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use serde_json::Value;
|
||||
use parking_lot::RwLock;
|
||||
use serde_repr::*;
|
||||
|
||||
use flowy_database_deps::cloud::*;
|
||||
@ -64,11 +63,12 @@ impl Display for ServerProviderType {
|
||||
pub struct AppFlowyServerProvider {
|
||||
config: AppFlowyCoreConfig,
|
||||
provider_type: RwLock<ServerProviderType>,
|
||||
device_id: Mutex<String>,
|
||||
device_id: Arc<RwLock<String>>,
|
||||
providers: RwLock<HashMap<ServerProviderType, Arc<dyn AppFlowyServer>>>,
|
||||
enable_sync: RwLock<bool>,
|
||||
encryption: RwLock<Arc<dyn AppFlowyEncryption>>,
|
||||
store_preferences: Weak<StorePreferences>,
|
||||
cache_user_service: RwLock<HashMap<ServerProviderType, Arc<dyn UserService>>>,
|
||||
}
|
||||
|
||||
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::<Arc<dyn AppFlowyServer>, 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<Arc<dyn UserService>, 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 {
|
||||
|
@ -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<AppFlowyCollabBuilder>,
|
||||
) -> Arc<UserManager> {
|
||||
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<ServerProviderType> for CollabStorageType {
|
||||
|
@ -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<dyn UserService>;
|
||||
fn folder_service(&self) -> Arc<dyn FolderCloudService>;
|
||||
fn database_service(&self) -> Arc<dyn DatabaseCloudService>;
|
||||
fn document_service(&self) -> Arc<dyn DocumentCloudService>;
|
||||
fn collab_storage(&self, collab_object: &CollabObject) -> Option<Arc<dyn RemoteCollabStorage>>;
|
||||
fn handle_realtime_event(&self, _json: Value) {}
|
||||
}
|
||||
|
||||
pub struct EncryptionImpl {
|
||||
|
@ -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);
|
||||
|
@ -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<T> {
|
||||
server: T,
|
||||
realtime_event_handlers: Vec<Box<dyn RealtimeEventHandler>>,
|
||||
user_update_tx: Option<UserUpdateSender>,
|
||||
}
|
||||
|
||||
impl<T> SupabaseUserServiceImpl<T> {
|
||||
pub fn new(server: T) -> Self {
|
||||
Self { server }
|
||||
pub fn new(
|
||||
server: T,
|
||||
realtime_event_handlers: Vec<Box<dyn RealtimeEventHandler>>,
|
||||
user_update_tx: Option<UserUpdateSender>,
|
||||
) -> 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::<RealtimeEvent>(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<UserUpdateReceiver> {
|
||||
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::<RealtimeUserEvent>(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<CollabUpdateSenderByOid>,
|
||||
device_id: Arc<RwLock<String>>,
|
||||
encryption: Weak<dyn AppFlowyEncryption>,
|
||||
}
|
||||
|
||||
impl RealtimeCollabUpdateHandler {
|
||||
pub fn new(
|
||||
sender_by_oid: Weak<CollabUpdateSenderByOid>,
|
||||
device_id: Arc<RwLock<String>>,
|
||||
encryption: Weak<dyn AppFlowyEncryption>,
|
||||
) -> 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::<RealtimeCollabUpdateEvent>(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);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<T: AsRef<str>>(
|
||||
pub fn decode<T: AsRef<str>, D: HexDecoder>(
|
||||
value: T,
|
||||
encrypt: i32,
|
||||
encryption_secret: &Option<String>,
|
||||
@ -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<T: AsRef<[u8]>>(data: T) -> Result<Vec<u8>, Error>;
|
||||
}
|
||||
|
||||
impl SupabaseRealtimeEventBinaryColumnDecoder {
|
||||
/// The realtime event binary column string is encoded twice. So it needs to be decoded twice.
|
||||
pub fn decode<T: AsRef<str>>(value: T) -> Option<Vec<u8>> {
|
||||
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<T: AsRef<[u8]>>(data: T) -> Result<Vec<u8>, 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<T: AsRef<[u8]>>(data: T) -> Result<Vec<u8>, Error> {
|
||||
let bytes = hex::decode(data)?;
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
@ -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<u8>,
|
||||
pub value: String,
|
||||
#[serde(default)]
|
||||
pub encrypt: i32,
|
||||
}
|
||||
|
||||
pub fn deserialize_value<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
struct ValueVisitor();
|
||||
|
||||
impl<'de> Visitor<'de> for ValueVisitor {
|
||||
type Value = Vec<u8>;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("Expect NodeBody")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
Ok(SupabaseRealtimeEventBinaryColumnDecoder::decode(v).unwrap_or_default())
|
||||
}
|
||||
|
||||
fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
|
||||
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,
|
||||
}
|
||||
|
@ -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<HashMap<String, RemoteUpdateSender>>;
|
||||
/// 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<String>,
|
||||
update_tx: RwLock<HashMap<String, RemoteUpdateSender>>,
|
||||
device_id: Arc<RwLock<String>>,
|
||||
collab_update_sender: Arc<CollabUpdateSenderByOid>,
|
||||
restful_postgres: Arc<RwLock<Option<Arc<RESTfulPostgresServer>>>>,
|
||||
encryption: Weak<dyn AppFlowyEncryption>,
|
||||
}
|
||||
@ -69,9 +68,10 @@ impl SupabaseServer {
|
||||
pub fn new(
|
||||
config: SupabaseConfiguration,
|
||||
enable_sync: bool,
|
||||
device_id: Arc<RwLock<String>>,
|
||||
encryption: Weak<dyn AppFlowyEncryption>,
|
||||
) -> 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<dyn UserService> {
|
||||
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<Box<dyn RealtimeEventHandler>> = 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<dyn FolderCloudService> {
|
||||
@ -139,53 +150,14 @@ impl AppFlowyServer for SupabaseServer {
|
||||
fn collab_storage(&self, collab_object: &CollabObject) -> Option<Arc<dyn RemoteCollabStorage>> {
|
||||
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::<RealtimeCollabUpdateEvent>(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);
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ pub fn database_service() -> Arc<dyn DatabaseCloudService> {
|
||||
|
||||
pub fn user_auth_service() -> Arc<dyn UserService> {
|
||||
let (server, _encryption_impl) = appflowy_server(None);
|
||||
Arc::new(SupabaseUserServiceImpl::new(server))
|
||||
Arc::new(SupabaseUserServiceImpl::new(server, vec![], None))
|
||||
}
|
||||
|
||||
pub fn folder_service() -> Arc<dyn FolderCloudService> {
|
||||
|
@ -112,7 +112,7 @@ pub fn database_service() -> Arc<dyn DatabaseCloudService> {
|
||||
|
||||
pub fn user_auth_service() -> Arc<dyn UserService> {
|
||||
let (server, _encryption_impl) = appflowy_server(None);
|
||||
Arc::new(SupabaseUserServiceImpl::new(server))
|
||||
Arc::new(SupabaseUserServiceImpl::new(server, vec![], None))
|
||||
}
|
||||
|
||||
pub fn folder_service() -> Arc<dyn FolderCloudService> {
|
||||
|
@ -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"] }
|
||||
|
@ -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<Vec<Vec<u8>>, Error>;
|
||||
|
||||
fn receive_realtime_event(&self, _json: Value) {}
|
||||
|
||||
fn subscribe_user_update(&self) -> Option<UserUpdateReceiver> {
|
||||
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<UserUpdate>;
|
||||
pub type UserUpdateSender = tokio::sync::broadcast::Sender<UserUpdate>;
|
||||
#[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<ThirdPartyParams, Error> {
|
||||
let map: HashMap<String, String> = any.unbox_or_error()?;
|
||||
let uuid = uuid_from_map(&map)?;
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -95,8 +95,9 @@ pub async fn get_user_profile_handler(
|
||||
) -> DataResult<UserProfilePB, FlowyError> {
|
||||
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<UserEncryptionSecretCheckPB, FlowyError> {
|
||||
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,
|
||||
|
@ -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<FlowyResult<()>>;
|
||||
fn open_workspace(&self, user_id: i64, user_workspace: &UserWorkspace) -> Fut<FlowyResult<()>>;
|
||||
fn did_update_network(&self, _reachable: bool) {}
|
||||
fn receive_realtime_event(&self, _json: Value) {}
|
||||
}
|
||||
|
||||
/// The user cloud service provider.
|
||||
|
@ -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<Arc<dyn UserStatusCallback>>,
|
||||
pub(crate) collab_builder: Weak<AppFlowyCollabBuilder>,
|
||||
resumable_sign_up: Mutex<Option<ResumableSignUp>>,
|
||||
current_session: parking_lot::RwLock<Option<Session>>,
|
||||
}
|
||||
|
||||
impl UserManager {
|
||||
@ -69,11 +71,12 @@ impl UserManager {
|
||||
cloud_services: Arc<dyn UserCloudServiceProvider>,
|
||||
store_preferences: Arc<StorePreferences>,
|
||||
collab_builder: Weak<AppFlowyCollabBuilder>,
|
||||
) -> Self {
|
||||
) -> Arc<Self> {
|
||||
let database = UserDB::new(&session_config.root_dir);
|
||||
let user_status_callback: RwLock<Arc<dyn UserStatusCallback>> =
|
||||
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<StorePreferences> {
|
||||
@ -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::<UserProfilePB>(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<UserProfile, FlowyError> {
|
||||
let user_id = uid.to_string();
|
||||
let user = user_table::dsl::user_table
|
||||
.filter(user_table::id.eq(&user_id))
|
||||
.first::<UserTable>(&*(self.db_connection(uid)?))?;
|
||||
pub async fn get_user_profile(&self, uid: i64) -> Result<UserProfile, FlowyError> {
|
||||
let user: UserProfile = user_table::dsl::user_table
|
||||
.filter(user_table::id.eq(&uid.to_string()))
|
||||
.first::<UserTable>(&*(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<UserProfile> {
|
||||
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<String>,
|
||||
params: UpdateUserProfileParams,
|
||||
@ -490,32 +494,18 @@ impl UserManager {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn set_current_session(&self, session: Option<Session>) -> 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<Session, FlowyError> {
|
||||
if let Some(session) = (self.current_session.read()).clone() {
|
||||
return Ok(session);
|
||||
}
|
||||
|
||||
match self
|
||||
.store_preferences
|
||||
.get_object::<Session>(&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<Session>) -> 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<ConnectionPool>,
|
||||
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::<UserTable>(&*conn)?
|
||||
.into();
|
||||
send_notification(&uid.to_string(), UserNotification::DidUpdateUserProfile)
|
||||
.payload(UserProfilePB::from(user))
|
||||
.send();
|
||||
Ok(())
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
@ -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<UserUpdate> for UserTableChangeset {
|
||||
fn from(value: UserUpdate) -> Self {
|
||||
UserTableChangeset {
|
||||
id: value.uid.to_string(),
|
||||
name: Some(value.name),
|
||||
email: Some(value.email),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user