Say why mesh chat failed instead of retrying a minute later
Every fetch ended in an empty catch, so a failed channel list was indistinguishable from a slow one: nothing rendered, nothing was logged, and the next attempt was a backoff tick away. Errors now land in the card's status line and the console, a failure retries in two seconds, and opening the pane before the list has arrived asks for it rather than waiting.
This commit is contained in:
@@ -438,11 +438,47 @@ function _vvNcWho(id) {
|
||||
return full ? (_vvNc.hosts[id] || id) : id.toUpperCase();
|
||||
}
|
||||
|
||||
// Whatever went wrong, said out loud in the card's own status line.
|
||||
//
|
||||
// Every fetch here used to end in `.catch(() => {})`. A failed channel list was therefore
|
||||
// indistinguishable from a slow one: nothing appeared, nothing was logged, and the next attempt
|
||||
// was a backoff tick away — up to ninety seconds — so a single failed request looked exactly like
|
||||
// "the chat takes a minute to load". Silence plus a long retry is how a fast failure becomes a
|
||||
// slow mystery.
|
||||
function _vvNcErr(where, e) {
|
||||
_vvNcErrShown = true;
|
||||
const st = document.getElementById('vv-nc-status');
|
||||
const msg = (e && e.message) ? e.message : String(e || 'failed');
|
||||
if (st) { st.textContent = where + ': ' + msg; st.style.color = '#a05a2c'; }
|
||||
try { console.warn('[mesh chat] ' + where, e); } catch (_) {}
|
||||
}
|
||||
// Cleared on any success. Deliberately not conditional on "was it showing an error" — comparing a
|
||||
// computed style back to the hex that set it is a string match on the browser's rgb() rendering,
|
||||
// which is the kind of check that works until it silently does not.
|
||||
let _vvNcErrShown = false;
|
||||
function _vvNcOk() {
|
||||
if (!_vvNcErrShown) return;
|
||||
_vvNcErrShown = false;
|
||||
const st = document.getElementById('vv-nc-status');
|
||||
if (st) { st.textContent = ''; st.style.color = '#3a3a3a'; }
|
||||
}
|
||||
|
||||
// A failed attempt retries in two seconds rather than waiting for the next backoff tick. Recovery
|
||||
// should track how long the failure lasted, not how long the poller happens to be sleeping.
|
||||
function _vvNcRetrySoon() {
|
||||
if (_vvNcTimer) clearTimeout(_vvNcTimer);
|
||||
_vvNcTimer = setTimeout(() => { vvNcChans(); _vvNcSchedule(); }, 2000);
|
||||
}
|
||||
|
||||
function vvNcChans() {
|
||||
fetch('/plugins/varaverk/api/node_chat.php?_=' + Date.now())
|
||||
.then(r => r.json())
|
||||
.then(r => {
|
||||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||||
return r.json();
|
||||
})
|
||||
.then(d => {
|
||||
if (!d.ok) return;
|
||||
if (!d.ok) { _vvNcErr('channels', d.error || 'endpoint returned ok:false'); return; }
|
||||
_vvNcOk();
|
||||
_vvNc.me = d.me; _vvNc.chans = d.channels || []; _vvNc.hosts = d.hostnames || {};
|
||||
const sel = document.getElementById('vv-nc-ch');
|
||||
if (!sel) return;
|
||||
@@ -457,17 +493,27 @@ function vvNcChans() {
|
||||
const b = document.getElementById('vv-nc-badge');
|
||||
if (b) { b.hidden = !tot || _vvNcMeshOn; b.textContent = '✉ ' + tot; }
|
||||
})
|
||||
.catch(() => {});
|
||||
.catch(e => { _vvNcErr('channels', e); _vvNcRetrySoon(); });
|
||||
}
|
||||
|
||||
function vvNcSelect(ch) { _vvNc.ch = ch; vvNcLoad(); }
|
||||
|
||||
function vvNcLoad() {
|
||||
if (!_vvNc.ch) return;
|
||||
// No channel yet means the list has not landed — ask for it rather than returning silently and
|
||||
// leaving the transcript on "loading" until a backoff tick happens to fetch it. This is the path
|
||||
// taken when the mesh pane is opened before the first channel list has arrived.
|
||||
if (!_vvNc.ch) { vvNcChans(); return; }
|
||||
fetch('/plugins/varaverk/api/node_chat.php?channel=' + encodeURIComponent(_vvNc.ch) + '&_=' + Date.now())
|
||||
.then(r => r.json())
|
||||
.then(d => { if (d.ok) { _vvNc.msgs = d.messages || []; _vvNc.lastRead = d.last_read || 0; vvNcRender(); } })
|
||||
.catch(() => {});
|
||||
.then(r => {
|
||||
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||||
return r.json();
|
||||
})
|
||||
.then(d => {
|
||||
if (!d.ok) { _vvNcErr('messages', d.error || 'endpoint returned ok:false'); return; }
|
||||
_vvNcOk();
|
||||
_vvNc.msgs = d.messages || []; _vvNc.lastRead = d.last_read || 0; vvNcRender();
|
||||
})
|
||||
.catch(e => { _vvNcErr('messages', e); });
|
||||
}
|
||||
|
||||
function vvNcRender() {
|
||||
@@ -508,7 +554,7 @@ function vvNcRender() {
|
||||
method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({ csrf_token: typeof csrf_token !== 'undefined' ? csrf_token : '',
|
||||
action: 'read', channel: _vvNc.ch })
|
||||
}).catch(() => {});
|
||||
}).catch(e => _vvNcErr('read', e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -610,7 +656,7 @@ function vvNcDel(id) {
|
||||
method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||
body: new URLSearchParams({ csrf_token: typeof csrf_token !== 'undefined' ? csrf_token : '',
|
||||
action: 'delete', channel: _vvNc.ch, id })
|
||||
}).then(() => vvNcLoad()).catch(() => {});
|
||||
}).then(() => vvNcLoad()).catch(e => _vvNcErr('delete', e));
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user