fix: all data fetches use cache:'no-store' — browser-cache poisoning guard
During the nginx outage the proxy URLs briefly served the landing page WITH cacheable headers; browsers cached that HTML for the proxy URLs and kept reading it after the origin recovered (satellites 'no satellites', empty aircraft). no-store on every feed fetch means the browser cache can never serve (or store) a poisoned body for data endpoints again. The proxies are no-store server-side anyway — this closes the outage-window loophole. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
30b1381a8b
commit
fcdcef760a
@ -52,7 +52,7 @@ export function createAdsbLayer(ctx, spec) {
|
||||
const byHex = new Map();
|
||||
let anyOk = false;
|
||||
const results = await Promise.allSettled(
|
||||
spec.endpoints.map((ep) => fetch(`proxy/adsb/${ep}`).then((r) => {
|
||||
spec.endpoints.map((ep) => fetch(`proxy/adsb/${ep}`, { cache: 'no-store' }).then((r) => {
|
||||
if (!r.ok) throw new Error(String(r.status));
|
||||
return r.json().then((d) => ({ ep, d }));
|
||||
}))
|
||||
|
||||
@ -146,7 +146,7 @@ export default function create(ctx) {
|
||||
async function poll() {
|
||||
let res;
|
||||
try {
|
||||
res = await fetch(buildUrl());
|
||||
res = await fetch(buildUrl(), { cache: 'no-store' });
|
||||
} catch (err) {
|
||||
ui.setStatus('aircraft', 'network error — retrying', 'warn');
|
||||
return;
|
||||
@ -217,7 +217,7 @@ export default function create(ctx) {
|
||||
replayInFlight = true;
|
||||
lastReplayT = tSec;
|
||||
try {
|
||||
const res = await fetch(`history/aircraft?t=${tSec}`);
|
||||
const res = await fetch(`history/aircraft?t=${tSec}`, { cache: 'no-store' });
|
||||
if (isLive) return; // clock snapped back to live mid-fetch — discard
|
||||
if (res.status === 404) {
|
||||
bc.removeAll();
|
||||
|
||||
@ -34,7 +34,7 @@ export default function create(ctx) {
|
||||
if (!loaded) ui.setStatus('firms', 'fetching detections…', 'warn');
|
||||
let res;
|
||||
try {
|
||||
res = await fetch('proxy/firms');
|
||||
res = await fetch('proxy/firms', { cache: 'no-store' });
|
||||
} catch {
|
||||
ui.setStatus('firms', 'network error', 'err'); return;
|
||||
}
|
||||
|
||||
@ -120,7 +120,7 @@ export function createGeoJsonLayer(ctx, spec) {
|
||||
async function poll() {
|
||||
let res;
|
||||
try {
|
||||
res = await fetch(spec.url);
|
||||
res = await fetch(spec.url, { cache: 'no-store' });
|
||||
} catch {
|
||||
ui.setStatus(spec.id, 'network error — retrying', 'warn');
|
||||
return;
|
||||
|
||||
@ -47,7 +47,7 @@ export default function create(ctx) {
|
||||
async function poll() {
|
||||
let res;
|
||||
try {
|
||||
res = await fetch(A.proxyMil);
|
||||
res = await fetch(A.proxyMil, { cache: 'no-store' });
|
||||
} catch (err) {
|
||||
ui.setStatus('military', 'network error — retrying', 'warn');
|
||||
return;
|
||||
|
||||
@ -26,7 +26,7 @@ export default function create(ctx) {
|
||||
ui.setStatus('radio', 'loading stations…', 'warn');
|
||||
let res;
|
||||
try {
|
||||
res = await fetch('proxy/rg/ara/content/places');
|
||||
res = await fetch('proxy/rg/ara/content/places', { cache: 'no-store' });
|
||||
} catch {
|
||||
ui.setStatus('radio', 'network error', 'err'); return;
|
||||
}
|
||||
@ -89,7 +89,7 @@ export default function create(ctx) {
|
||||
p.hidden = false;
|
||||
let items = [];
|
||||
try {
|
||||
const res = await fetch(`proxy/rg/ara/content/page/${encodeURIComponent(meta.placeId)}/channels`);
|
||||
const res = await fetch(`proxy/rg/ara/content/page/${encodeURIComponent(meta.placeId)}/channels`, { cache: 'no-store' });
|
||||
const j = await res.json();
|
||||
for (const block of (j.data && j.data.content) || []) {
|
||||
for (const it of block.items || []) {
|
||||
|
||||
@ -53,7 +53,7 @@ export default function create(ctx) {
|
||||
lastKey = key;
|
||||
inFlight = true;
|
||||
try {
|
||||
const res = await fetch(`proxy/adsb/lat/${lat}/lon/${lon}/dist/${DIST_NM}`);
|
||||
const res = await fetch(`proxy/adsb/lat/${lat}/lon/${lon}/dist/${DIST_NM}`, { cache: 'no-store' });
|
||||
if (key !== lastKey) return; // camera moved again mid-fetch — stale
|
||||
if (!res.ok) { ui.setStatus('radius', `HTTP ${res.status}`, 'warn'); return; }
|
||||
const data = await res.json();
|
||||
|
||||
@ -38,7 +38,7 @@ export default function create(ctx) {
|
||||
async function load() {
|
||||
ui.setStatus('roadcams', 'loading cameras…', 'warn');
|
||||
const results = await Promise.allSettled(REGIONS.map(async (r) => {
|
||||
const res = await fetch(`proxy/cams/${r.id}`);
|
||||
const res = await fetch(`proxy/cams/${r.id}`, { cache: 'no-store' });
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
return { region: r, cams: await res.json() };
|
||||
}));
|
||||
|
||||
@ -120,7 +120,7 @@ export default function create(ctx) {
|
||||
async function run() {
|
||||
const sources = CONFIG.satellites.sources;
|
||||
const settled = await Promise.allSettled(
|
||||
sources.map((s) => fetch(`${CONFIG.proxy.celestrak}?${s.q}`).then((res) => {
|
||||
sources.map((s) => fetch(`${CONFIG.proxy.celestrak}?${s.q}`, { cache: 'no-store' }).then((res) => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
return res.text();
|
||||
}))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user