Skip to content

Auto Refresh is not working #1158

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

Open
sai-chandra22 opened this issue Apr 17, 2025 · 1 comment
Open

Auto Refresh is not working #1158

sai-chandra22 opened this issue Apr 17, 2025 · 1 comment

Comments

@sai-chandra22
Copy link

Hey, I am using the access token for my api's which has the expiration time of 15 mins, So after 15 mins if I again opened the app, sometimes auto refresh is working, some times it is not working and as a result, 403 error in apis coming, eventually logging out the user after certain retries.

  await sb.Supabase.initialize(
    url: ApiKeys.supabaseUrl,
    anonKey: ApiKeys.supabaseAnonKey,
    authOptions: sb.FlutterAuthClientOptions(
      autoRefreshToken: true,
      localStorage:
          CustomSecureStorage(persistSessionKey: LocalStorage.session),
    ),
  );

class CustomSecureStorage extends sb.LocalStorage {
  late FlutterSecureStorage _storage;
  final String persistSessionKey;

  CustomSecureStorage({required this.persistSessionKey});

  @override
  Future<void> initialize() async {
    WidgetsFlutterBinding.ensureInitialized();
    _storage = const FlutterSecureStorage(
      aOptions: AndroidOptions(
        encryptedSharedPreferences: true,
        resetOnError: false,
      ),
      iOptions: IOSOptions(
        accessibility: KeychainAccessibility.first_unlock,
      ),
    );
    debugPrint('🔥 337ssd: Secure storage initialized');

    // No need for specific initialization in secure storage
  }

  @override
  Future<bool> hasAccessToken() async {
    final exists = await _storage.containsKey(key: persistSessionKey);
    debugPrint('🔍 337ssd: Has access token: $exists');
    return exists;
  }

  @override
  Future<String?> accessToken() async {
    final token = await _storage.read(key: persistSessionKey);
    prints('🔑 337ssd: Retrieved access token: $token');
    return token;
  }

  @override
  Future<void> persistSession(String persistSessionString) async {
    prints('💾 337ssd: Storing session in secure storage');
    await _storage.write(key: persistSessionKey, value: persistSessionString);
  }

  @override
  Future<void> removePersistedSession() async {
    prints('🗑️ 337ssd: Removing session from secure storage');
    await _storage.delete(key: persistSessionKey);
  }

  Future<void> clearSecureStorage() async {
    await _storage.delete(key: persistSessionKey);
    debugPrint('🗑️ 337ssd: All secure storage data cleared');
  }
}
class TokenExpiryManager with WidgetsBindingObserver {
  static final TokenExpiryManager _instance = TokenExpiryManager._internal();
  factory TokenExpiryManager() => _instance;
  static final Completer<void> _initializationCompleter = Completer<void>();
  static Future<void> get initialized => _initializationCompleter.future;

  final _supabase = sb.Supabase.instance.client;
  final homeController = Get.find<HomeController>();
  static bool _isInitialized = false;
  static Future<void>? _initializationFuture;
  sb.SupabaseClient get supabase => _supabase;
  int attemptCount = 0;
  bool isFirstTime = false;
  bool isMinimizedAndNotRefreshed = false;

  // Add these variables
  Completer<void>? _tokenRefreshCompleter;
  bool _isRefreshing = false;
  bool _isTokenValid = false;

  // Add this getter to check status
  bool get isRefreshingToken => _isRefreshing;
  bool get isTokenValid => _isTokenValid;

  // Add this method to wait for refresh
  Future<void> waitForTokenRefresh() async {
    if (isFirstTime) {
      if (_isRefreshing && _tokenRefreshCompleter != null) {
        await _tokenRefreshCompleter!.future;
      }
    }
  }

  StreamSubscription<sb.AuthState>? _authStateSubscription;

  TokenExpiryManager._internal(); // Empty internal constructor

