|
If the user cancels or closes the popup, I have tried this but still doesn't work. This is Dart/Flutter ///
Future<AuthResponse> facebookSignIn() async {
try {
await appDB.supabaseClient.auth.signInWithOAuth(
OAuthProvider.facebook,
redirectTo: AppPlatform.isWeb ? null : "${AppConstants.appDeepLink}/auth",
);
// Wait for session to change after redirect
final completer = Completer<AuthResponse>();
late final StreamSubscription<AuthState> sub;
sub = appDB.supabaseClient.auth.onAuthStateChange.listen((event) async {
if (completer.isCompleted) return;
if (event.session != null) {
await sub.cancel();
completer.complete(AuthResponse(
session: event.session,
user: event.session!.user
));
} else if (event.event == AuthChangeEvent.signedOut || event.event == AuthChangeEvent.tokenRefreshed) {
// User canceled or error occurred
await sub.cancel();
completer.completeError(AuthException("Facebook login failed or was cancelled"));
}
});
//
return completer.future.timeout(
Duration(seconds: 60),
onTimeout: () {
sub.cancel();
throw AuthException("Timed out waiting for Facebook login");
},
);
} catch (err) {
rethrow;
}
} |
Answered by
KiddoV
Nov 3, 2025
Replies: 3 comments 1 reply
|
The code is handling the successful sign-in or a sign-out event after the redirect has successfully occurred. However, it fails for a canceled popup because the external browser doesn't trigger any change in your app's |
0 replies
try this. rely on the future timeout to detect that the user took too long key is to manage the StreamSubscription correctly within the timeout logic. |
1 reply
|
I ended up making an extension. Not sure if it good but it works. ///
extension GoTrueClientEx on GoTrueClient {
/// Safe OAuth sign-in that waits for the user to complete the flow,
/// detects app return/cancel, and applies a timeout.
Future<AuthResponse> safeSignInWithOAuth(
OAuthProvider provider, {
String? redirectTo,
String? scopes,
LaunchMode authScreenLaunchMode = LaunchMode.platformDefault,
Map<String, String>? queryParams,
Duration timeout = const Duration(minutes: 3),
}) async {
final completer = Completer<AuthResponse>();
// Listen for auth state changes
await signOut(); // Clears all existing sessions - Preventing errors from previous login attempts from firing during a new OAuth flow
final sub = onAuthStateChange.listen(
(ev) {
final session = ev.session;
if (session != null && !completer.isCompleted) {
completer.complete(AuthResponse(session: session, user: session.user));
}
},
cancelOnError: true,
onError: (err) {
if (!completer.isCompleted) completer.completeError(AuthException("OAuth failed, $err"));
},
);
// Lifecycle watcher to detect user returning without completing login
final lifecycleWatcher = AppLifecycleListener(
onResume: () {
if (!completer.isCompleted) {
completer.completeError(AuthException("User returned without completing ${provider.name} sign-in"));
}
},
);
// Start the OAuth flow
try {
await signInWithOAuth(
provider,
redirectTo: redirectTo,
scopes: scopes,
authScreenLaunchMode: authScreenLaunchMode,
queryParams: queryParams,
);
// Wait for the auth session or timeout
final res = await completer.future.timeout(timeout, onTimeout: () {
if (!completer.isCompleted) throw AuthException("${provider.name} sign-in timed out or cancelled");
return completer.future;
});
// Successfully sign in
return res;
} catch (err) {
if (!completer.isCompleted) {
completer.completeError(AuthException("OAuth flow failed, $err"));
}
rethrow;
} finally {
// Cleanup
await sub.cancel();
lifecycleWatcher.dispose();
}
}
} |
0 replies
Answer selected by
KiddoV
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I ended up making an extension. Not sure if it good but it works.