fix(auth): Improve token retrieval reliability on cold start

This commit is contained in:
cogwheel
2025-12-21 08:51:03 +05:30
parent 83c2e01528
commit d4a9b6863d
2 changed files with 49 additions and 2 deletions

View File

@@ -184,7 +184,25 @@ class AuthStateManager extends _$AuthStateManager {
try {
final storage = ref.read(optimizedStorageServiceProvider);
final token = await storage.getAuthToken();
// On cold start, secure storage (iOS Keychain) can be slow or
// transiently fail. Retry a few times before giving up to avoid
// incorrectly showing the sign-in page.
String? token;
const maxAttempts = 3;
for (var attempt = 1; attempt <= maxAttempts; attempt++) {
token = await storage.getAuthToken();
if (token != null) break;
// Only retry if this might be a cold start issue
if (attempt < maxAttempts) {
DebugLogger.auth(
'Token read returned null, retrying ($attempt/$maxAttempts)',
);
// Exponential backoff: 50ms, 100ms
await Future.delayed(Duration(milliseconds: 50 * attempt));
}
}
if (token != null && token.isNotEmpty) {
DebugLogger.auth('Found stored token during initialization');
@@ -244,9 +262,10 @@ class AuthStateManager extends _$AuthStateManager {
_prefetchConversations();
// Background server validation; if it fails, invalidate token gracefully
final validToken = token; // Capture non-null token for closure
Future.microtask(() async {
try {
final ok = await _validateToken(token);
final ok = await _validateToken(validToken);
DebugLogger.auth('Deferred token validation result: $ok');
if (!ok) {
await onTokenInvalidated();
@@ -267,6 +286,23 @@ class AuthStateManager extends _$AuthStateManager {
);
}
} else {
// No token found after retries. Check if we have saved credentials
// and attempt silent login immediately to avoid showing sign-in page.
final hasCreds = await storage.hasCredentials();
if (hasCreds) {
DebugLogger.auth(
'No token but credentials exist - attempting silent login',
);
// Keep loading state while we attempt silent login
// This prevents the router from redirecting to sign-in
await _performSilentLogin();
// _performSilentLogin() updates state appropriately on both success
// and failure (e.g., AuthStatus.error for network issues), so we
// return here to preserve that state.
return;
}
// No credentials - set to unauthenticated
DebugLogger.auth('No token or credentials found');
_update(
(current) => current.copyWith(
status: AuthStatus.unauthenticated,

View File

@@ -71,6 +71,17 @@ void main() {
synchronizable: false,
),
);
// Warm up secure storage on cold start. iOS Keychain access can be slow
// on first read, which causes race conditions where auth token returns
// null even when it exists. This pre-warms the keychain connection.
try {
await secureStorage
.read(key: '_warmup')
.timeout(const Duration(milliseconds: 500), onTimeout: () => null);
} catch (_) {
// Ignore warmup errors - this is best-effort
}
_startupTimeline!.instant('secure_storage_ready');
// Initialize Hive (now optimized with migration state caching)