restoreSession method

Future<RestoreResult> restoreSession()

Restore a previously saved session on app launch.

The flow is offline-aware and optimistic:

  1. Read the persisted session from secure storage.
  2. If none exists → return RestoreResult.noSession.
  3. Populate the in-memory state immediately (accessToken, currentUser, expiry) and fire authStateChanges so apps render their authenticated UI without waiting for the network.
  4. If the access token is still valid → return RestoreResult.restored.
  5. Otherwise refresh against the server:

Implementation

Future<RestoreResult> restoreSession() async {
  final persisted = await _storage.readSession();
  if (persisted == null) return RestoreResult.noSession;

  // Optimistic restoration — populate state from disk before any network.
  _accessToken = persisted.accessToken;
  _accessTokenExpiresAt = persisted.expiresAt;
  _currentUser = persisted.user;
  _authStateController.add(persisted.user);

  // Valid by the clock is not valid: the session may have been signed out
  // on another device, revoked, or the account disabled since. Ask the
  // server before saying it is restored (#104). No answer -- offline, a
  // timeout, a server error -- keeps the optimistic session: offline.
  if (!persisted.isAccessTokenExpired) {
    try {
      final user = await _api.getMe(persisted.accessToken);
      await _updateUserAndPersist(user);
      return RestoreResult.restored;
    } catch (e) {
      if (!_refusesAccessToken(e)) return RestoreResult.offline;
      // Refused: the access token is done. The refresh token decides.
    }
  }

  try {
    await _refreshSingleFlight(refreshToken: persisted.refreshToken);
    return RestoreResult.restored;
  } catch (e) {
    // Refused: _refreshSingleFlight has cleared it. Anything else keeps the
    // optimistic state: the app stays signed in, API calls fail until the
    // network returns, and [refreshSession] can be called then.
    return _isRejection(e) ? RestoreResult.expired : RestoreResult.offline;
  }
}