mirror of
https://git.ianrenton.com/ian/spothole.git
synced 2026-06-26 22:47:25 +00:00
Update map and bands pages to use SSE endpoint after initial load. Closes #43.
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
// How often to query the server?
|
||||
const REFRESH_INTERVAL_SEC = 60;
|
||||
// SSE connection for live spot updates
|
||||
let evtSource;
|
||||
let restartSSEOnErrorTimeoutId;
|
||||
// Debounce timer so rapid SSE bursts only trigger one updateBands() call, as these could trigger a flash of the user
|
||||
// visible content
|
||||
let updateBandsDebounceId;
|
||||
|
||||
// A couple of constants that must match what's in CSS. We need to know them before the content actually renders, so we
|
||||
// can't just ask the elements themselves for their dimensions.
|
||||
@@ -12,19 +16,62 @@ BAND_COLUMN_SPOT_DIV_HEIGHT_PX = BAND_COLUMN_FONT_SIZE * 1.6;
|
||||
|
||||
// Load spots and populate the bands display.
|
||||
function loadSpots() {
|
||||
$.getJSON('/api/v1/spots' + buildQueryString(false), function (jsonData) {
|
||||
// Store last updated time
|
||||
lastUpdateTime = moment.utc();
|
||||
updateRefreshDisplay();
|
||||
// Close any existing SSE connection before fetching fresh data
|
||||
if (evtSource != null) {
|
||||
evtSource.close();
|
||||
}
|
||||
$.getJSON('/api/v1/spots' + buildQueryString(), function (jsonData) {
|
||||
// Store data
|
||||
spots = jsonData;
|
||||
// Update bands display
|
||||
updateBands();
|
||||
// Start the ongoing SSE connection
|
||||
startSSEConnection();
|
||||
});
|
||||
}
|
||||
|
||||
// Build a query string for the API, based on the filters that the user has selected.
|
||||
function buildQueryString(includeCredentials) {
|
||||
// Start an SSE connection to receive new spots as they arrive.
|
||||
function startSSEConnection() {
|
||||
if (evtSource != null) {
|
||||
evtSource.close();
|
||||
}
|
||||
evtSource = new EventSource('/api/v1/spots/stream' + buildQueryString());
|
||||
|
||||
evtSource.onmessage = function (event) {
|
||||
const newSpot = JSON.parse(event.data);
|
||||
|
||||
// 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, 500);
|
||||
};
|
||||
|
||||
evtSource.onerror = function () {
|
||||
if (evtSource != null) {
|
||||
evtSource.close();
|
||||
}
|
||||
clearTimeout(restartSSEOnErrorTimeoutId);
|
||||
restartSSEOnErrorTimeoutId = setTimeout(startSSEConnection, 1000);
|
||||
};
|
||||
}
|
||||
|
||||
// Remove spots from the display that are older than the selected max age.
|
||||
function expireOldSpots() {
|
||||
const maxAgeSeconds = parseInt($("#max-spot-age option:selected").val());
|
||||
const cutoff = (Date.now() / 1000) - maxAgeSeconds;
|
||||
const before = spots.length;
|
||||
spots = spots.filter(s => s["time"] && s["time"] >= cutoff);
|
||||
if (spots.length !== before) {
|
||||
updateBands();
|
||||
}
|
||||
}
|
||||
|
||||
// Build a query string for the API, based on the filters that the user has selected. There's no need for credentials
|
||||
// in the bands page's version of this, because nothing QRZ.com/HamQTH can provide will affect the display.
|
||||
function buildQueryString() {
|
||||
let str = "?";
|
||||
["dx_continent", "de_continent", "mode", "source", "band", "sig"].forEach(fn => {
|
||||
if (!allFilterOptionsSelected(fn)) {
|
||||
@@ -34,9 +81,6 @@ function buildQueryString(includeCredentials) {
|
||||
str = str + "max_age=" + $("#max-spot-age option:selected").val();
|
||||
// Additional filters for the bands view: No dupes, no QRT
|
||||
str = str + "&dedupe=true&allow_qrt=false";
|
||||
if (includeCredentials) {
|
||||
str = str + getCredentialQueryString();
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -257,22 +301,28 @@ function loadOptions() {
|
||||
// Load settings from settings storage now all the controls are available
|
||||
loadSettings();
|
||||
|
||||
// Load spots and set up the timer
|
||||
// Load spots and start SSE for live updates
|
||||
loadSpots();
|
||||
setInterval(loadSpots, REFRESH_INTERVAL_SEC * 1000);
|
||||
// Set up spot expiry checker
|
||||
setInterval(expireOldSpots, 60 * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
// Method called when any display property is changed to reload the map and persist the display settings.
|
||||
// Method called when any display property is changed to reload the bands display and persist settings.
|
||||
function displayUpdated() {
|
||||
updateMap();
|
||||
updateBands();
|
||||
saveSettings();
|
||||
}
|
||||
|
||||
// Startup
|
||||
$(document).ready(function () {
|
||||
// Close SSE connection cleanly when navigating away
|
||||
window.addEventListener('beforeunload', function () {
|
||||
if (evtSource != null) {
|
||||
evtSource.close();
|
||||
}
|
||||
});
|
||||
|
||||
// Call loadOptions(), this will then trigger loading spots and setting up timers.
|
||||
loadOptions();
|
||||
// Update the refresh timing display every second
|
||||
setInterval(updateRefreshDisplay, 1000);
|
||||
});
|
||||
Reference in New Issue
Block a user