Fix: debounce login + try/catch TG.showAlert

- emailLogin: защита от двойного клика (_loginInProgress)
- api(): timeout 20s + cache:'no-store' + redirect:'follow'
- tg.js: try/catch вокруг showAlert/showConfirm (WebAppMethodUnsupported fix)
This commit is contained in:
root 2026-08-20 21:52:52 +00:00
parent ae652e37f5
commit 36a81a05ec
3 changed files with 25 additions and 7 deletions

View File

@ -58,13 +58,19 @@ async function api(path, opts = {}, _retry = 0) {
o.body = JSON.stringify(o.body); o.body = JSON.stringify(o.body);
} }
let r; let r;
// timeout 15s — иначе UI висит вечно // timeout 20s
const controller = new AbortController(); const controller = new AbortController();
const tid = setTimeout(() => controller.abort(), 15000); const tid = setTimeout(() => controller.abort(), 20000);
const t0 = Date.now(); const t0 = Date.now();
try { try {
// credentials:'include' — браузер шлёт cookies (HttpOnly buhapp_at) // credentials:'include' — браузер шлёт cookies (HttpOnly buhapp_at)
r = await fetch(API_BASE + path, { ...o, credentials: 'include', signal: controller.signal }); r = await fetch(API_BASE + path, {
...o,
credentials: 'include',
signal: controller.signal,
cache: 'no-store',
redirect: 'follow',
});
} catch (networkErr) { } catch (networkErr) {
clearTimeout(tid); clearTimeout(tid);
const ms = Date.now() - t0; const ms = Date.now() - t0;

View File

@ -243,6 +243,8 @@ const App = {
}, },
async emailLogin() { async emailLogin() {
if (this._loginInProgress) return; // защита от двойного клика
this._loginInProgress = true;
const email = document.getElementById('el-email').value.trim(); const email = document.getElementById('el-email').value.trim();
const pass = document.getElementById('el-pass').value; const pass = document.getElementById('el-pass').value;
const btn = document.getElementById('el-login'); const btn = document.getElementById('el-login');
@ -260,6 +262,8 @@ const App = {
if (hint) { hint.textContent = '⚠️ ' + e.message; hint.style.color = 'var(--danger)'; } if (hint) { hint.textContent = '⚠️ ' + e.message; hint.style.color = 'var(--danger)'; }
TG_APP.showAlert('Ошибка входа: ' + e.message); TG_APP.showAlert('Ошибка входа: ' + e.message);
if (btn) { btn.disabled = false; btn.textContent = 'Войти'; } if (btn) { btn.disabled = false; btn.textContent = 'Войти'; }
} finally {
this._loginInProgress = false;
} }
}, },

View File

@ -21,12 +21,20 @@ window.TG_APP = {
} catch (e) {} } catch (e) {}
}, },
showAlert: (msg) => { showAlert: (msg) => {
if (TG?.showAlert) TG.showAlert(msg); try {
else alert(msg); if (TG?.showAlert) TG.showAlert(String(msg));
else alert(String(msg));
} catch (e) {
try { alert(String(msg)); } catch {}
}
}, },
showConfirm: (msg) => { showConfirm: (msg) => {
if (TG?.showConfirm) return new Promise((r) => TG.showConfirm(msg, r)); try {
return Promise.resolve(confirm(msg)); if (TG?.showConfirm) return new Promise((r) => TG.showConfirm(msg, r));
return Promise.resolve(confirm(String(msg)));
} catch (e) {
try { return Promise.resolve(confirm(String(msg))); } catch { return Promise.resolve(false); }
}
}, },
theme: () => TG?.themeParams || {}, theme: () => TG?.themeParams || {},