| 
									
										
										
										
											2023-12-08 20:01:54 +07:00
										 |  |  | import 'dart:async'; | 
					
						
							| 
									
										
										
										
											2023-11-25 01:18:31 -08:00
										 |  |  | import 'dart:io'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-08 20:01:54 +07:00
										 |  |  | import 'package:app_links/app_links.dart'; | 
					
						
							| 
									
										
										
										
											2023-11-25 01:18:31 -08:00
										 |  |  | import 'package:appflowy/env/cloud_env.dart'; | 
					
						
							| 
									
										
										
										
											2023-10-12 20:25:00 +08:00
										 |  |  | import 'package:appflowy/startup/startup.dart'; | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  | import 'package:appflowy/startup/tasks/app_widget.dart'; | 
					
						
							| 
									
										
										
										
											2023-11-25 01:18:31 -08:00
										 |  |  | import 'package:appflowy/startup/tasks/supabase_task.dart'; | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  | import 'package:appflowy/user/application/auth/auth_error.dart'; | 
					
						
							| 
									
										
										
										
											2023-12-08 20:01:54 +07:00
										 |  |  | import 'package:appflowy/user/application/auth/auth_service.dart'; | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  | import 'package:appflowy/user/application/auth/device_id.dart'; | 
					
						
							| 
									
										
										
										
											2023-10-12 20:25:00 +08:00
										 |  |  | import 'package:appflowy/user/application/user_auth_listener.dart'; | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  | import 'package:appflowy/workspace/presentation/home/toast.dart'; | 
					
						
							|  |  |  | import 'package:appflowy_backend/dispatch/dispatch.dart'; | 
					
						
							| 
									
										
										
										
											2023-12-08 20:01:54 +07:00
										 |  |  | import 'package:appflowy_backend/log.dart'; | 
					
						
							| 
									
										
										
										
											2023-12-21 08:12:40 +08:00
										 |  |  | import 'package:appflowy_backend/protobuf/flowy-error/code.pb.dart'; | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  | import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; | 
					
						
							|  |  |  | import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'; | 
					
						
							|  |  |  | import 'package:dartz/dartz.dart'; | 
					
						
							| 
									
										
										
										
											2023-12-08 20:01:54 +07:00
										 |  |  | import 'package:flutter/material.dart'; | 
					
						
							|  |  |  | import 'package:url_protocol/url_protocol.dart'; | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | class AppFlowyCloudDeepLink { | 
					
						
							|  |  |  |   final _appLinks = AppLinks(); | 
					
						
							| 
									
										
										
										
											2023-12-21 08:12:40 +08:00
										 |  |  |   // The AppLinks is a singleton, so we need to cancel the previous subscription
 | 
					
						
							|  |  |  |   // before creating a new one.
 | 
					
						
							|  |  |  |   static StreamSubscription<Uri?>? _deeplinkSubscription; | 
					
						
							|  |  |  |   ValueNotifier<DeepLinkResult?>? _stateNotifier = ValueNotifier(null); | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  |   Completer<Either<FlowyError, UserProfilePB>>? _completer; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   AppFlowyCloudDeepLink() { | 
					
						
							| 
									
										
										
										
											2023-12-21 08:12:40 +08:00
										 |  |  |     if (_deeplinkSubscription == null) { | 
					
						
							|  |  |  |       _deeplinkSubscription = _appLinks.uriLinkStream.listen( | 
					
						
							|  |  |  |         (Uri? uri) async { | 
					
						
							|  |  |  |           Log.info('onDeepLink: ${uri.toString()}'); | 
					
						
							|  |  |  |           await _handleUri(uri); | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         onError: (Object err, StackTrace stackTrace) { | 
					
						
							|  |  |  |           Log.error('on deeplink stream error: ${err.toString()}', stackTrace); | 
					
						
							|  |  |  |           _deeplinkSubscription?.cancel(); | 
					
						
							|  |  |  |           _deeplinkSubscription = null; | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       if (Platform.isWindows) { | 
					
						
							|  |  |  |         // register deep link for Windows
 | 
					
						
							|  |  |  |         registerProtocolHandler(appflowyDeepLinkSchema); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       _deeplinkSubscription?.resume(); | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   Future<void> dispose() async { | 
					
						
							| 
									
										
										
										
											2023-12-21 08:12:40 +08:00
										 |  |  |     _deeplinkSubscription?.pause(); | 
					
						
							|  |  |  |     _stateNotifier?.dispose(); | 
					
						
							|  |  |  |     _stateNotifier = null; | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   void resigerCompleter( | 
					
						
							|  |  |  |     Completer<Either<FlowyError, UserProfilePB>> completer, | 
					
						
							|  |  |  |   ) { | 
					
						
							|  |  |  |     _completer = completer; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   VoidCallback subscribeDeepLinkLoadingState( | 
					
						
							|  |  |  |     ValueChanged<DeepLinkResult> listener, | 
					
						
							|  |  |  |   ) { | 
					
						
							| 
									
										
										
										
											2023-12-08 20:01:54 +07:00
										 |  |  |     void listenerFn() { | 
					
						
							| 
									
										
										
										
											2023-12-21 08:12:40 +08:00
										 |  |  |       if (_stateNotifier?.value != null) { | 
					
						
							|  |  |  |         listener(_stateNotifier!.value!); | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-21 08:12:40 +08:00
										 |  |  |     _stateNotifier?.addListener(listenerFn); | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  |     return listenerFn; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-21 08:12:40 +08:00
										 |  |  |   void unsubscribeDeepLinkLoadingState(VoidCallback listener) => | 
					
						
							|  |  |  |       _stateNotifier?.removeListener(listener); | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   Future<void> _handleUri( | 
					
						
							|  |  |  |     Uri? uri, | 
					
						
							|  |  |  |   ) async { | 
					
						
							| 
									
										
										
										
											2023-12-21 08:12:40 +08:00
										 |  |  |     _stateNotifier?.value = DeepLinkResult(state: DeepLinkState.none); | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  |     if (uri != null) { | 
					
						
							| 
									
										
										
										
											2023-12-21 08:12:40 +08:00
										 |  |  |       _isAuthCallbackDeeplink(uri).fold( | 
					
						
							|  |  |  |         (_) async { | 
					
						
							|  |  |  |           final deviceId = await getDeviceId(); | 
					
						
							|  |  |  |           final payload = OauthSignInPB( | 
					
						
							| 
									
										
										
										
											2023-12-27 11:42:39 +08:00
										 |  |  |             authenticator: AuthenticatorPB.AppFlowyCloud, | 
					
						
							| 
									
										
										
										
											2023-12-21 08:12:40 +08:00
										 |  |  |             map: { | 
					
						
							|  |  |  |               AuthServiceMapKeys.signInURL: uri.toString(), | 
					
						
							|  |  |  |               AuthServiceMapKeys.deviceId: deviceId, | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  |             }, | 
					
						
							|  |  |  |           ); | 
					
						
							| 
									
										
										
										
											2023-12-21 08:12:40 +08:00
										 |  |  |           _stateNotifier?.value = DeepLinkResult(state: DeepLinkState.loading); | 
					
						
							|  |  |  |           final result = await UserEventOauthSignIn(payload) | 
					
						
							|  |  |  |               .send() | 
					
						
							|  |  |  |               .then((value) => value.swap()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           _stateNotifier?.value = DeepLinkResult( | 
					
						
							|  |  |  |             state: DeepLinkState.finish, | 
					
						
							|  |  |  |             result: result, | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |           // If there is no completer, runAppFlowy() will be called.
 | 
					
						
							|  |  |  |           if (_completer == null) { | 
					
						
							|  |  |  |             result.fold( | 
					
						
							|  |  |  |               (err) { | 
					
						
							|  |  |  |                 Log.error(err); | 
					
						
							|  |  |  |                 final context = AppGlobals.rootNavKey.currentState?.context; | 
					
						
							|  |  |  |                 if (context != null) { | 
					
						
							|  |  |  |                   showSnackBarMessage( | 
					
						
							|  |  |  |                     context, | 
					
						
							|  |  |  |                     err.msg, | 
					
						
							|  |  |  |                   ); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |               }, | 
					
						
							|  |  |  |               (_) async { | 
					
						
							|  |  |  |                 await runAppFlowy(); | 
					
						
							|  |  |  |               }, | 
					
						
							|  |  |  |             ); | 
					
						
							|  |  |  |           } else { | 
					
						
							|  |  |  |             _completer?.complete(result); | 
					
						
							|  |  |  |             _completer = null; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         (err) { | 
					
						
							|  |  |  |           Log.error('onDeepLinkError: Unexpect deep link: $err'); | 
					
						
							|  |  |  |           if (_completer == null) { | 
					
						
							|  |  |  |             final context = AppGlobals.rootNavKey.currentState?.context; | 
					
						
							|  |  |  |             if (context != null) { | 
					
						
							|  |  |  |               showSnackBarMessage( | 
					
						
							|  |  |  |                 context, | 
					
						
							|  |  |  |                 err.msg, | 
					
						
							|  |  |  |               ); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           } else { | 
					
						
							|  |  |  |             _completer?.complete(left(err)); | 
					
						
							|  |  |  |             _completer = null; | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  |     } else { | 
					
						
							|  |  |  |       Log.error('onDeepLinkError: Unexpect empty deep link callback'); | 
					
						
							|  |  |  |       _completer?.complete(left(AuthError.emptyDeeplink)); | 
					
						
							|  |  |  |       _completer = null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-21 08:12:40 +08:00
										 |  |  |   Either<(), FlowyError> _isAuthCallbackDeeplink(Uri uri) { | 
					
						
							|  |  |  |     if (uri.fragment.contains('access_token')) { | 
					
						
							|  |  |  |       return left(()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return right( | 
					
						
							|  |  |  |       FlowyError.create() | 
					
						
							|  |  |  |         ..code = ErrorCode.MissingAuthField | 
					
						
							|  |  |  |         ..msg = uri.path, | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-10-12 20:25:00 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | class InitAppFlowyCloudTask extends LaunchTask { | 
					
						
							| 
									
										
										
										
											2023-10-24 23:13:51 +08:00
										 |  |  |   UserAuthStateListener? _authStateListener; | 
					
						
							| 
									
										
										
										
											2023-10-12 20:25:00 +08:00
										 |  |  |   bool isLoggingOut = false; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Future<void> initialize(LaunchContext context) async { | 
					
						
							|  |  |  |     if (!isAppFlowyCloudEnabled) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2023-10-24 23:13:51 +08:00
										 |  |  |     _authStateListener = UserAuthStateListener(); | 
					
						
							| 
									
										
										
										
											2023-10-12 20:25:00 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-24 23:13:51 +08:00
										 |  |  |     _authStateListener?.start( | 
					
						
							| 
									
										
										
										
											2023-10-12 20:25:00 +08:00
										 |  |  |       didSignIn: () { | 
					
						
							|  |  |  |         isLoggingOut = false; | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       onInvalidAuth: (message) async { | 
					
						
							| 
									
										
										
										
											2023-10-24 20:11:06 +08:00
										 |  |  |         Log.error(message); | 
					
						
							| 
									
										
										
										
											2023-10-12 20:25:00 +08:00
										 |  |  |         if (!isLoggingOut) { | 
					
						
							|  |  |  |           await runAppFlowy(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-10-24 23:13:51 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   @override | 
					
						
							|  |  |  |   Future<void> dispose() async { | 
					
						
							|  |  |  |     await _authStateListener?.stop(); | 
					
						
							|  |  |  |     _authStateListener = null; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2023-10-12 20:25:00 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2023-11-29 12:55:13 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  | class DeepLinkResult { | 
					
						
							|  |  |  |   final DeepLinkState state; | 
					
						
							|  |  |  |   final Either<FlowyError, UserProfilePB>? result; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   DeepLinkResult({required this.state, this.result}); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enum DeepLinkState { | 
					
						
							|  |  |  |   none, | 
					
						
							|  |  |  |   loading, | 
					
						
							|  |  |  |   finish, | 
					
						
							|  |  |  | } |