Root cause: when auth.me() failed during initial navigation, the catch block
called navigate('/login') without clearing _pendingLoginRedirect. The outer
finally then fired a second concurrent navigate('/login'), which held
isNavigating=true while running. If the user submitted the login form (or
iCloud Keychain autofilled credentials) before the second navigation
completed, navigate('/', user) was silently blocked by the isNavigating guard —
login appeared to succeed but the app never advanced to the dashboard.
Fix: clear _pendingLoginRedirect in the catch block so the finally handler
does not spawn the duplicate navigation.
Also adds a GET /api/v1/version endpoint (no auth required) and shows the
version on the login page, so users can verify their PWA has received the
latest cached JS.
Resolves #68
Co-authored-by: Ulas Kalayci <ulas.kalayci@googlemail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -567,7 +567,8 @@
|
||||
"loginButton": "تسجيل الدخول",
|
||||
"loggingIn": "جارٍ تسجيل الدخول…",
|
||||
"tooManyAttempts": "محاولات كثيرة جداً. يرجى الانتظار قليلاً.",
|
||||
"invalidCredentials": "بيانات اعتماد غير صالحة."
|
||||
"invalidCredentials": "بيانات اعتماد غير صالحة.",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"install": {
|
||||
"title": "تثبيت Oikos",
|
||||
@@ -604,4 +605,4 @@
|
||||
"unitMonth": "شهر",
|
||||
"unitMonths": "أشهر"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,7 +618,8 @@
|
||||
"loginButton": "Anmelden",
|
||||
"loggingIn": "Wird angemeldet …",
|
||||
"tooManyAttempts": "Zu viele Versuche. Bitte warte kurz.",
|
||||
"invalidCredentials": "Ungültige Anmeldedaten."
|
||||
"invalidCredentials": "Ungültige Anmeldedaten.",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"install": {
|
||||
"title": "Oikos installieren",
|
||||
@@ -676,4 +677,4 @@
|
||||
"pendingBadgeTitle": "{{count}} fällige Erinnerung",
|
||||
"pendingBadgeTitlePlural": "{{count}} fällige Erinnerungen"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +567,8 @@
|
||||
"loginButton": "Σύνδεση",
|
||||
"loggingIn": "Σύνδεση…",
|
||||
"tooManyAttempts": "Πολλές προσπάθειες. Παρακαλώ περιμένετε λίγο.",
|
||||
"invalidCredentials": "Λανθασμένα στοιχεία σύνδεσης."
|
||||
"invalidCredentials": "Λανθασμένα στοιχεία σύνδεσης.",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"install": {
|
||||
"title": "Εγκατάσταση Oikos",
|
||||
@@ -604,4 +605,4 @@
|
||||
"unitMonth": "μήνα",
|
||||
"unitMonths": "μήνες"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +567,8 @@
|
||||
"loginButton": "Log in",
|
||||
"loggingIn": "Logging in…",
|
||||
"tooManyAttempts": "Too many attempts. Please wait a moment.",
|
||||
"invalidCredentials": "Invalid credentials."
|
||||
"invalidCredentials": "Invalid credentials.",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"install": {
|
||||
"title": "Install Oikos",
|
||||
@@ -625,4 +626,4 @@
|
||||
"pendingBadgeTitle": "{{count}} reminder due",
|
||||
"pendingBadgeTitlePlural": "{{count}} reminders due"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +567,8 @@
|
||||
"loginButton": "Iniciar sesión",
|
||||
"loggingIn": "Iniciando sesión…",
|
||||
"tooManyAttempts": "Demasiados intentos. Por favor, espera un momento.",
|
||||
"invalidCredentials": "Credenciales incorrectas."
|
||||
"invalidCredentials": "Credenciales incorrectas.",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"install": {
|
||||
"title": "Instalar Oikos",
|
||||
@@ -604,4 +605,4 @@
|
||||
"unitMonth": "mes",
|
||||
"unitMonths": "meses"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +567,8 @@
|
||||
"loginButton": "Se connecter",
|
||||
"loggingIn": "Connexion…",
|
||||
"tooManyAttempts": "Trop de tentatives. Veuillez patienter un moment.",
|
||||
"invalidCredentials": "Identifiants invalides."
|
||||
"invalidCredentials": "Identifiants invalides.",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"install": {
|
||||
"title": "Installer Oikos",
|
||||
@@ -604,4 +605,4 @@
|
||||
"unitMonth": "mois",
|
||||
"unitMonths": "mois"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +567,8 @@
|
||||
"loginButton": "लॉग इन",
|
||||
"loggingIn": "लॉग इन हो रहा है…",
|
||||
"tooManyAttempts": "बहुत अधिक प्रयास। कृपया थोड़ा प्रतीक्षा करें।",
|
||||
"invalidCredentials": "अमान्य क्रेडेंशियल।"
|
||||
"invalidCredentials": "अमान्य क्रेडेंशियल।",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"install": {
|
||||
"title": "Oikos इंस्टॉल करें",
|
||||
@@ -604,4 +605,4 @@
|
||||
"unitMonth": "माह",
|
||||
"unitMonths": "माह"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +567,8 @@
|
||||
"loginButton": "Accedi",
|
||||
"loggingIn": "Accesso in corso…",
|
||||
"tooManyAttempts": "Troppi tentativi. Attendi un momento.",
|
||||
"invalidCredentials": "Credenziali non valide."
|
||||
"invalidCredentials": "Credenziali non valide.",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"install": {
|
||||
"title": "Installa Oikos",
|
||||
@@ -604,4 +605,4 @@
|
||||
"unitMonth": "mese",
|
||||
"unitMonths": "mesi"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +567,8 @@
|
||||
"loginButton": "ログイン",
|
||||
"loggingIn": "ログイン中…",
|
||||
"tooManyAttempts": "試行回数が多すぎます。しばらくお待ちください。",
|
||||
"invalidCredentials": "ユーザー名またはパスワードが正しくありません。"
|
||||
"invalidCredentials": "ユーザー名またはパスワードが正しくありません。",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"install": {
|
||||
"title": "Oikos をインストール",
|
||||
@@ -604,4 +605,4 @@
|
||||
"unitMonth": "ヶ月",
|
||||
"unitMonths": "ヶ月"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +567,8 @@
|
||||
"loginButton": "Entrar",
|
||||
"loggingIn": "Entrando…",
|
||||
"tooManyAttempts": "Muitas tentativas. Por favor, aguarde.",
|
||||
"invalidCredentials": "Credenciais inválidas."
|
||||
"invalidCredentials": "Credenciais inválidas.",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"install": {
|
||||
"title": "Instalar Oikos",
|
||||
@@ -604,4 +605,4 @@
|
||||
"unitMonth": "mês",
|
||||
"unitMonths": "meses"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +567,8 @@
|
||||
"loginButton": "Войти",
|
||||
"loggingIn": "Вход…",
|
||||
"tooManyAttempts": "Слишком много попыток. Подождите немного.",
|
||||
"invalidCredentials": "Неверные данные для входа."
|
||||
"invalidCredentials": "Неверные данные для входа.",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"install": {
|
||||
"title": "Установить Oikos",
|
||||
@@ -604,4 +605,4 @@
|
||||
"unitMonth": "месяц",
|
||||
"unitMonths": "месяцев"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +567,8 @@
|
||||
"loginButton": "Logga in",
|
||||
"loggingIn": "Loggar in...",
|
||||
"tooManyAttempts": "För många försök. Vänta ett ögonblick.",
|
||||
"invalidCredentials": "Ogiltiga användaruppgifter."
|
||||
"invalidCredentials": "Ogiltiga användaruppgifter.",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"install": {
|
||||
"title": "Installera Oikos",
|
||||
@@ -604,4 +605,4 @@
|
||||
"unitMonth": "månad",
|
||||
"unitMonths": "månader"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -567,7 +567,8 @@
|
||||
"loginButton": "Giriş yap",
|
||||
"loggingIn": "Giriş yapılıyor…",
|
||||
"tooManyAttempts": "Çok fazla deneme. Lütfen bir süre bekleyin.",
|
||||
"invalidCredentials": "Geçersiz kimlik bilgileri."
|
||||
"invalidCredentials": "Geçersiz kimlik bilgileri.",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"install": {
|
||||
"title": "Oikos'u Yükle",
|
||||
@@ -604,4 +605,4 @@
|
||||
"unitMonth": "ay",
|
||||
"unitMonths": "ay"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+627
-626
File diff suppressed because it is too large
Load Diff
@@ -567,7 +567,8 @@
|
||||
"loginButton": "登录",
|
||||
"loggingIn": "登录中…",
|
||||
"tooManyAttempts": "尝试次数过多,请稍后再试。",
|
||||
"invalidCredentials": "用户名或密码错误。"
|
||||
"invalidCredentials": "用户名或密码错误。",
|
||||
"version": "v{{version}}"
|
||||
},
|
||||
"install": {
|
||||
"title": "安装 Oikos",
|
||||
@@ -604,4 +605,4 @@
|
||||
"unitMonth": "个月",
|
||||
"unitMonths": "个月"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
import { auth } from '/api.js';
|
||||
import { t } from '/i18n.js';
|
||||
|
||||
const VERSION_URL = '/api/v1/version';
|
||||
|
||||
/**
|
||||
* Rendert die Login-Seite in den gegebenen Container.
|
||||
* @param {HTMLElement} container
|
||||
@@ -56,12 +58,19 @@ export async function render(container) {
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<p class="login-version" id="login-version"></p>
|
||||
</main>
|
||||
`;
|
||||
|
||||
const form = container.querySelector('#login-form');
|
||||
const errorEl = container.querySelector('#login-error');
|
||||
const submitBtn = container.querySelector('#login-btn');
|
||||
const versionEl = container.querySelector('#login-version');
|
||||
|
||||
fetch(VERSION_URL)
|
||||
.then((r) => r.json())
|
||||
.then((d) => { versionEl.textContent = t('login.version', { version: d.version }); })
|
||||
.catch(() => {});
|
||||
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -171,6 +171,10 @@ async function navigate(path, userOrPushState = true, pushState = true) {
|
||||
} catch {
|
||||
currentPath = null; // Reset damit navigate('/login') nicht geblockt wird
|
||||
isNavigating = false;
|
||||
// _pendingLoginRedirect leeren: der catch ruft navigate('/login') direkt auf,
|
||||
// der finally soll keinen zweiten Aufruf starten (würde isNavigating=true setzen,
|
||||
// während die Login-Seite rendert, und so post-login navigate blockieren).
|
||||
_pendingLoginRedirect = false;
|
||||
navigate('/login');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -55,3 +55,11 @@
|
||||
font-size: var(--text-sm);
|
||||
margin-bottom: var(--space-4);
|
||||
}
|
||||
|
||||
.login-version {
|
||||
margin-top: var(--space-4);
|
||||
font-size: var(--text-xs);
|
||||
color: var(--color-text-tertiary, var(--color-text-secondary));
|
||||
text-align: center;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user