fix: heartbeat button shows proper errors instead of failing silently
Universal: Auto Version Bump / Version Bump (push) Successful in 11s
Universal: Pre-Release / Build Pre-Release (${{ inputs.stability || github.ref_name }}) (push) Successful in 20s

- CSRF check returns JSON instead of die() with raw text
- JS parses non-JSON responses gracefully and shows server error
- Visual feedback (check/cross icon) on success/failure
- 3-second icon revert after result

Claude-Session: https://claude.ai/code/session_01Jo2JpjCwfHAh2HHRSjczKq
This commit is contained in:
2026-06-28 14:19:47 -05:00
parent d898de5bdc
commit 252ccdfa10
2 changed files with 23 additions and 7 deletions
@@ -86,7 +86,12 @@ class DisplayController extends BaseController
public function sendHeartbeat()
{
Session::checkToken() or die(Text::_('JINVALID_TOKEN'));
if (!Session::checkToken())
{
$this->jsonResponse(['success' => false, 'message' => 'Session expired — please reload the page.']);
return;
}
try
{
@@ -126,20 +126,31 @@ document.addEventListener('DOMContentLoaded', function () {
fd.append(token, '1');
fetch(url, {method: 'POST', body: fd, headers: {'X-Requested-With': 'XMLHttpRequest'}})
.then(function (r) { return r.json(); })
.then(function (r) {
return r.text().then(function (text) {
try { return JSON.parse(text); }
catch (e) { return {success: false, message: 'Server error: ' + text.substring(0, 200)}; }
});
})
.then(function (d) {
var msg = d.message || (d.success ? 'Heartbeat sent to HQ.' : 'Heartbeat failed.');
if (d.success) {
Joomla.renderMessages({message: [d.message || 'Heartbeat sent to HQ.']});
if (icon) { icon.className = 'icon-check'; icon.style.color = '#198754'; }
Joomla.renderMessages({message: [msg]});
} else {
Joomla.renderMessages({error: [d.message || 'Heartbeat failed.']});
if (icon) { icon.className = 'icon-times'; icon.style.color = '#dc3545'; }
Joomla.renderMessages({error: [msg]});
}
})
.catch(function () {
Joomla.renderMessages({error: ['Network error sending heartbeat.']});
.catch(function (err) {
if (icon) { icon.className = 'icon-times'; icon.style.color = '#dc3545'; }
Joomla.renderMessages({error: ['Heartbeat failed: ' + (err.message || 'network error')]});
})
.finally(function () {
btn.disabled = false;
if (icon) icon.className = 'icon-upload';
setTimeout(function () {
if (icon) { icon.className = 'icon-upload'; icon.style.color = ''; }
}, 3000);
});
});
}