  static Future<TokenExpiryManager> initialize() async {
    if (!_isInitialized) {
      _initializationFuture ??= _instance._initialize();
      await _initializationFuture;
      _isInitialized = true;

      // Add observer only once
      WidgetsBinding.instance.removeObserver(_instance);
      WidgetsBinding.instance.addObserver(_instance);
    }
    return _instance;
  }

  Future<void> _initialize() async {
    debugPrint('55ssd TokenExpiryManager initializing...');
    isFirstTime = true;

    await _authStateSubscription?.cancel();

    _authStateSubscription = _supabase.auth.onAuthStateChange
        .listen(_handleAuthStateChange, cancelOnError: false);

    _initializationCompleter.complete();
    debugPrint('55ssd TokenExpiryManager initialization complete');

    // Check session status on initialization
  }

  void _handleAuthStateChange(sb.AuthState data) async {
    final session = data.session;
    final event = data.event;
    final currentSession = _supabase.auth.currentSession;
    final user = await LocalStorage.getUserModel();
    final token = await LocalStorage.getRefreshToken();

    prints('55ssd 73ssd inside listenToAuthChanges session: $session');
    prints('55ssd 73ssd inside listenToAuthChanges event: $event');
    prints(
        '55ssd 73ssd inside listenToAuthChanges currentSession: $currentSession');
    prints(
        '55ssd 73ssd inside listenToAuthChanges isExpired: ${session?.isExpired}');
    prints(
        '55ssd 73ssd inside listenToAuthChanges refreshToken: ${await LocalStorage.getRefreshToken()}');

    callSaveTestLog(
        'Entered Starting point of app: Session: $session, Event: $event',
        "Last saved token:$token",
        '${user?.firstName ?? 777}');

    if (session != null) {
      switch (event) {
        case sb.AuthChangeEvent.tokenRefreshed:
          _isRefreshing = true;
          _tokenRefreshCompleter = Completer<void>();
          try {
            debugPrint('Token refresh started');
            await _saveSessionToLocal(session);
            _isTokenValid = true;
            isFirstTime = false;
            isMinimizedAndNotRefreshed = false;
            _tokenRefreshCompleter?.complete();
          } catch (e) {
            _tokenRefreshCompleter?.completeError(e);
            isFirstTime = false;
            _isTokenValid = false;
            isMinimizedAndNotRefreshed = false;
          } finally {
            _isRefreshing = false;
            isFirstTime = false;
            _tokenRefreshCompleter = null;
            isMinimizedAndNotRefreshed = false;
          }
          break;

        case sb.AuthChangeEvent.initialSession:
          try {
            debugPrint('Initial session - fresh launch');
            await _saveSessionToLocal(session);
            isFirstTime = false;
            _isTokenValid = true;
            isMinimizedAndNotRefreshed = false;
          } catch (e) {
            debugPrint('Error saving initial session: $e');
            isFirstTime = false;
            _isTokenValid = false;
            isMinimizedAndNotRefreshed = false;
          }
          break;

        case sb.AuthChangeEvent.signedOut:
          debugPrint('User signed out');

          break;

        case sb.AuthChangeEvent.userDeleted:
          debugPrint('User account deleted');
          //  await _clearLocalSession();
          break;

        default:
          debugPrint('Auth event: $event');
      }
    } else {
      _isTokenValid = false;
      debugPrint('73ssd session is $session, no details found');
    }
  }

  void logout() async {
    await _supabase.auth.signOut();
  }

  callSaveTestLog(String testLog, String currentToken, String name) async {
    try {
      final response =
          await OnBoardingGQLQueries.saveTestLog(testLog, currentToken, name);
      debugPrint('73ssd Test log saved successfully: $response');
      final metadata = response['metadata'];
      final success = response['success'];
      if (success != null && success == true) {
        debugPrint('73ssd Test log saved successfully: $metadata');
      } else {
        debugPrint('73ssd Failed to save test log: $metadata');
      }
    } catch (e) {
      debugPrint('73ssd Error saving test log: $e');
    }
  }

