Add login timeout mechanism and fix premature API calls
- Add 60-second timeout to login initialization in LoginView - Show retry/offline options when login times out - Move loadFavorites() to handleLoginSuccess to prevent API calls before authentication is complete - Prevents errors when auto-login is slow or stuck
This commit is contained in:
1 parent
688d798b9d
commit
825534621a
2 files changed
+109
-6
No files matched your search
@@ -24,24 +24,49 @@
|
||||
let initStatus = $state('Connecting to Qobuz...');
|
||||
let error = $state<string | null>(null);
|
||||
let initError = $state<string | null>(null);
|
||||
let isTimedOut = $state(false);
|
||||
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const LOGIN_TIMEOUT_MS = 60000; // 60 seconds
|
||||
|
||||
// Initialize the Qobuz client on mount
|
||||
$effect(() => {
|
||||
initializeClient();
|
||||
return () => {
|
||||
// Cleanup timeout on unmount
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
async function initializeClient() {
|
||||
try {
|
||||
isInitializing = true;
|
||||
initError = null;
|
||||
isTimedOut = false;
|
||||
initStatus = 'Connecting to Qobuz...';
|
||||
|
||||
// Start timeout timer
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
timeoutId = setTimeout(() => {
|
||||
if (isInitializing) {
|
||||
console.warn('Login initialization timed out after 60 seconds');
|
||||
isTimedOut = true;
|
||||
isInitializing = false;
|
||||
}
|
||||
}, LOGIN_TIMEOUT_MS);
|
||||
|
||||
const result = await invoke<boolean>('init_client');
|
||||
console.log('Client initialized:', result);
|
||||
|
||||
// Check if already logged in (in-memory session)
|
||||
const loggedIn = await invoke<boolean>('is_logged_in');
|
||||
if (loggedIn) {
|
||||
clearTimeoutTimer();
|
||||
const userInfo = await invoke<{ user_name: string; subscription: string } | null>('get_user_info');
|
||||
if (userInfo) {
|
||||
onLoginSuccess({ userName: userInfo.user_name, subscription: userInfo.subscription });
|
||||
@@ -65,6 +90,7 @@
|
||||
}>('auto_login');
|
||||
|
||||
if (response.success) {
|
||||
clearTimeoutTimer();
|
||||
console.log('Auto-login successful');
|
||||
onLoginSuccess({
|
||||
userName: response.user_name || 'Qobuz User',
|
||||
@@ -76,14 +102,32 @@
|
||||
// Don't show error, just fall through to manual login
|
||||
}
|
||||
}
|
||||
|
||||
// If we reach here, no auto-login - clear timeout and show login form
|
||||
clearTimeoutTimer();
|
||||
} catch (err) {
|
||||
console.error('Failed to initialize client:', err);
|
||||
clearTimeoutTimer();
|
||||
initError = String(err);
|
||||
} finally {
|
||||
isInitializing = false;
|
||||
if (!isTimedOut) {
|
||||
isInitializing = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function clearTimeoutTimer() {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
timeoutId = null;
|
||||
}
|
||||
}
|
||||
|
||||
function handleRetryLogin() {
|
||||
isTimedOut = false;
|
||||
initializeClient();
|
||||
}
|
||||
|
||||
async function handleLogin(e: Event) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -154,7 +198,18 @@
|
||||
<p class="subtitle">Native Qobuz Client for Linux</p>
|
||||
</div>
|
||||
|
||||
{#if isInitializing}
|
||||
{#if isTimedOut}
|
||||
<div class="timeout-box">
|
||||
<p class="timeout-title">Connection is taking too long</p>
|
||||
<p class="timeout-detail">
|
||||
Unable to connect to Qobuz after 60 seconds. This could be a network issue or Qobuz may be temporarily unavailable.
|
||||
</p>
|
||||
<div class="timeout-actions">
|
||||
<button class="retry-btn" onclick={handleRetryLogin}>Try Again</button>
|
||||
<button class="offline-btn" onclick={handleStartOffline}>Start Offline</button>
|
||||
</div>
|
||||
</div>
|
||||
{:else if isInitializing}
|
||||
<div class="initializing">
|
||||
<div class="spinner"></div>
|
||||
<p>{initStatus}</p>
|
||||
@@ -311,9 +366,37 @@
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.timeout-box {
|
||||
text-align: center;
|
||||
padding: 24px;
|
||||
background-color: rgba(251, 191, 36, 0.1);
|
||||
border: 1px solid rgba(251, 191, 36, 0.3);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.timeout-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #fbbf24;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.timeout-detail {
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.5;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.timeout-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.retry-btn {
|
||||
margin-top: 16px;
|
||||
padding: 8px 24px;
|
||||
padding: 10px 24px;
|
||||
background-color: var(--accent-primary);
|
||||
color: white;
|
||||
border: none;
|
||||
@@ -328,6 +411,23 @@
|
||||
background-color: var(--accent-hover);
|
||||
}
|
||||
|
||||
.offline-btn {
|
||||
padding: 10px 24px;
|
||||
background-color: transparent;
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--text-muted);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
transition: all 150ms ease;
|
||||
}
|
||||
|
||||
.offline-btn:hover {
|
||||
border-color: var(--text-primary);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -1266,6 +1266,9 @@
|
||||
setLoggedIn(info);
|
||||
showToast(`Welcome, ${info.userName}!`, 'success');
|
||||
|
||||
// Load favorites now that login is confirmed
|
||||
loadFavorites();
|
||||
|
||||
// Refresh offline status now that we're logged in
|
||||
await refreshOfflineStatus();
|
||||
|
||||
@@ -1569,8 +1572,8 @@
|
||||
initDownloadStates();
|
||||
startDownloadEventListeners();
|
||||
|
||||
// Load favorites for global state
|
||||
loadFavorites();
|
||||
// Note: loadFavorites() is called in handleLoginSuccess after login is confirmed
|
||||
// This prevents API calls before authentication is complete
|
||||
|
||||
// Subscribe to download state changes to trigger reactivity
|
||||
const unsubscribeDownloads = subscribeDownloads(() => {
|
||||
|
||||
Reference in new issue
Block a user