Fix missing headers in SSE queries

This commit is contained in:
Ian Renton
2026-08-14 23:47:22 +01:00
parent efec8e220e
commit ef2c23c8b6
16 changed files with 358 additions and 129 deletions
+54 -48
View File
@@ -1,26 +1,25 @@
// SSE event source
let evtSource;
let restartSSEOnErrorTimeoutId;
let sseAbortController = null;
// Table row count, to alternate shading
let rowCount = 0;
// Set up a listener to close the SSE connection nicely when we navigate away from the page, to prevent console errors
// and keep things nice and tidy for the server.
window.addEventListener('beforeunload', function () {
if (evtSource != null) {
evtSource.close();
if (sseAbortController != null) {
sseAbortController.abort();
}
});
// Load spots and populate the table.
function loadSpots() {
// If we have an ongoing SSE connection, stop it so it doesn't interfere with our reload
if (evtSource != null) {
evtSource.close();
if (sseAbortController != null) {
sseAbortController.abort();
}
// Make the new query
$.ajax({url: '/api/v2/spots' + buildQueryString(), dataType: 'json', headers: getCredentialHeaders(), success: function (jsonData) {
// Make the new query. No credential headers on the first load to keep things quick
$.ajax({url: '/api/v2/spots' + buildQueryString(), dataType: 'json', success: function (jsonData) {
// Store data
spots = jsonData;
// Update table
@@ -36,53 +35,60 @@ function loadSpots() {
// Start an SSE connection (closing an existing one if it exists). This will then be used to add to the table on the
// fly.
function startSSEConnection() {
if (evtSource != null) {
evtSource.close();
if (sseAbortController != null) {
sseAbortController.abort();
}
evtSource = new EventSource('/api/v2/spots/stream' + buildQueryString());
sseAbortController = new AbortController();
evtSource.onmessage = function (event) {
// Get the new spot
const newSpot = JSON.parse(event.data);
// Awful fudge to ensure new incoming spots at the top of the list don't have timestamps that make them look
// like they belong further down the list. If the spot is older than the latest one we already have, bump its
// time up to match it. This isn't great but since we poll spot providers every 2 minutes anyway, it shouldn't
// be too far wrong.
if (spots.length > 0) {
newSpot["time"] = Math.max(newSpot["time"], Math.max(...spots.map(s => s["time"])))
}
// SSE is going to fetch only a few spots at a time, so now we include QRZ/HamQTH credentials because the delay won't be significant.
fetchEventSource('/api/v2/spots/stream' + buildQueryString(), {
headers: getCredentialHeaders(),
signal: sseAbortController.signal,
openWhenHidden: true,
// Add spot to internal data store
spots.unshift(newSpot);
// Work out if we need to remove an old spot
if (spots.length > $("#spots_to_fetch option:selected").val()) {
spots = spots.slice(0, -1);
// Drop oldest spot off the end of the table. This is two rows because of the mobile view extra rows
$("#table tbody tr").last().remove();
$("#table tbody tr").last().remove();
}
// If we had zero spots before (i.e. one now), the table will have a "No spots" row that we need to remove now
// that we have one.
if (spots.length === 1) {
$("#table tbody tr").last().remove();
}
onmessage(event) {
if (!event.data) {
return; // heartbeat/keep-alive, nothing to do
}
// Add the new spot to table
addSpotToTopOfTable(newSpot, true);
// Get the new spot
const newSpot = JSON.parse(event.data);
// Awful fudge to ensure new incoming spots at the top of the list don't have timestamps that make them look
// like they belong further down the list. If the spot is older than the latest one we already have, bump its
// time up to match it. This isn't great but since we poll spot providers every 2 minutes anyway, it shouldn't
// be too far wrong.
if (spots.length > 0) {
newSpot["time"] = Math.max(newSpot["time"], Math.max(...spots.map(s => s["time"])))
}
// Ping if we need to
if ($("#pingOnNewSpots")[0].checked) {
new Audio("/audio/ping.mp3").play();
}
};
// Add spot to internal data store
spots.unshift(newSpot);
// Work out if we need to remove an old spot
if (spots.length > $("#spots_to_fetch option:selected").val()) {
spots = spots.slice(0, -1);
// Drop oldest spot off the end of the table. This is two rows because of the mobile view extra rows
$("#table tbody tr").last().remove();
$("#table tbody tr").last().remove();
}
// If we had zero spots before (i.e. one now), the table will have a "No spots" row that we need to remove now
// that we have one.
if (spots.length === 1) {
$("#table tbody tr").last().remove();
}
evtSource.onerror = function () {
if (evtSource != null) {
evtSource.close();
// Add the new spot to table
addSpotToTopOfTable(newSpot, true);
// Ping if we need to
if ($("#pingOnNewSpots")[0].checked) {
new Audio("/audio/ping.mp3").play();
}
},
onerror(err) {
console.error('SSE error:', err);
return 1000;
}
clearTimeout(restartSSEOnErrorTimeoutId)
restartSSEOnErrorTimeoutId = setTimeout(startSSEConnection, 1000);
};
});
}
// Build a query string for the API, based on the filters that the user has selected.