From c63753e592fe7f1f67ac93d25e6e3775e4bb7ef0 Mon Sep 17 00:00:00 2001 From: matitos Date: Sat, 19 Sep 2026 23:52:54 +0000 Subject: [PATCH] Stop caching index.html and speed up socket reconnect Phones offer no easy hard-refresh, so a cached index.html kept serving an old bundle after a deploy (the live feed showed "reconnecting"). index.html and the SPA catch-all now send Cache-Control: no-store, while hashed /assets stay immutable. Also retry the first socket reconnect after 1s (was 2s) and surface the close code in the header so a failure is actionable on a phone. --- frontend/nginx.conf | 15 +++++++++++++++ frontend/src/App.vue | 11 ++++++++++- frontend/src/api.js | 13 ++++++++----- 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 23cdafc..c4f4d52 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -4,7 +4,22 @@ server { root /usr/share/nginx/html; index index.html; + # Hashed Vite assets are immutable; cache them hard. + location /assets/ { + add_header Cache-Control "public, max-age=31536000, immutable"; + try_files $uri =404; + } + + # The HTML entry point must never be cached, or a phone (which offers no + # easy hard-refresh) keeps loading an old bundle after a deploy. The + # catch-all serves index.html for client-side routes, so it gets the same + # no-store header. + location = /index.html { + add_header Cache-Control "no-store"; + } + location / { + add_header Cache-Control "no-store"; try_files $uri $uri/ /index.html; } diff --git a/frontend/src/App.vue b/frontend/src/App.vue index ea31347..57c7ddb 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -36,6 +36,7 @@ const events = ref([]) const equityPoints = ref([]) const busy = ref(false) const connected = ref(false) +const socketError = ref('') let socket let pollTimer @@ -132,8 +133,15 @@ onMounted(async () => { ]) socket = api.openSocket({ onMessage: handleMessage, - onOpen: () => (connected.value = true), + onOpen: () => { + connected.value = true + socketError.value = '' + }, onClose: () => (connected.value = false), + onError: (code) => { + if (code === 1008) socketError.value = 'rejected by the server (token/auth)' + else socketError.value = `socket closed (code ${code})` + }, }) pollTimer = setInterval(refreshAll, 5000) }) @@ -161,6 +169,7 @@ onUnmounted(() => { :class="connected ? 'bg-emerald-400' : 'bg-slate-500'" > {{ connected ? 'live' : 'reconnecting' }} + ยท {{ socketError }}
diff --git a/frontend/src/api.js b/frontend/src/api.js index 6930614..6b41fa7 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -26,7 +26,7 @@ export const getPresets = () => http.get('/presets').then((r) => r.data) export const createPreset = (payload) => http.post('/presets', payload).then((r) => r.data) export const deletePreset = (id) => http.delete(`/presets/${id}`).then((r) => r.data) -export function openSocket({ onMessage, onOpen, onClose }) { +export function openSocket({ onMessage, onOpen, onClose, onError }) { const proto = window.location.protocol === 'https:' ? 'wss' : 'ws' const url = `${proto}://${window.location.host}/api/ws` let socket @@ -36,9 +36,9 @@ export function openSocket({ onMessage, onOpen, onClose }) { const schedule = () => { if (closed) return - // Back off from 2s up to 30s so a persistently failing socket (e.g. auth) - // does not hammer the server, while a brief blip still reconnects fast. - const delay = Math.min(2000 * 2 ** attempt, 30000) + // Retry at 1s, then back off up to 30s so a persistently failing socket + // does not hammer the server while a brief blip recovers immediately. + const delay = Math.min(1000 * 2 ** attempt, 30000) attempt += 1 retryTimer = setTimeout(connect, delay) } @@ -51,8 +51,11 @@ export function openSocket({ onMessage, onOpen, onClose }) { onOpen?.() } socket.onmessage = (event) => onMessage?.(JSON.parse(event.data)) - socket.onclose = () => { + socket.onclose = (event) => { onClose?.() + // 1008 = policy violation (token rejected); 1006 = abnormal close, which + // is what an auth/proxy rejection looks like to the browser. + onError?.(event.code, event.reason) schedule() } socket.onerror = () => socket.close() -- 2.54.0