  Future<void> _saveSessionToLocal(sb.Session? session,
      // ignore: unused_element_parameter
      [String? enteredLocation]) async {
    //  final user = await LocalStorage.getUserModel();
    try {
      if (session != null) {
        await LocalStorage.setAllTokenData(
          token: session.accessToken,
          refreshToken: session.refreshToken!,
          expiresIn: session.expiresIn?.toString() ?? '',
          expiresAt: session.expiresAt.toString(),
        );

        debugPrint(
            'Token saved locally: AccessToken and RefreshToken updated.');
      } else {
        debugPrint('No session available to save.');
      }
    } catch (e) {
      debugPrint('73ssd Error saving session to local storage: $e');
    }
  }

  bool _hasResumedOnce = false;

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) async {
    final user = await LocalStorage.getUserModel();
    final token = await LocalStorage.getRefreshToken();
    final homeController = Get.find<HomeController>();
    debugPrint(
        '55ssd :App lifecycle state condition: ${homeController.enablePrivacyMode.value == true}');

    if (state == AppLifecycleState.paused &&
        homeController.enablePrivacyMode.value == true) {
      isMinimizedAndNotRefreshed = true;
      final currentSession = _supabase.auth.currentSession;
      debugPrint('55ssd :App paused: Checking session');
      callSaveTestLog(
        'User minimized the app with subscription canceled status: ${_authStateSubscription == null} session: $currentSession',
        "Last saved token : $token",
        '${user?.firstName ?? 777}',
      );
    }

    if (state == AppLifecycleState.resumed) {
      debugPrint('55ssd :App resumed');
      isMinimizedAndNotRefreshed = false;
      if (_hasResumedOnce) return;
      _hasResumedOnce = true;
      final currentSession = _supabase.auth.currentSession;
      callSaveTestLog(
        'User resumed the app with currentSession: $currentSession',
        "Last saved token : $token",
        '${user?.firstName ?? 777}',
      );

      if (user != null) {
        final context = AppGlobalKey.navKey.currentContext;
        if (context != null) {
          prints('73ssd inside Phoenix.rebirth');
          Phoenix.rebirth(context);
          callSaveTestLog(
            'Restarting the app from code level: $currentSession',
            "Last saved token : $token",
            '${user.firstName ?? 777}',
          );
        }
        if (_authStateSubscription == null) {
          debugPrint('73ssd inside if _authStateSubscription == null');
          callSaveTestLog(
            'Again initialising the app: $currentSession',
            "Last saved token : $token",
            '${user.firstName ?? 777}',
          );
          await _initialize();
        }
      }
      _hasResumedOnce = false;
    }
  }

  void dispose() {
    _authStateSubscription?.cancel();
    WidgetsBinding.instance.removeObserver(this);
    debugPrint('TokenExpiryManager disposed');
  }

  Future<String> getApiToken() async {
    final session = _supabase.auth.currentSession;
    final user = await LocalStorage.getUserModel();
    try {
      if (session != null && user != null) {
        //  await _saveSessionToLocal(session, 'getApiToken');
        prints('73ssd inside getApiToken session: ${session.accessToken}');

        return session.accessToken;
      } else if (session == null && user != null) {
        final token = await LocalStorage.getGraphQLApiToken();
        prints('73ssd inside local storage token: $token');
        return token ?? ApiKeys.unAuthToken;
      } else {
        return ApiKeys.unAuthToken;
      }
    } catch (e) {
      debugPrint('Error getting API token: $e');
      return ApiKeys.unAuthToken;
    }
  }
}
@sai-chandra22
Copy link
Author

sai-chandra22 commented Apr 17, 2025

This is at least working on IOS (auto refresh), but on android its not at all working, seems strange but this is the issue, can anybody help on this @laktek @tomashley @seenickcode @saltcod

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant