Fix hang: AbortController 15s timeout + rebuild opts

- Без timeout'а fetch мог висеть вечно (CORS preflight, network)
- rebuild opts на каждый retry (раньше body повторно стрингифаил)
- refresh через try/catch (не должен ломать основной поток)
- v=10 cache bust
This commit is contained in:
root 2026-08-20 21:34:53 +00:00
parent 9236aa8bfb
commit 1f2c69c3de
2 changed files with 42 additions and 33 deletions

View File

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

View File

@ -41,41 +41,50 @@ const Store = {
}; };
async function api(path, opts = {}, _retry = 0) { async function api(path, opts = {}, _retry = 0) {
opts.headers = opts.headers || {}; // rebuild opts on every retry (body may be already stringified)
if (Store.access && !opts.headers.Authorization) { const o = { ...opts, headers: { ...(opts.headers || {}) } };
opts.headers.Authorization = `Bearer ${Store.access}`; if (Store.access && !o.headers.Authorization) {
o.headers.Authorization = `Bearer ${Store.access}`;
} }
if (opts.body && typeof opts.body === 'object' && !(opts.body instanceof FormData)) { if (o.body && typeof o.body === 'object' && !(o.body instanceof FormData)) {
opts.headers['Content-Type'] = 'application/json'; o.headers['Content-Type'] = 'application/json';
opts.body = JSON.stringify(opts.body); o.body = JSON.stringify(o.body);
} }
let r; let r;
// timeout 15s — иначе UI висит вечно
const controller = new AbortController();
const tid = setTimeout(() => controller.abort(), 15000);
try { try {
r = await fetch(API_BASE + path, opts); r = await fetch(API_BASE + path, { ...o, signal: controller.signal });
} catch (networkErr) { } catch (networkErr) {
// сеть упала — ретрай через 2с clearTimeout(tid);
console.warn('fetch network error:', networkErr);
if (networkErr.name === 'AbortError') {
throw new Error('Сервер не отвечает (таймаут 15с)');
}
if (_retry < 1) { if (_retry < 1) {
await new Promise((res) => setTimeout(res, 2000)); await new Promise((res) => setTimeout(res, 2000));
return api(path, opts, _retry + 1); return api(path, opts, _retry + 1);
} }
throw new Error('Нет связи с сервером'); throw new Error('Нет связи с сервером');
} }
clearTimeout(tid);
if (r.status === 401 && Store.refresh && !opts._isRefresh) { if (r.status === 401 && Store.refresh && !opts._isRefresh) {
// refresh try {
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}`; o.headers.Authorization = `Bearer ${Store.access}`;
return api(path, opts, _retry + 1); return api(path, opts, _retry + 1);
} else { } else {
Store.clear(); Store.clear();
} }
} catch (e) { console.warn('refresh failed', e); }
} }
if (r.status === 502 || r.status === 503 || r.status === 504) { if (r.status === 502 || r.status === 503 || r.status === 504) {
if (_retry < 1) { if (_retry < 1) {