Fix: cache-bust + retry + graceful 401

- ?v=8 на всех JS (CF cache bypass)
- api(): retry при 502/503/504 и network error
- api(): защита от infinite loop при refresh (флаг _isRefresh)
- App.init: при протухшем токене сразу showWelcome (без showError)
This commit is contained in:
root 2026-08-20 21:31:56 +00:00
parent 716f074ea7
commit 7feb693df9
3 changed files with 36 additions and 15 deletions

View File

@ -34,15 +34,15 @@
</button> </button>
</nav> </nav>
</div> </div>
<script src="/js/tg.js"></script> <script src="/js/tg.js?v=8"></script>
<script src="/js/api.js"></script> <script src="/js/api.js?v=8"></script>
<script src="/js/views/map.js"></script> <script src="/js/views/map.js?v=8"></script>
<script src="/js/views/chats.js"></script> <script src="/js/views/chats.js?v=8"></script>
<script src="/js/views/chat.js"></script> <script src="/js/views/chat.js?v=8"></script>
<script src="/js/views/profile.js"></script> <script src="/js/views/profile.js?v=8"></script>
<script src="/js/views/review.js"></script> <script src="/js/views/review.js?v=8"></script>
<script src="/js/views/settings.js"></script> <script src="/js/views/settings.js?v=8"></script>
<script src="/js/views/onboarding.js"></script> <script src="/js/views/onboarding.js?v=8"></script>
<script src="/js/app.js"></script> <script src="/js/app.js?v=8"></script>
</body> </body>
</html> </html>

View File

@ -40,7 +40,7 @@ const Store = {
}, },
}; };
async function api(path, opts = {}) { async function api(path, opts = {}, _retry = 0) {
opts.headers = opts.headers || {}; opts.headers = opts.headers || {};
if (Store.access && !opts.headers.Authorization) { if (Store.access && !opts.headers.Authorization) {
opts.headers.Authorization = `Bearer ${Store.access}`; opts.headers.Authorization = `Bearer ${Store.access}`;
@ -49,23 +49,41 @@ async function api(path, opts = {}) {
opts.headers['Content-Type'] = 'application/json'; opts.headers['Content-Type'] = 'application/json';
opts.body = JSON.stringify(opts.body); opts.body = JSON.stringify(opts.body);
} }
let r = await fetch(API_BASE + path, opts); let r;
if (r.status === 401 && Store.refresh) { try {
r = await fetch(API_BASE + path, opts);
} catch (networkErr) {
// сеть упала — ретрай через 2с
if (_retry < 1) {
await new Promise((res) => setTimeout(res, 2000));
return api(path, opts, _retry + 1);
}
throw new Error('Нет связи с сервером');
}
if (r.status === 401 && Store.refresh && !opts._isRefresh) {
// refresh // refresh
const r2 = await fetch(API_BASE + '/api/v1/auth/refresh', { const r2 = await fetch(API_BASE + '/api/v1/auth/refresh', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ Refresh: Store.refresh }), body: JSON.stringify({ Refresh: Store.refresh }),
_isRefresh: true,
}); });
if (r2.ok) { if (r2.ok) {
const tokens = await r2.json(); const tokens = await r2.json();
Store.save(tokens); Store.save(tokens);
opts.headers.Authorization = `Bearer ${Store.access}`; opts.headers.Authorization = `Bearer ${Store.access}`;
r = await fetch(API_BASE + path, opts); return api(path, opts, _retry + 1);
} else { } else {
Store.clear(); Store.clear();
} }
} }
if (r.status === 502 || r.status === 503 || r.status === 504) {
if (_retry < 1) {
await new Promise((res) => setTimeout(res, 2000));
return api(path, opts, _retry + 1);
}
throw new Error('Сервер перезапускается, попробуй через минуту');
}
const text = await r.text(); const text = await r.text();
let data = null; let data = null;
try { data = text ? JSON.parse(text) : null; } catch { data = text; } try { data = text ? JSON.parse(text) : null; } catch { data = text; }

View File

@ -41,13 +41,16 @@ const App = {
this.showError('Не удалось войти через Telegram: ' + e.message); this.showError('Не удалось войти через Telegram: ' + e.message);
} }
} else if (API.Store.access) { } else if (API.Store.access) {
// есть токен — пробуем
try { try {
await API.Auth.me(); await API.Auth.me();
await this.checkConsent(); await this.checkConsent();
this.showView('map'); this.showView('map');
} catch (e) { } catch (e) {
// токен протух или сеть — сразу на welcome, без показа ошибки
console.warn('init me() failed:', e);
API.Store.clear(); API.Store.clear();
this.showError('Сессия истекла: ' + e.message); this.showWelcome();
} }
} else { } else {
// Вне Telegram — показать email login (но сначала согласия) // Вне Telegram — показать email login (но сначала согласия)