test: add document sync test on appflowy cloud (#4163)

* test: add document sync test on appflowy cloud

* chore: add runner

* test: Stream has already been listened to.

* fix: using singleton subscription

* fix: using singleton subscription
This commit is contained in:
Nathan.fooo
2023-12-21 08:12:40 +08:00
committed by GitHub
parent f5a9f2bf4d
commit 6ecc3c9076
29 changed files with 560 additions and 381 deletions

View File

@ -49,7 +49,6 @@ class DependencyResolver {
_resolveUserDeps(getIt, mode);
_resolveHomeDeps(getIt);
_resolveFolderDeps(getIt);
_resolveDocDeps(getIt);
_resolveCommonService(getIt, mode);
}
}
@ -219,10 +218,3 @@ void _resolveFolderDeps(GetIt getIt) {
);
getIt.registerFactory<FavoriteBloc>(() => FavoriteBloc());
}
void _resolveDocDeps(GetIt getIt) {
// Doc
getIt.registerFactoryParam<DocumentBloc, ViewPB, void>(
(view, _) => DocumentBloc(view: view),
);
}

View File

@ -13,6 +13,7 @@ import 'package:appflowy/user/application/user_auth_listener.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:appflowy_backend/dispatch/dispatch.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-user/protobuf.dart';
import 'package:dartz/dartz.dart';
@ -21,30 +22,38 @@ import 'package:url_protocol/url_protocol.dart';
class AppFlowyCloudDeepLink {
final _appLinks = AppLinks();
StreamSubscription<Uri?>? _deeplinkSubscription;
final ValueNotifier<DeepLinkResult?> stateNotifier = ValueNotifier(null);
// 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);
Completer<Either<FlowyError, UserProfilePB>>? _completer;
AppFlowyCloudDeepLink() {
if (Platform.isWindows) {
// register deep link for Windows
registerProtocolHandler(appflowyDeepLinkSchema);
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();
}
_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();
},
);
}
Future<void> dispose() async {
await _deeplinkSubscription?.cancel();
_deeplinkSubscription?.pause();
_stateNotifier?.dispose();
_stateNotifier = null;
}
void resigerCompleter(
@ -57,71 +66,80 @@ class AppFlowyCloudDeepLink {
ValueChanged<DeepLinkResult> listener,
) {
void listenerFn() {
if (stateNotifier.value != null) {
listener(stateNotifier.value!);
if (_stateNotifier?.value != null) {
listener(_stateNotifier!.value!);
}
}
stateNotifier.addListener(listenerFn);
_stateNotifier?.addListener(listenerFn);
return listenerFn;
}
void unsubscribeDeepLinkLoadingState(
VoidCallback listener,
) {
stateNotifier.removeListener(listener);
}
void unsubscribeDeepLinkLoadingState(VoidCallback listener) =>
_stateNotifier?.removeListener(listener);
Future<void> _handleUri(
Uri? uri,
) async {
stateNotifier.value = DeepLinkResult(state: DeepLinkState.none);
_stateNotifier?.value = DeepLinkResult(state: DeepLinkState.none);
if (uri != null) {
if (_isAuthCallbackDeeplink(uri)) {
final deviceId = await getDeviceId();
final payload = OauthSignInPB(
authType: AuthTypePB.AFCloud,
map: {
AuthServiceMapKeys.signInURL: uri.toString(),
AuthServiceMapKeys.deviceId: deviceId,
},
);
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,
);
}
},
(err) async {
Log.error(err);
await runAppFlowy();
_isAuthCallbackDeeplink(uri).fold(
(_) async {
final deviceId = await getDeviceId();
final payload = OauthSignInPB(
authType: AuthTypePB.AFCloud,
map: {
AuthServiceMapKeys.signInURL: uri.toString(),
AuthServiceMapKeys.deviceId: deviceId,
},
);
} else {
_completer?.complete(result);
_completer = null;
}
} else {
Log.error('onDeepLinkError: Unexpect deep link: ${uri.toString()}');
_completer?.complete(left(AuthError.signInWithOauthError));
_completer = null;
}
_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;
}
},
);
} else {
Log.error('onDeepLinkError: Unexpect empty deep link callback');
_completer?.complete(left(AuthError.emptyDeeplink));
@ -129,8 +147,16 @@ class AppFlowyCloudDeepLink {
}
}
bool _isAuthCallbackDeeplink(Uri uri) {
return (uri.fragment.contains('access_token'));
Either<(), FlowyError> _isAuthCallbackDeeplink(Uri uri) {
if (uri.fragment.contains('access_token')) {
return left(());
}
return right(
FlowyError.create()
..code = ErrorCode.MissingAuthField
..msg = uri.path,
);
}
}