Report every failed fetch instead of discarding it
Twenty-four fetch chains ended in an empty catch, which is not error handling but error deletion: the request fails, nothing renders, nothing is logged, and the surface sits on Loading forever. That is how the mesh chat's ReferenceError read as a slow load for as long as it did.
This commit is contained in:
@@ -92,6 +92,37 @@ function vvEscAttr(s) {
|
||||
.replace(/</g,'<').replace(/>/g,'>');
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════════════════════════
|
||||
// What a failed fetch says.
|
||||
//
|
||||
// Every fetch in this plugin used to end in `.catch(() => {})` — 24 of them. That is not error
|
||||
// handling, it is error deletion: the request fails, nothing renders, nothing is logged, and the
|
||||
// surface either sits on "Loading…" forever or silently keeps showing stale numbers. The mesh chat
|
||||
// spent an unknown amount of time "taking a minute to load" because a ReferenceError was thrown on
|
||||
// every render and swallowed here; the fault named itself the moment a catch reported it.
|
||||
//
|
||||
// Console always, because a poller that drops one tick should not shout on screen. A target
|
||||
// element when the caller has one, because a panel that will otherwise never fill has to say why.
|
||||
//
|
||||
// Global for the same reason vvEscHtml is: pages/*.php are included one at a time and each would
|
||||
// otherwise carry its own copy, which is the arrangement that lets two of them drift.
|
||||
function vvFetchErr(where, e, el) {
|
||||
const msg = (e && e.message) ? e.message : String(e || 'request failed');
|
||||
try { console.warn('[varaverk] ' + where + ' — ' + msg, e); } catch (_) {}
|
||||
if (el) {
|
||||
const n = (typeof el === 'string') ? document.getElementById(el) : el;
|
||||
if (n) { n.textContent = where + ' failed: ' + msg; n.style.color = '#a05a2c'; }
|
||||
}
|
||||
}
|
||||
|
||||
// Throws on a non-2xx instead of handing HTML to JSON.parse. Unraid answers an expired session
|
||||
// with a 302 to the login page, so without this the reported error is "Unexpected token '<'",
|
||||
// which names the symptom and hides the cause.
|
||||
function vvJson(r) {
|
||||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||||
return r.json();
|
||||
}
|
||||
|
||||
// A URL about to be put in href/src or handed to window.open. Anything that is not plainly http,
|
||||
// https or a site-relative path becomes empty — javascript: is the one that matters, and an
|
||||
// allowlist is the only way to say that without chasing encodings. include/docs.php applies the
|
||||
|
||||
@@ -1440,7 +1440,7 @@ vv_ai_profiles_script();
|
||||
fetch(API, { method: 'POST', headers: POST_HEAD,
|
||||
body: new URLSearchParams({ action: 'stop', token: t }) })
|
||||
.then(r => r.json())
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('stop generation', e));
|
||||
// Nothing is rendered from the response. The poll already owns turning a terminal state into
|
||||
// a message, and having two paths do it is how a turn ends up in the transcript twice.
|
||||
}
|
||||
@@ -1465,7 +1465,7 @@ vv_ai_profiles_script();
|
||||
noteLine('Stopped before anything was written.');
|
||||
}
|
||||
fetch(API, { method: 'POST', headers: POST_HEAD,
|
||||
body: new URLSearchParams({ action: 'clear', token }) }).catch(() => {});
|
||||
body: new URLSearchParams({ action: 'clear', token }) }).catch(e => vvFetchErr('clear job', e));
|
||||
finish();
|
||||
save();
|
||||
return;
|
||||
@@ -1475,7 +1475,7 @@ vv_ai_profiles_script();
|
||||
addAnswer(j);
|
||||
messages.push({ role: 'assistant', content: j.answer });
|
||||
fetch(API, { method: 'POST', headers: POST_HEAD,
|
||||
body: new URLSearchParams({ action: 'clear', token }) }).catch(() => {});
|
||||
body: new URLSearchParams({ action: 'clear', token }) }).catch(e => vvFetchErr('clear job', e));
|
||||
finish();
|
||||
save();
|
||||
// The answer is passed so a page can react to what was said, not just that a turn
|
||||
@@ -1519,7 +1519,7 @@ vv_ai_profiles_script();
|
||||
}) })
|
||||
.then(r => r.json())
|
||||
.then(d => { if (d.ok) { chatId = d.id; onChats(chatId); } })
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('save conversation', e));
|
||||
}
|
||||
|
||||
|
||||
@@ -2121,7 +2121,7 @@ vv_ai_profiles_script();
|
||||
|
||||
const last = pool.slice().sort((a, b) => (b.updated || 0) - (a.updated || 0))[0];
|
||||
if (last) loadChat(last.id);
|
||||
}).catch(() => {});
|
||||
}).catch(e => vvFetchErr('resume conversation', e));
|
||||
}
|
||||
|
||||
const inst = {
|
||||
@@ -2305,7 +2305,7 @@ vv_ai_profiles_script();
|
||||
function load() {
|
||||
fetch(API + '?action=chats').then(r => r.json())
|
||||
.then(d => { if (d.ok) { rows = d.chats || []; render(); } })
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('conversation list', e));
|
||||
}
|
||||
|
||||
box.addEventListener('click', e => {
|
||||
@@ -2328,7 +2328,7 @@ vv_ai_profiles_script();
|
||||
// from the store, which would silently resurrect it on the next turn.
|
||||
if (del.dataset.del === activeId && o.chat) o.chat.newChat();
|
||||
load();
|
||||
}).catch(() => {});
|
||||
}).catch(e => vvFetchErr('delete conversation', e));
|
||||
return;
|
||||
}
|
||||
const row = e.target.closest('.vv-ai-crow');
|
||||
|
||||
@@ -121,7 +121,7 @@ function vv_ai_findings_strip(string $prefix, array $kinds, string $title): void
|
||||
</div>`;
|
||||
}).join('');
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('repair findings', e));
|
||||
}
|
||||
|
||||
// Delegated, and it reloads rather than mutating the row: the store is the truth and a card that
|
||||
|
||||
@@ -618,7 +618,7 @@ vv_ai_chat_markup('vv-ai', [
|
||||
function loadBanner(live) {
|
||||
fetch(API + '?action=stats' + (live ? '&live=1' : '')).then(r => r.json())
|
||||
.then(d => { if (d.ok) renderBanner(d.stats); })
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('AI banner', e));
|
||||
}
|
||||
|
||||
// ── Bug reports ─────────────────────────────────────────────────────────
|
||||
@@ -628,7 +628,7 @@ vv_ai_chat_markup('vv-ai', [
|
||||
function loadBugs() {
|
||||
fetch(API + '?action=bugs').then(r => r.json())
|
||||
.then(d => { if (d.ok) renderBugs(d.bugs || []); })
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('AI bug list', e));
|
||||
}
|
||||
|
||||
function renderBugs(bugs) {
|
||||
@@ -750,7 +750,7 @@ vv_ai_chat_markup('vv-ai', [
|
||||
fetch(API, { method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||||
body: new URLSearchParams({ action: 'bug_close', id, open: '0' }) })
|
||||
.then(() => loadBugs()).catch(() => {});
|
||||
.then(() => loadBugs()).catch(e => vvFetchErr('AI bug report', e));
|
||||
};
|
||||
|
||||
// ── Findings & proposals ────────────────────────────────────────────────
|
||||
@@ -1108,7 +1108,7 @@ vv_ai_chat_markup('vv-ai', [
|
||||
function loadTokens() {
|
||||
fetch(API + '?action=tokens').then(r => r.json())
|
||||
.then(d => { if (d.ok) { tokData = d.tokens; renderTokens(); } })
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('AI token ledger', e));
|
||||
}
|
||||
|
||||
function renderTokens() {
|
||||
|
||||
@@ -2087,7 +2087,7 @@ function vvLoadTokens() {
|
||||
return fetch('/plugins/varaverk/api/ai.php?action=tokens')
|
||||
.then(r => r.json())
|
||||
.then(d => { if (d.ok) { vvTokData = d.tokens; vvRenderTokens(); } })
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('token ledger', e));
|
||||
}
|
||||
|
||||
function vvRenderTokens() {
|
||||
@@ -2522,7 +2522,7 @@ function vvPollStreams() {
|
||||
}
|
||||
vvRenderStreams();
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('media streams', e));
|
||||
}
|
||||
|
||||
// An unreachable partner is stated, never folded into the totals as zero. "Nobody is watching
|
||||
|
||||
@@ -1274,7 +1274,7 @@ function vvPtLoad() {
|
||||
fetch('/plugins/varaverk/api/partnership.php')
|
||||
.then(r => r.json())
|
||||
.then(_render)
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('partnership status', e));
|
||||
}
|
||||
|
||||
_vvPtReload = vvPtLoad; // expose to top-level toggle fns
|
||||
|
||||
@@ -1074,7 +1074,7 @@ function vvRyLoad() {
|
||||
fetch('/plugins/varaverk/api/rsync.php')
|
||||
.then(r => r.json())
|
||||
.then(_render)
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('rsync config', e));
|
||||
}
|
||||
|
||||
vvRyLoad();
|
||||
@@ -1499,7 +1499,7 @@ function vvRpLoad() {
|
||||
_vvRpFillForm(_vvRpSelected);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('rsync profiles', e));
|
||||
}
|
||||
|
||||
function _vvRpPopulateSelector() {
|
||||
@@ -1656,7 +1656,7 @@ function vvMsStop() {
|
||||
fetch('/plugins/varaverk/api/manual_sync.php', { method: 'POST', body: fd })
|
||||
.then(r => r.json())
|
||||
.then(() => {})
|
||||
.catch(() => {})
|
||||
.catch(e => vvFetchErr('manual sync start', e))
|
||||
.finally(() => { btn.textContent = '■ Stop'; btn.disabled = false; });
|
||||
}
|
||||
|
||||
@@ -2018,7 +2018,7 @@ function _vvMsStartPoll(token, btn, stat, out, badge, stopBtn) {
|
||||
setTimeout(() => { badge.style.display = 'none'; stat.textContent = ''; }, 6000);
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('manual sync poll', e));
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
@@ -2039,7 +2039,7 @@ function vvMsLoadRecent() {
|
||||
fetch(_MS_API + '?action=recent')
|
||||
.then(r => r.json())
|
||||
.then(d => { _vvMsRecent = (d && d.rows) || []; _msRenderRecent(); })
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('recent syncs', e));
|
||||
}
|
||||
|
||||
function _msRenderRecent() {
|
||||
@@ -2098,7 +2098,7 @@ document.addEventListener('click', async ev => {
|
||||
body: new URLSearchParams(body) })
|
||||
.then(r => r.json())
|
||||
.then(() => vvMsLoadRecent())
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('manual sync action', e));
|
||||
});
|
||||
|
||||
// Fills the form and stops. Deliberately does not run: one click away from a transfer that may
|
||||
|
||||
@@ -1567,7 +1567,7 @@ function vvPollStatus() {
|
||||
}
|
||||
vvRunningSet = nowRunning;
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('job status', e));
|
||||
}
|
||||
|
||||
function vvStartStatusPoll() {
|
||||
@@ -2785,7 +2785,7 @@ function vvBoardPoll() {
|
||||
vvUpdateErrors(d.errors ?? []);
|
||||
vvUpdatePartner(d.partner);
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('script board', e));
|
||||
}
|
||||
|
||||
function vvUpdateLocks(locks) {
|
||||
|
||||
@@ -662,7 +662,7 @@ function vvStartPhase2Watch(sinceMs) {
|
||||
const done = (d.items || []).some(i => i.id === 'partnership' && i.ok);
|
||||
if (done || Date.now() > deadline) stop();
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(e => vvFetchErr('setup checklist', e));
|
||||
};
|
||||
|
||||
// Redraw between checklist polls so the elapsed counter moves. Without it the panel is static
|
||||
|
||||
Reference in New Issue
Block a user