restoreSession method

Future<({bool hadStoredLogin, User? user})> restoreSession()

Restores a session when the database no longer contains a user row.

Secure credentials take precedence over legacy old TAT credentials. hadStoredLogin remains true when stored data is unusable or cannot be verified because of a transient failure, allowing startup to skip the intro.

Never throws and never blocks longer than _restoreLoginTimeout: this runs before runApp, so a storage failure, a malformed portal response, or a stalled connection must degrade to "open the login screen" instead of leaving the app without any UI.

Implementation

Future<({User? user, bool hadStoredLogin})> restoreSession() async {
  var hadStoredLogin = false;
  String? username;
  String? password;

  try {
    final secureUsername = await _secureStorage.read(key: _usernameKey);
    final securePassword = await _secureStorage.read(key: _passwordKey);
    if (secureUsername != null || securePassword != null) {
      hadStoredLogin = true;
      if (secureUsername?.isNotEmpty == true &&
          securePassword?.isNotEmpty == true) {
        username = secureUsername;
        password = securePassword;
      } else {
        await _clearSecureCredentials();
      }
    }
  } catch (error) {
    log('Failed to read stored credentials: $error', name: 'AuthRepository');
  }

  if (username == null || password == null) {
    try {
      final preferences = await SharedPreferences.getInstance();
      final legacyUserData = preferences.get(_legacyUserDataKey);
      if (legacyUserData != null) {
        hadStoredLogin = true;
        try {
          final decoded = switch (legacyUserData) {
            final String value => jsonDecode(value),
            _ => null,
          };
          if (decoded
              case {
                'account': final String account,
                'password': final String legacyPassword,
              }
              when account.isNotEmpty && legacyPassword.isNotEmpty) {
            username = account;
            password = legacyPassword;
          } else {
            await _clearLegacyCredentials(preferences);
          }
        } on FormatException {
          await _clearLegacyCredentials(preferences);
        }
      }
    } catch (error) {
      log(
        'Failed to read legacy credentials: $error',
        name: 'AuthRepository',
      );
    }
  }

  if (username == null || password == null) {
    if (hadStoredLogin) {
      _onSessionDestroyed(const LoginException(.credentialsMissing));
    }
    return (user: null, hadStoredLogin: hadStoredLogin);
  }

  try {
    final user = await login(
      username,
      password,
    ).timeout(_restoreLoginTimeout);
    return (user: user, hadStoredLogin: true);
  } on LoginException catch (exception) {
    await _clearCredentials();
    _onSessionDestroyed(exception);
    return (user: null, hadStoredLogin: true);
  } catch (error) {
    // Everything else is treated as transient — a network failure, a stalled
    // connection, or a captive portal answering `login.do` with HTML that
    // fails to parse. Keep the credentials and let the user retry from the
    // login screen rather than aborting startup.
    log('Failed to restore session: $error', name: 'AuthRepository');
    return (user: null, hadStoredLogin: true);
  }
}