mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-08-15 08:57:31 +00:00
Fix missing headers in SSE queries
This commit is contained in:
+3
-2
@@ -6,9 +6,10 @@ let lastUpdateTime;
|
||||
// Storage for the alert data that the server gives us.
|
||||
let alerts = [];
|
||||
|
||||
// Load alerts and populate the table.
|
||||
// Load alerts and populate the table. No need for QRZ/HamQTH credential headers as these won't add any useful data
|
||||
// to alerts
|
||||
function loadAlerts() {
|
||||
$.ajax({url: '/api/v2/alerts' + buildQueryString(), dataType: 'json', headers: getCredentialHeaders(), success: function (jsonData) {
|
||||
$.ajax({url: '/api/v2/alerts' + buildQueryString(), dataType: 'json', success: function (jsonData) {
|
||||
// Store last updated time
|
||||
lastUpdateTime = moment.utc();
|
||||
updateRefreshDisplay();
|
||||
|
||||
+30
-25
@@ -1,6 +1,5 @@
|
||||
// SSE connection for live spot updates
|
||||
let evtSource;
|
||||
let restartSSEOnErrorTimeoutId;
|
||||
let sseAbortController = null;
|
||||
// Debounce timer so rapid SSE bursts only trigger one updateBands() call, as these could trigger a flash of the user
|
||||
// visible content
|
||||
let updateBandsDebounceId;
|
||||
@@ -17,10 +16,10 @@ BAND_COLUMN_SPOT_DIV_HEIGHT_PX = BAND_COLUMN_FONT_SIZE * 1.6;
|
||||
// Load spots and populate the bands display.
|
||||
function loadSpots() {
|
||||
// Close any existing SSE connection before fetching fresh data
|
||||
if (evtSource != null) {
|
||||
evtSource.close();
|
||||
if (sseAbortController != null) {
|
||||
sseAbortController.abort();
|
||||
}
|
||||
$.ajax({url: '/api/v2/spots' + buildQueryString(), dataType: 'json', headers: getCredentialHeaders(), success: function (jsonData) {
|
||||
$.ajax({url: '/api/v2/spots' + buildQueryString(), dataType: 'json', success: function (jsonData) {
|
||||
// Store data
|
||||
spots = jsonData;
|
||||
// Update bands display
|
||||
@@ -33,30 +32,36 @@ function loadSpots() {
|
||||
|
||||
// Start an SSE connection to receive new spots as they arrive.
|
||||
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) {
|
||||
const newSpot = JSON.parse(event.data);
|
||||
// No need to include QRZ/HamQTH credentials because the information wouldn't be displayed on the bands panel anyway
|
||||
fetchEventSource('/api/v2/spots/stream' + buildQueryString(), {
|
||||
signal: sseAbortController.signal,
|
||||
openWhenHidden: true,
|
||||
|
||||
// Replace any existing spot for this callsign
|
||||
spots = spots.filter(s => newSpot["dx_call"] !== s["dx_call"]);
|
||||
spots.unshift(newSpot);
|
||||
onmessage(event) {
|
||||
if (!event.data) {
|
||||
return; // heartbeat/keep-alive, nothing to do
|
||||
}
|
||||
|
||||
// Debounce. Wait 500ms after the last message before re-rendering to avoid too many ugly flashes
|
||||
clearTimeout(updateBandsDebounceId);
|
||||
updateBandsDebounceId = setTimeout(updateBands, 5000);
|
||||
};
|
||||
const newSpot = JSON.parse(event.data);
|
||||
|
||||
evtSource.onerror = function () {
|
||||
if (evtSource != null) {
|
||||
evtSource.close();
|
||||
// Replace any existing spot for this callsign
|
||||
spots = spots.filter(s => newSpot["dx_call"] !== s["dx_call"]);
|
||||
spots.unshift(newSpot);
|
||||
|
||||
// Debounce. Wait 500ms after the last message before re-rendering to avoid too many ugly flashes
|
||||
clearTimeout(updateBandsDebounceId);
|
||||
updateBandsDebounceId = setTimeout(updateBands, 5000);
|
||||
},
|
||||
onerror(err) {
|
||||
console.error('SSE error:', err);
|
||||
return 1000;
|
||||
}
|
||||
clearTimeout(restartSSEOnErrorTimeoutId);
|
||||
restartSSEOnErrorTimeoutId = setTimeout(startSSEConnection, 1000);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Remove spots from the display that are older than the selected max age.
|
||||
@@ -319,8 +324,8 @@ function displayUpdated() {
|
||||
$(document).ready(function () {
|
||||
// Close SSE connection cleanly when navigating away
|
||||
window.addEventListener('beforeunload', function () {
|
||||
if (evtSource != null) {
|
||||
evtSource.close();
|
||||
if (sseAbortController != null) {
|
||||
sseAbortController.abort();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+49
-38
@@ -9,8 +9,7 @@ const ITU_ZONES_COLOR_DARK = 'rgba(120, 120, 60, 1.0)';
|
||||
const WAB_WAI_GRID_COLOR_DARK = 'rgba(60, 60, 120, 1.0)';
|
||||
|
||||
// SSE connection for live spot updates
|
||||
let evtSource;
|
||||
let restartSSEOnErrorTimeoutId;
|
||||
let sseAbortController = null;
|
||||
// Map dx_call to a pair of marker & geodesic line, so we can de-duplicate spots as they arrive and only show
|
||||
// one marker per dx_call. The key here is actually dx_call + SSID if the spot gives us an SSID; this is mostly for
|
||||
// if APRS spots are enabled so we can ID a home and mobile station separately rather than our marker oscillating
|
||||
@@ -35,8 +34,8 @@ let firstLoad = true;
|
||||
// Load spots and populate the map.
|
||||
function loadSpots() {
|
||||
// Close any existing SSE connection before fetching fresh data
|
||||
if (evtSource != null) {
|
||||
evtSource.close();
|
||||
if (sseAbortController != null) {
|
||||
sseAbortController.abort();
|
||||
}
|
||||
// Including QRZ/HamQTH lookups to improve positions causes the load to be really slow, so on first load
|
||||
// the user would be waiting ages without data and will think it's broken. We therefore load in several
|
||||
@@ -45,7 +44,8 @@ function loadSpots() {
|
||||
// 2) (If we have credentials) reload with them, replacing what's already there,
|
||||
// 3) Subscribe to the SSE endpoint (with credentials if we have them) so that updates come with augmented
|
||||
// data if they can.
|
||||
$.ajax({url: '/api/v2/spots' + buildQueryString(), dataType: 'json', headers: getCredentialHeaders(), success: function (jsonData) {
|
||||
$.ajax({
|
||||
url: '/api/v2/spots' + buildQueryString(), dataType: 'json', success: function (jsonData) {
|
||||
// Store data
|
||||
spots = jsonData;
|
||||
// Update map
|
||||
@@ -54,14 +54,19 @@ function loadSpots() {
|
||||
terminator.setTime();
|
||||
}
|
||||
// Check if we have any credentials to use
|
||||
if (getCredentialQueryString() !== "") {
|
||||
if (Object.keys(getCredentialHeaders()).length > 0) {
|
||||
// OK, we have credentials and have loaded once without them so the user has a basic map. Now reload
|
||||
// with the credentials and replace what's on the map, so we can improve the data.
|
||||
$.getJSON('/api/v1/spots' + buildQueryString(true), function (jsonData2) {
|
||||
spots = jsonData2;
|
||||
updateMap();
|
||||
// Now start the ongoing SSE connection
|
||||
startSSEConnection();
|
||||
$.ajax({
|
||||
url: '/api/v2/spots' + buildQueryString(),
|
||||
dataType: 'json',
|
||||
headers: getCredentialHeaders(),
|
||||
success: function(jsonData2) {
|
||||
spots = jsonData2;
|
||||
updateMap();
|
||||
// Now start the ongoing SSE connection
|
||||
startSSEConnection();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// We had no credentials with which to augment the data anyway, so just start the SSE connection
|
||||
@@ -73,37 +78,43 @@ function loadSpots() {
|
||||
|
||||
// Start the SSE connection to receive new spots as they arrive
|
||||
function startSSEConnection() {
|
||||
if (evtSource != null) {
|
||||
evtSource.close();
|
||||
if (sseAbortController != null) {
|
||||
sseAbortController.abort();
|
||||
}
|
||||
sseAbortController = new AbortController();
|
||||
|
||||
// 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.
|
||||
evtSource = new EventSource('/api/v1/spots/stream' + buildQueryString(true));
|
||||
fetchEventSource('/api/v2/spots/stream' + buildQueryString(), {
|
||||
headers: getCredentialHeaders(),
|
||||
signal: sseAbortController.signal,
|
||||
openWhenHidden: true,
|
||||
|
||||
evtSource.onmessage = function (event) {
|
||||
const newSpot = JSON.parse(event.data);
|
||||
const key = spotKey(newSpot);
|
||||
onmessage(event) {
|
||||
if (!event.data) {
|
||||
return; // heartbeat/keep-alive, nothing to do
|
||||
}
|
||||
|
||||
// Remove existing marker/geodesic for this callsign if present
|
||||
removeSpotFromMap(key);
|
||||
spots = spots.filter(s => spotKey(s) !== key);
|
||||
const newSpot = JSON.parse(event.data);
|
||||
const key = spotKey(newSpot);
|
||||
|
||||
// Skip spots with no map coordinates
|
||||
if (newSpot["dx_latitude"] == null || newSpot["dx_longitude"] == null) {
|
||||
return;
|
||||
// Remove existing marker/geodesic for this callsign if present
|
||||
removeSpotFromMap(key);
|
||||
spots = spots.filter(s => spotKey(s) !== key);
|
||||
|
||||
// Skip spots with no map coordinates
|
||||
if (newSpot["dx_latitude"] == null || newSpot["dx_longitude"] == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add to data store and map
|
||||
spots.unshift(newSpot);
|
||||
addSpotToMap(newSpot);
|
||||
},
|
||||
onerror(err) {
|
||||
console.error('SSE error:', err);
|
||||
return 1000;
|
||||
}
|
||||
|
||||
// Add to data store and map
|
||||
spots.unshift(newSpot);
|
||||
addSpotToMap(newSpot);
|
||||
};
|
||||
|
||||
evtSource.onerror = function () {
|
||||
if (evtSource != null) {
|
||||
evtSource.close();
|
||||
}
|
||||
clearTimeout(restartSSEOnErrorTimeoutId);
|
||||
restartSSEOnErrorTimeoutId = setTimeout(startSSEConnection, 1000);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// Remove spots from the map that are older than the selected max age.
|
||||
@@ -603,8 +614,8 @@ function displayIntroBox() {
|
||||
$(document).ready(function () {
|
||||
// Close SSE connection cleanly when navigating away
|
||||
window.addEventListener('beforeunload', function () {
|
||||
if (evtSource != null) {
|
||||
evtSource.close();
|
||||
if (sseAbortController != null) {
|
||||
sseAbortController.abort();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+54
-48
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user