restoreSession method
Restore a previously saved session on app launch.
The flow is offline-aware and optimistic:
- Read the persisted session from secure storage.
- If none exists → return RestoreResult.noSession.
- Populate the in-memory state immediately (accessToken, currentUser, expiry) and fire authStateChanges so apps render their authenticated UI without waiting for the network.
- If the access token is still valid → return RestoreResult.restored.
- Otherwise refresh against the server:
- Success → return RestoreResult.restored.
- Auth rejection (token revoked/expired/invalid) → clear session, return RestoreResult.expired.
- Network error → keep optimistic state, return RestoreResult.offline. The app can retry later via refreshSession.
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;
}
}