From 0ae2b7149286d4719bbaafdf1ecb3d76c8b4eb7a Mon Sep 17 00:00:00 2001 From: Vinzent Date: Thu, 21 Nov 2024 21:38:31 +0100 Subject: [PATCH] refactor: store session and pkce in the same storage in gotrue_client --- packages/gotrue/lib/src/gotrue_client.dart | 136 +++++++++++++----- .../lib/src/types/gotrue_async_storage.dart | 3 + .../supabase/lib/src/supabase_client.dart | 21 ++- .../lib/src/supabase_client_options.dart | 9 ++ .../src/flutter_go_true_client_options.dart | 13 ++ .../lib/src/local_storage.dart | 49 +++++++ .../supabase_flutter/lib/src/supabase.dart | 31 ++-- .../lib/src/supabase_auth.dart | 63 -------- 8 files changed, 201 insertions(+), 124 deletions(-) diff --git a/packages/gotrue/lib/src/gotrue_client.dart b/packages/gotrue/lib/src/gotrue_client.dart index 63f5c85f..b93db808 100644 --- a/packages/gotrue/lib/src/gotrue_client.dart +++ b/packages/gotrue/lib/src/gotrue_client.dart @@ -87,8 +87,14 @@ class GoTrueClient { Stream get onAuthStateChangeSync => _onAuthStateChangeControllerSync.stream; + final Completer _initalizedStorage = Completer(); + final AuthFlowType _flowType; + final bool _persistSession; + + final String _storageKey; + final _log = Logger('supabase.auth'); /// Proxy to the web BroadcastChannel API. Should be null on non-web platforms. @@ -101,8 +107,10 @@ class GoTrueClient { String? url, Map? headers, bool? autoRefreshToken, + bool? persistSession, Client? httpClient, GotrueAsyncStorage? asyncStorage, + String? storageKey, AuthFlowType flowType = AuthFlowType.pkce, }) : _url = url ?? Constants.defaultGotrueUrl, _headers = { @@ -111,7 +119,9 @@ class GoTrueClient { }, _httpClient = httpClient, _asyncStorage = asyncStorage, - _flowType = flowType { + _flowType = flowType, + _persistSession = persistSession ?? false, + _storageKey = storageKey ?? Constants.defaultStorageKey { _autoRefreshToken = autoRefreshToken ?? true; final gotrueUrl = url ?? Constants.defaultGotrueUrl; @@ -127,10 +137,19 @@ class GoTrueClient { client: this, fetch: _fetch, ); + + assert(asyncStorage != null || !_persistSession, + 'You need to provide asyncStorage to persist session.'); + if (asyncStorage != null) { + _initalizedStorage.complete( + asyncStorage.initialize().catchError((e) => notifyException(e))); + } + if (_autoRefreshToken) { startAutoRefresh(); } + _initialize(); _mayStartBroadcastChannel(); } @@ -148,6 +167,37 @@ class GoTrueClient { /// Returns the current session, if any; Session? get currentSession => _currentSession; + /// This method should not throw as it is called from the constructor. + Future _initialize() async { + try { + if (_persistSession && _asyncStorage != null) { + await _initalizedStorage.future; + final jsonStr = await _asyncStorage!.getItem(key: _storageKey); + var shouldEmitInitialSession = true; + if (jsonStr != null) { + await setInitialSession(jsonStr); + shouldEmitInitialSession = false; + + // Only try to recover session if the session got set in [setInitialSession] + // because if not the session is missing data and already notified an + // exception. + if (currentSession != null) { + // [notifyException] gets already called here if needed, so we can + // catch any error. + recoverSession(jsonStr).then((_) {}, onError: (_) {}); + } + } + if (shouldEmitInitialSession) { + // Emit a null session if the user did not have persisted session + notifyAllSubscribers(AuthChangeEvent.initialSession); + } + } + } catch (error, stackTrace) { + _log.warning('Error while loading initial session', error, stackTrace); + notifyException(error, stackTrace); + } + } + /// Creates a new anonymous user. /// /// Returns An `AuthResponse` with a session where the `is_anonymous` claim @@ -172,7 +222,7 @@ class GoTrueClient { final session = authResponse.session; if (session != null) { - _saveSession(session); + await _saveSession(session); notifyAllSubscribers(AuthChangeEvent.signedIn); } @@ -217,9 +267,8 @@ class GoTrueClient { assert(_asyncStorage != null, 'You need to provide asyncStorage to perform pkce flow.'); final codeVerifier = generatePKCEVerifier(); - await _asyncStorage!.setItem( - key: '${Constants.defaultStorageKey}-code-verifier', - value: codeVerifier); + await _asyncStorage! + .setItem(key: '$_storageKey-code-verifier', value: codeVerifier); codeChallenge = generatePKCEChallenge(codeVerifier); } @@ -259,7 +308,7 @@ class GoTrueClient { final session = authResponse.session; if (session != null) { - _saveSession(session); + await _saveSession(session); notifyAllSubscribers(AuthChangeEvent.signedIn); } @@ -312,7 +361,7 @@ class GoTrueClient { final authResponse = AuthResponse.fromJson(response); if (authResponse.session?.accessToken != null) { - _saveSession(authResponse.session!); + await _saveSession(authResponse.session!); notifyAllSubscribers(AuthChangeEvent.signedIn); } return authResponse; @@ -339,8 +388,8 @@ class GoTrueClient { assert(_asyncStorage != null, 'You need to provide asyncStorage to perform pkce flow.'); - final codeVerifierRawString = await _asyncStorage! - .getItem(key: '${Constants.defaultStorageKey}-code-verifier'); + final codeVerifierRawString = + await _asyncStorage!.getItem(key: '$_storageKey-code-verifier'); if (codeVerifierRawString == null) { throw AuthException('Code verifier could not be found in local storage.'); } @@ -363,14 +412,13 @@ class GoTrueClient { ), ); - await _asyncStorage! - .removeItem(key: '${Constants.defaultStorageKey}-code-verifier'); + await _asyncStorage!.removeItem(key: '$_storageKey-code-verifier'); final authSessionUrlResponse = AuthSessionUrlResponse( session: Session.fromJson(response)!, redirectType: redirectType?.name); final session = authSessionUrlResponse.session; - _saveSession(session); + await _saveSession(session); if (redirectType == AuthChangeEvent.passwordRecovery) { notifyAllSubscribers(AuthChangeEvent.passwordRecovery); } else { @@ -434,7 +482,7 @@ class GoTrueClient { ); } - _saveSession(authResponse.session!); + await _saveSession(authResponse.session!); notifyAllSubscribers(AuthChangeEvent.signedIn); return authResponse; @@ -472,9 +520,8 @@ class GoTrueClient { assert(_asyncStorage != null, 'You need to provide asyncStorage to perform pkce flow.'); final codeVerifier = generatePKCEVerifier(); - await _asyncStorage!.setItem( - key: '${Constants.defaultStorageKey}-code-verifier', - value: codeVerifier); + await _asyncStorage! + .setItem(key: '$_storageKey-code-verifier', value: codeVerifier); codeChallenge = generatePKCEChallenge(codeVerifier); } await _fetch.request( @@ -559,7 +606,7 @@ class GoTrueClient { ); } - _saveSession(authResponse.session!); + await _saveSession(authResponse.session!); notifyAllSubscribers(type == OtpType.recovery ? AuthChangeEvent.passwordRecovery : AuthChangeEvent.signedIn); @@ -594,9 +641,8 @@ class GoTrueClient { assert(_asyncStorage != null, 'You need to provide asyncStorage to perform pkce flow.'); final codeVerifier = generatePKCEVerifier(); - await _asyncStorage!.setItem( - key: '${Constants.defaultStorageKey}-code-verifier', - value: codeVerifier); + await _asyncStorage! + .setItem(key: '$_storageKey-code-verifier', value: codeVerifier); codeChallenge = generatePKCEChallenge(codeVerifier); codeChallengeMethod = codeVerifier == codeChallenge ? 'plain' : 's256'; } @@ -832,7 +878,7 @@ class GoTrueClient { final redirectType = url.queryParameters['type']; if (storeSession == true) { - _saveSession(session); + await _saveSession(session); if (redirectType == 'recovery') { notifyAllSubscribers(AuthChangeEvent.passwordRecovery); } else { @@ -855,9 +901,8 @@ class GoTrueClient { final accessToken = currentSession?.accessToken; if (scope != SignOutScope.others) { - _removeSession(); - await _asyncStorage?.removeItem( - key: '${Constants.defaultStorageKey}-code-verifier'); + await _removeSession(); + await _asyncStorage?.removeItem(key: '$_storageKey-code-verifier'); notifyAllSubscribers(AuthChangeEvent.signedOut); } @@ -889,7 +934,7 @@ class GoTrueClient { 'You need to provide asyncStorage to perform pkce flow.'); final codeVerifier = generatePKCEVerifier(); await _asyncStorage!.setItem( - key: '${Constants.defaultStorageKey}-code-verifier', + key: '$_storageKey-code-verifier', value: '$codeVerifier/${AuthChangeEvent.passwordRecovery.name}', ); codeChallenge = generatePKCEChallenge(codeVerifier); @@ -978,9 +1023,7 @@ class GoTrueClient { if (session == null) { _log.warning("Can't recover session from string, session is null"); await signOut(); - throw notifyException( - AuthException('Current session is missing data.'), - ); + throw AuthException('Session to restore is missing data.'); } if (session.isExpired) { @@ -995,7 +1038,7 @@ class GoTrueClient { } else { final shouldEmitEvent = _currentSession == null || _currentSession?.user.id != session.user.id; - _saveSession(session); + await _saveSession(session); if (shouldEmitEvent) { notifyAllSubscribers(AuthChangeEvent.tokenRefreshed); @@ -1126,7 +1169,7 @@ class GoTrueClient { 'You need to provide asyncStorage to perform pkce flow.'); final codeVerifier = generatePKCEVerifier(); await _asyncStorage!.setItem( - key: '${Constants.defaultStorageKey}-code-verifier', + key: '$_storageKey-code-verifier', value: codeVerifier, ); @@ -1146,17 +1189,36 @@ class GoTrueClient { } /// set currentSession and currentUser - void _saveSession(Session session) { + Future _saveSession(Session session) async { _log.finest('Saving session: $session'); _log.fine('Saving session'); _currentSession = session; _currentUser = session.user; + + if (_persistSession && _asyncStorage != null) { + if (!_initalizedStorage.isCompleted) { + await _initalizedStorage.future; + } + _asyncStorage!.setItem( + key: _storageKey, + value: jsonEncode(session.toJson()), + ); + } } - void _removeSession() { + Future _removeSession() async { _log.fine('Removing session'); _currentSession = null; _currentUser = null; + + if (_persistSession && _asyncStorage != null) { + if (!_initalizedStorage.isCompleted) { + await _initalizedStorage.future; + } + _asyncStorage!.removeItem( + key: _storageKey, + ); + } } void _mayStartBroadcastChannel() { @@ -1170,7 +1232,7 @@ class GoTrueClient { try { _broadcastChannel = web.getBroadcastChannel(broadcastKey); _broadcastChannelSubscription = - _broadcastChannel?.onMessage.listen((messageEvent) { + _broadcastChannel?.onMessage.listen((messageEvent) async { final rawEvent = messageEvent['event']; _log.finest('Received broadcast message: $messageEvent'); _log.info('Received broadcast event: $rawEvent'); @@ -1195,9 +1257,9 @@ class GoTrueClient { session = Session.fromJson(messageEvent['session']); } if (session != null) { - _saveSession(session); + await _saveSession(session); } else { - _removeSession(); + await _removeSession(); } notifyAllSubscribers(event, session: session, broadcast: false); } @@ -1247,14 +1309,14 @@ class GoTrueClient { throw AuthSessionMissingException(); } - _saveSession(session); + await _saveSession(session); notifyAllSubscribers(AuthChangeEvent.tokenRefreshed); _refreshTokenCompleter?.complete(data); return data; } on AuthException catch (error, stack) { if (error is! AuthRetryableFetchException) { - _removeSession(); + await _removeSession(); notifyAllSubscribers(AuthChangeEvent.signedOut); } else { notifyException(error, stack); diff --git a/packages/gotrue/lib/src/types/gotrue_async_storage.dart b/packages/gotrue/lib/src/types/gotrue_async_storage.dart index 29ce2b15..b4a1304e 100644 --- a/packages/gotrue/lib/src/types/gotrue_async_storage.dart +++ b/packages/gotrue/lib/src/types/gotrue_async_storage.dart @@ -2,6 +2,9 @@ abstract class GotrueAsyncStorage { const GotrueAsyncStorage(); + /// May be implemented to allow for initialization of the storage before use. + Future initialize() async {} + /// Retrieves an item asynchronously from the storage with the key. Future getItem({required String key}); diff --git a/packages/supabase/lib/src/supabase_client.dart b/packages/supabase/lib/src/supabase_client.dart index 4f500b8b..5382ec9e 100644 --- a/packages/supabase/lib/src/supabase_client.dart +++ b/packages/supabase/lib/src/supabase_client.dart @@ -137,11 +137,7 @@ class SupabaseClient { }, _httpClient = httpClient, _isolate = isolate ?? (YAJsonIsolate()..initialize()) { - _authInstance = _initSupabaseAuthClient( - autoRefreshToken: authOptions.autoRefreshToken, - gotrueAsyncStorage: authOptions.pkceAsyncStorage, - authFlowType: authOptions.authFlowType, - ); + _authInstance = _initSupabaseAuthClient(authOptions: authOptions); _authHttpClient = AuthHttpClient(_supabaseKey, httpClient ?? Client(), _getAccessToken); rest = _initRestClient(); @@ -273,11 +269,8 @@ class SupabaseClient { _authInstance?.dispose(); } - GoTrueClient _initSupabaseAuthClient({ - bool? autoRefreshToken, - required GotrueAsyncStorage? gotrueAsyncStorage, - required AuthFlowType authFlowType, - }) { + GoTrueClient _initSupabaseAuthClient( + {required AuthClientOptions authOptions}) { final authHeaders = {...headers}; authHeaders['apikey'] = _supabaseKey; authHeaders['Authorization'] = 'Bearer $_supabaseKey'; @@ -285,10 +278,12 @@ class SupabaseClient { return GoTrueClient( url: _authUrl, headers: authHeaders, - autoRefreshToken: autoRefreshToken, + autoRefreshToken: authOptions.autoRefreshToken, httpClient: _httpClient, - asyncStorage: gotrueAsyncStorage, - flowType: authFlowType, + asyncStorage: authOptions.asyncStorage, + storageKey: authOptions.storageKey, + persistSession: authOptions.persistSession, + flowType: authOptions.authFlowType, ); } diff --git a/packages/supabase/lib/src/supabase_client_options.dart b/packages/supabase/lib/src/supabase_client_options.dart index 3587e618..fe326a4b 100644 --- a/packages/supabase/lib/src/supabase_client_options.dart +++ b/packages/supabase/lib/src/supabase_client_options.dart @@ -8,13 +8,22 @@ class PostgrestClientOptions { class AuthClientOptions { final bool autoRefreshToken; + + @Deprecated( + "The storage for the session is now handled by the auth client itself and is combined with the storage for pkce, so please use [asyncStorage] insetad") final GotrueAsyncStorage? pkceAsyncStorage; + final GotrueAsyncStorage? asyncStorage; final AuthFlowType authFlowType; + final String? storageKey; + final bool? persistSession; const AuthClientOptions({ this.autoRefreshToken = true, this.pkceAsyncStorage, + this.asyncStorage, this.authFlowType = AuthFlowType.pkce, + this.storageKey, + this.persistSession, }); } diff --git a/packages/supabase_flutter/lib/src/flutter_go_true_client_options.dart b/packages/supabase_flutter/lib/src/flutter_go_true_client_options.dart index 2c82a0f7..956dc4b8 100644 --- a/packages/supabase_flutter/lib/src/flutter_go_true_client_options.dart +++ b/packages/supabase_flutter/lib/src/flutter_go_true_client_options.dart @@ -1,6 +1,8 @@ import 'package:supabase_flutter/supabase_flutter.dart'; class FlutterAuthClientOptions extends AuthClientOptions { + @Deprecated( + "The storage for the session is now handled by the auth client itself and is combined with the storage for pkce, so please use [asyncStorage] insetad") final LocalStorage? localStorage; /// If true, the client will start the deep link observer and obtain sessions @@ -11,6 +13,9 @@ class FlutterAuthClientOptions extends AuthClientOptions { super.authFlowType, super.autoRefreshToken, super.pkceAsyncStorage, + super.asyncStorage, + super.storageKey, + super.persistSession, this.localStorage, this.detectSessionInUri = true, }); @@ -20,13 +25,21 @@ class FlutterAuthClientOptions extends AuthClientOptions { bool? autoRefreshToken, LocalStorage? localStorage, GotrueAsyncStorage? pkceAsyncStorage, + GotrueAsyncStorage? asyncStorage, + String? storageKey, + bool? persistSession, bool? detectSessionInUri, }) { return FlutterAuthClientOptions( authFlowType: authFlowType ?? this.authFlowType, autoRefreshToken: autoRefreshToken ?? this.autoRefreshToken, + // ignore: deprecated_member_use_from_same_package localStorage: localStorage ?? this.localStorage, + // ignore: deprecated_member_use pkceAsyncStorage: pkceAsyncStorage ?? this.pkceAsyncStorage, + asyncStorage: asyncStorage, + storageKey: storageKey, + persistSession: persistSession, detectSessionInUri: detectSessionInUri ?? this.detectSessionInUri, ); } diff --git a/packages/supabase_flutter/lib/src/local_storage.dart b/packages/supabase_flutter/lib/src/local_storage.dart index 7b201584..11bdef21 100644 --- a/packages/supabase_flutter/lib/src/local_storage.dart +++ b/packages/supabase_flutter/lib/src/local_storage.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:supabase_flutter/src/supabase_auth.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import './local_storage_stub.dart' @@ -148,3 +149,51 @@ class SharedPreferencesGotrueAsyncStorage extends GotrueAsyncStorage { await _prefs.setString(key, value); } } + +/// Combines the storage for pkce and session into one. +/// +/// Previously the session got stored by [SupabaseAuth] and the pkce flow by +/// [GoTrueClient] in a separate storage and with different interface. +/// This combiens both into one. +/// +/// This introduces another level of abstraction for the actual +/// session storage, but is necessary to prevent breaking changes. +class PkceAndSessionLocalStorage extends GotrueAsyncStorage { + final LocalStorage sessionLocalStorage; + final GotrueAsyncStorage pkceAsyncStorage; + + PkceAndSessionLocalStorage(this.sessionLocalStorage, this.pkceAsyncStorage); + @override + Future initialize() async { + await sessionLocalStorage.initialize(); + await pkceAsyncStorage.initialize(); + super.initialize(); + } + + @override + Future getItem({required String key}) { + if (key.endsWith("-code-verifier")) { + return pkceAsyncStorage.getItem(key: key); + } else { + return sessionLocalStorage.accessToken(); + } + } + + @override + Future removeItem({required String key}) async { + if (key.endsWith("-code-verifier")) { + await pkceAsyncStorage.removeItem(key: key); + } else { + await sessionLocalStorage.removePersistedSession(); + } + } + + @override + Future setItem({required String key, required String value}) async { + if (key.endsWith("-code-verifier")) { + await pkceAsyncStorage.setItem(key: key, value: value); + } else { + await sessionLocalStorage.persistSession(value); + } + } +} diff --git a/packages/supabase_flutter/lib/src/supabase.dart b/packages/supabase_flutter/lib/src/supabase.dart index 952460b0..77e5d389 100644 --- a/packages/supabase_flutter/lib/src/supabase.dart +++ b/packages/supabase_flutter/lib/src/supabase.dart @@ -101,19 +101,39 @@ class Supabase with WidgetsBindingObserver { _log.config("Initialize Supabase v$version"); + // ignore: deprecated_member_use if (authOptions.pkceAsyncStorage == null) { authOptions = authOptions.copyWith( pkceAsyncStorage: SharedPreferencesGotrueAsyncStorage(), ); } + // ignore: deprecated_member_use_from_same_package if (authOptions.localStorage == null) { authOptions = authOptions.copyWith( localStorage: SharedPreferencesLocalStorage( persistSessionKey: "sb-${Uri.parse(url).host.split(".").first}-auth-token", + // For now we don't set the above key that is used by supabase-js too + // as [AuthClientOptions.storageKey], because this would change + // the key for exsting pkce items. For v3 we should change this. ), ); } + if (authOptions.persistSession == null) { + authOptions = authOptions.copyWith( + persistSession: true, + ); + } + + if (authOptions.asyncStorage == null) { + authOptions = authOptions.copyWith( + asyncStorage: PkceAndSessionLocalStorage( + // ignore: deprecated_member_use_from_same_package + authOptions.localStorage!, + // ignore: deprecated_member_use + authOptions.pkceAsyncStorage!, + )); + } _instance._init( url, anonKey, @@ -130,13 +150,6 @@ class Supabase with WidgetsBindingObserver { final supabaseAuth = SupabaseAuth(); _instance._supabaseAuth = supabaseAuth; await supabaseAuth.initialize(options: authOptions); - - // Wrap `recoverSession()` in a `CancelableOperation` so that it can be canceled in dispose - // if still in progress - _instance._restoreSessionCancellableOperation = - CancelableOperation.fromFuture( - supabaseAuth.recoverSession(), - ); } _log.info('***** Supabase init completed *****'); @@ -160,16 +173,12 @@ class Supabase with WidgetsBindingObserver { bool _debugEnable = false; - /// Wraps the `recoverSession()` call so that it can be terminated when `dispose()` is called - late CancelableOperation _restoreSessionCancellableOperation; - CancelableOperation? _realtimeReconnectOperation; StreamSubscription? _logSubscription; /// Dispose the instance to free up resources. Future dispose() async { - await _restoreSessionCancellableOperation.cancel(); _logSubscription?.cancel(); client.dispose(); _instance._supabaseAuth?.dispose(); diff --git a/packages/supabase_flutter/lib/src/supabase_auth.dart b/packages/supabase_flutter/lib/src/supabase_auth.dart index ada94a24..13215b5d 100644 --- a/packages/supabase_flutter/lib/src/supabase_auth.dart +++ b/packages/supabase_flutter/lib/src/supabase_auth.dart @@ -15,7 +15,6 @@ import 'package:url_launcher/url_launcher.dart'; class SupabaseAuth with WidgetsBindingObserver { static WidgetsBinding? get _widgetsBindingInstance => WidgetsBinding.instance; - late LocalStorage _localStorage; late AuthFlowType _authFlowType; /// Whether to automatically refresh the token @@ -26,8 +25,6 @@ class SupabaseAuth with WidgetsBindingObserver { /// throughout your app's life. static bool _initialDeeplinkIsHandled = false; - StreamSubscription? _authSubscription; - StreamSubscription? _deeplinkSubscription; final _appLinks = AppLinks(); @@ -40,65 +37,14 @@ class SupabaseAuth with WidgetsBindingObserver { Future initialize({ required FlutterAuthClientOptions options, }) async { - _localStorage = options.localStorage!; _authFlowType = options.authFlowType; _autoRefreshToken = options.autoRefreshToken; - _authSubscription = Supabase.instance.client.auth.onAuthStateChange.listen( - (data) { - _onAuthStateChange(data.event, data.session); - }, - onError: (error, stackTrace) {}, - ); - - await _localStorage.initialize(); - - final hasPersistedSession = await _localStorage.hasAccessToken(); - var shouldEmitInitialSession = true; - if (hasPersistedSession) { - final persistedSession = await _localStorage.accessToken(); - if (persistedSession != null) { - try { - await Supabase.instance.client.auth - .setInitialSession(persistedSession); - shouldEmitInitialSession = false; - } catch (error, stackTrace) { - _log.warning( - 'Error while setting initial session', error, stackTrace); - } - } - } - if (shouldEmitInitialSession) { - Supabase.instance.client.auth - // ignore: invalid_use_of_internal_member - .notifyAllSubscribers(AuthChangeEvent.initialSession); - } _widgetsBindingInstance?.addObserver(this); if (options.detectSessionInUri) { await _startDeeplinkObserver(); } - - // Emit a null session if the user did not have persisted session - } - - /// Recovers the session from local storage. - /// - /// Called lazily after `.initialize()` by `Supabase` instance - Future recoverSession() async { - try { - final hasPersistedSession = await _localStorage.hasAccessToken(); - if (hasPersistedSession) { - final persistedSession = await _localStorage.accessToken(); - if (persistedSession != null) { - await Supabase.instance.client.auth.recoverSession(persistedSession); - } - } - } on AuthException catch (error, stackTrace) { - _log.warning(error.message, error, stackTrace); - } catch (error, stackTrace) { - _log.warning("Error while recovering session", error, stackTrace); - } } /// Dispose the instance to free up resources @@ -106,7 +52,6 @@ class SupabaseAuth with WidgetsBindingObserver { if (!kIsWeb && Platform.environment.containsKey('FLUTTER_TEST')) { _initialDeeplinkIsHandled = false; } - _authSubscription?.cancel(); _stopDeeplinkObserver(); _widgetsBindingInstance?.removeObserver(this); } @@ -127,14 +72,6 @@ class SupabaseAuth with WidgetsBindingObserver { } } - void _onAuthStateChange(AuthChangeEvent event, Session? session) { - if (session != null) { - _localStorage.persistSession(jsonEncode(session.toJson())); - } else if (event == AuthChangeEvent.signedOut) { - _localStorage.removePersistedSession(); - } - } - /// If _authCallbackUrlHost not init, we treat all deep links as auth callback bool _isAuthCallbackDeeplink(Uri uri) { return (uri.fragment.contains('access_token') &&