Implementation
Future<String> getAccessToken() async {
final token = await _storage.read(key: _accessTokenKey);
if (token == null || token.isEmpty) {
throw Exception('Not signed in to OpenAI.');
}
final expiryStr = await _storage.read(key: _expiryKey);
final expiry = expiryStr == null ? null : DateTime.tryParse(expiryStr);
// Missing expiry is treated like "already expired" — returning a stale
// token on an unknown-age install is worse than paying for a refresh.
final needsRefresh =
expiry == null ||
expiry.isBefore(DateTime.now().add(const Duration(minutes: 5)));
if (needsRefresh) {
final refreshToken = await _storage.read(key: _refreshTokenKey);
if (refreshToken != null && refreshToken.isNotEmpty) {
await _refreshToken();
final refreshed = await _storage.read(key: _accessTokenKey);
if (refreshed == null || refreshed.isEmpty) {
throw Exception('Not signed in to OpenAI.');
}
return refreshed;
}
// No refresh token. Expiry known + expired → truly stuck; surface as
// signed-out. Expiry unknown (legacy) → fall through and return the
// stored token, matching the pre-hardening behavior.
if (expiry != null) {
throw Exception('Not signed in to OpenAI.');
}
}
return token;
}