fix(auth): Improve token retrieval reliability on cold start
This commit is contained in:
@@ -184,7 +184,25 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
final storage = ref.read(optimizedStorageServiceProvider);
|
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) {
|
if (token != null && token.isNotEmpty) {
|
||||||
DebugLogger.auth('Found stored token during initialization');
|
DebugLogger.auth('Found stored token during initialization');
|
||||||
@@ -244,9 +262,10 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
_prefetchConversations();
|
_prefetchConversations();
|
||||||
|
|
||||||
// Background server validation; if it fails, invalidate token gracefully
|
// Background server validation; if it fails, invalidate token gracefully
|
||||||
|
final validToken = token; // Capture non-null token for closure
|
||||||
Future.microtask(() async {
|
Future.microtask(() async {
|
||||||
try {
|
try {
|
||||||
final ok = await _validateToken(token);
|
final ok = await _validateToken(validToken);
|
||||||
DebugLogger.auth('Deferred token validation result: $ok');
|
DebugLogger.auth('Deferred token validation result: $ok');
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
await onTokenInvalidated();
|
await onTokenInvalidated();
|
||||||
@@ -267,6 +286,23 @@ class AuthStateManager extends _$AuthStateManager {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} 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(
|
_update(
|
||||||
(current) => current.copyWith(
|
(current) => current.copyWith(
|
||||||
status: AuthStatus.unauthenticated,
|
status: AuthStatus.unauthenticated,
|
||||||
|
|||||||
@@ -71,6 +71,17 @@ void main() {
|
|||||||
synchronizable: false,
|
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');
|
_startupTimeline!.instant('secure_storage_ready');
|
||||||
|
|
||||||
// Initialize Hive (now optimized with migration state caching)
|
// Initialize Hive (now optimized with migration state caching)
|
||||||
|
|||||||
Reference in New Issue
Block a user