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();
|
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() {
|
function vvNcChans() {
|
||||||
fetch('/plugins/varaverk/api/node_chat.php?_=' + Date.now())
|
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 => {
|
.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 || {};
|
_vvNc.me = d.me; _vvNc.chans = d.channels || []; _vvNc.hosts = d.hostnames || {};
|
||||||
const sel = document.getElementById('vv-nc-ch');
|
const sel = document.getElementById('vv-nc-ch');
|
||||||
if (!sel) return;
|
if (!sel) return;
|
||||||
@@ -457,17 +493,27 @@ function vvNcChans() {
|
|||||||
const b = document.getElementById('vv-nc-badge');
|
const b = document.getElementById('vv-nc-badge');
|
||||||
if (b) { b.hidden = !tot || _vvNcMeshOn; b.textContent = '✉ ' + tot; }
|
if (b) { b.hidden = !tot || _vvNcMeshOn; b.textContent = '✉ ' + tot; }
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(e => { _vvNcErr('channels', e); _vvNcRetrySoon(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
function vvNcSelect(ch) { _vvNc.ch = ch; vvNcLoad(); }
|
function vvNcSelect(ch) { _vvNc.ch = ch; vvNcLoad(); }
|
||||||
|
|
||||||
function 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())
|
fetch('/plugins/varaverk/api/node_chat.php?channel=' + encodeURIComponent(_vvNc.ch) + '&_=' + Date.now())
|
||||||
.then(r => r.json())
|
.then(r => {
|
||||||
.then(d => { if (d.ok) { _vvNc.msgs = d.messages || []; _vvNc.lastRead = d.last_read || 0; vvNcRender(); } })
|
if (!r.ok) throw new Error('HTTP ' + r.status);
|
||||||
.catch(() => {});
|
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() {
|
function vvNcRender() {
|
||||||
@@ -508,7 +554,7 @@ function vvNcRender() {
|
|||||||
method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||||
body: new URLSearchParams({ csrf_token: typeof csrf_token !== 'undefined' ? csrf_token : '',
|
body: new URLSearchParams({ csrf_token: typeof csrf_token !== 'undefined' ? csrf_token : '',
|
||||||
action: 'read', channel: _vvNc.ch })
|
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'},
|
method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'},
|
||||||
body: new URLSearchParams({ csrf_token: typeof csrf_token !== 'undefined' ? csrf_token : '',
|
body: new URLSearchParams({ csrf_token: typeof csrf_token !== 'undefined' ? csrf_token : '',
|
||||||
action: 'delete', channel: _vvNc.ch, id })
|
action: 'delete', channel: _vvNc.ch, id })
|
||||||
}).then(() => vvNcLoad()).catch(() => {});
|
}).then(() => vvNcLoad()).catch(e => _vvNcErr('delete', e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user