-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
refactor: deeplink handler #7802
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
ea478d5
refactor: deeplink handler
LucasXu0 a64cfce
Merge branch 'main' into workspace_deeplink
LucasXu0 e6e25a7
feat: add invitation deeplink handler
LucasXu0 4c55a55
test: add deeplink tests
LucasXu0 c8e2811
feat: add user_id in the invitation callback
LucasXu0 da57f79
feat: add expire login deeplink handler
LucasXu0 953c3fd
feat: add loading indicator in continue with password page
LucasXu0 7e549ad
feat: replace user_id with email
LucasXu0 4f2de1c
fix: invitation deeplink test
LucasXu0 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
94 changes: 94 additions & 0 deletions
94
frontend/appflowy_flutter/lib/startup/tasks/deeplink/deeplink_handler.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import 'dart:async'; | ||
|
||
import 'package:appflowy_backend/log.dart'; | ||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; | ||
import 'package:appflowy_result/appflowy_result.dart'; | ||
|
||
typedef DeepLinkResultHandler = void Function( | ||
DeepLinkHandler handler, | ||
FlowyResult<dynamic, FlowyError> result, | ||
); | ||
|
||
typedef DeepLinkStateHandler = void Function( | ||
DeepLinkHandler handler, | ||
DeepLinkState state, | ||
); | ||
|
||
typedef DeepLinkErrorHandler = void Function( | ||
FlowyError error, | ||
); | ||
|
||
abstract class DeepLinkHandler<T> { | ||
/// Checks if this handler should handle the given URI | ||
bool canHandle(Uri uri); | ||
|
||
/// Handles the deep link URI | ||
|
||
Future<FlowyResult<T, FlowyError>> handle({ | ||
required Uri uri, | ||
required DeepLinkStateHandler onStateChange, | ||
}); | ||
} | ||
|
||
class DeepLinkHandlerRegistry { | ||
DeepLinkHandlerRegistry._(); | ||
static final instance = DeepLinkHandlerRegistry._(); | ||
|
||
final List<DeepLinkHandler> _handlers = []; | ||
|
||
/// Register a new DeepLink handler | ||
void register(DeepLinkHandler handler) { | ||
_handlers.add(handler); | ||
} | ||
|
||
Future<void> processDeepLink({ | ||
required Uri uri, | ||
required DeepLinkStateHandler onStateChange, | ||
required DeepLinkResultHandler onResult, | ||
required DeepLinkErrorHandler onError, | ||
}) async { | ||
Log.info('Processing DeepLink: ${uri.toString()}'); | ||
|
||
bool handled = false; | ||
|
||
for (final handler in _handlers) { | ||
if (handler.canHandle(uri)) { | ||
Log.info('Handler ${handler.runtimeType} will handle the DeepLink'); | ||
|
||
final result = await handler.handle( | ||
uri: uri, | ||
onStateChange: onStateChange, | ||
); | ||
|
||
onResult(handler, result); | ||
|
||
handled = true; | ||
break; | ||
} | ||
} | ||
|
||
if (!handled) { | ||
Log.error('No handler found for DeepLink: ${uri.toString()}'); | ||
|
||
onError( | ||
FlowyError(msg: 'No handler found for DeepLink: ${uri.toString()}'), | ||
); | ||
} | ||
} | ||
} | ||
|
||
class DeepLinkResult<T> { | ||
DeepLinkResult({ | ||
required this.state, | ||
this.result, | ||
}); | ||
final DeepLinkState state; | ||
final FlowyResult<T, FlowyError>? result; | ||
} | ||
|
||
enum DeepLinkState { | ||
none, | ||
loading, | ||
finish, | ||
error, | ||
} |
31 changes: 31 additions & 0 deletions
31
frontend/appflowy_flutter/lib/startup/tasks/deeplink/expire_login_deeplink_handler.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import 'dart:async'; | ||
|
||
import 'package:appflowy/startup/tasks/deeplink/deeplink_handler.dart'; | ||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; | ||
import 'package:appflowy_result/appflowy_result.dart'; | ||
|
||
/// Expire login deeplink example: | ||
/// appflowy-flutter:%23error=access_denied&error_code=403&error_description=Email+link+is+invalid+or+has+expired | ||
class ExpireLoginDeepLinkHandler extends DeepLinkHandler<void> { | ||
@override | ||
bool canHandle(Uri uri) { | ||
final isExpireLogin = uri.toString().contains('error=access_denied'); | ||
if (!isExpireLogin) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
@override | ||
Future<FlowyResult<void, FlowyError>> handle({ | ||
required Uri uri, | ||
required DeepLinkStateHandler onStateChange, | ||
}) async { | ||
return FlowyResult.failure( | ||
FlowyError( | ||
msg: 'Magic link is invalid or has expired', | ||
), | ||
); | ||
} | ||
} |
67 changes: 67 additions & 0 deletions
67
frontend/appflowy_flutter/lib/startup/tasks/deeplink/invitation_deeplink_handler.dart
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
import 'dart:async'; | ||
|
||
import 'package:appflowy/startup/tasks/deeplink/deeplink_handler.dart'; | ||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/workspace_notifier.dart'; | ||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; | ||
import 'package:appflowy_result/appflowy_result.dart'; | ||
|
||
// invitation callback deeplink example: | ||
// appflowy-flutter://invitation-callback?workspace_id=b2d11122-1fc8-474d-9ef1-ec12fea7ffe8&user_id=275966408418922496 | ||
class InvitationDeepLinkHandler extends DeepLinkHandler<void> { | ||
static const invitationCallbackHost = 'invitation-callback'; | ||
static const invitationCallbackWorkspaceId = 'workspace_id'; | ||
static const invitationCallbackEmail = 'email'; | ||
|
||
@override | ||
bool canHandle(Uri uri) { | ||
final isInvitationCallback = uri.host == invitationCallbackHost; | ||
if (!isInvitationCallback) { | ||
return false; | ||
} | ||
|
||
final containsWorkspaceId = | ||
uri.queryParameters.containsKey(invitationCallbackWorkspaceId); | ||
if (!containsWorkspaceId) { | ||
return false; | ||
} | ||
|
||
final containsEmail = | ||
uri.queryParameters.containsKey(invitationCallbackEmail); | ||
if (!containsEmail) { | ||
return false; | ||
} | ||
|
||
return true; | ||
} | ||
|
||
@override | ||
Future<FlowyResult<void, FlowyError>> handle({ | ||
required Uri uri, | ||
required DeepLinkStateHandler onStateChange, | ||
}) async { | ||
final workspaceId = uri.queryParameters[invitationCallbackWorkspaceId]; | ||
final email = uri.queryParameters[invitationCallbackEmail]; | ||
if (workspaceId == null) { | ||
return FlowyResult.failure( | ||
FlowyError( | ||
msg: 'Workspace ID is required', | ||
), | ||
); | ||
} | ||
|
||
if (email == null) { | ||
return FlowyResult.failure( | ||
FlowyError( | ||
msg: 'Email is required', | ||
), | ||
); | ||
} | ||
|
||
openWorkspaceNotifier.value = WorkspaceNotifyValue( | ||
workspaceId: workspaceId, | ||
email: email, | ||
); | ||
|
||
return FlowyResult.success(null); | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.