Fix false stale Fleet reports caused by clock skew
This commit is contained in:
@@ -46,15 +46,36 @@ export const statusReceivedAt = writable(0);
|
||||
// startServerStream() call in the browser.
|
||||
const _uiNow = writable(Date.now());
|
||||
export const uiNow = readonly(_uiNow);
|
||||
let serverClock: { time: number; receivedAt: number } | null = null;
|
||||
|
||||
// Report timestamps belong to the backend clock, which can differ from the
|
||||
// browser's wall clock. Advance that clock locally even if updates stop.
|
||||
export function serverTimeNow(): number {
|
||||
return serverClock ? serverClock.time + performance.now() - serverClock.receivedAt : Date.now();
|
||||
}
|
||||
|
||||
export function syncServerTime(serverNow?: number): void {
|
||||
if (typeof serverNow === 'number' && Number.isFinite(serverNow)) {
|
||||
// A delayed HTTP snapshot must not rewind time behind a newer stream
|
||||
// update and make that update look like it came from the future.
|
||||
const time = serverClock ? Math.max(serverNow, serverTimeNow()) : serverNow;
|
||||
serverClock = { time, receivedAt: performance.now() };
|
||||
}
|
||||
// Refresh immediately: a suspended tab's interval may be well behind.
|
||||
_uiNow.set(serverTimeNow());
|
||||
}
|
||||
|
||||
let tickInterval: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
let source: EventSource | null = null;
|
||||
|
||||
export function startServerStream(): (() => void) | undefined {
|
||||
if (!browser || source) return;
|
||||
syncServerTime();
|
||||
source = new EventSource('/api/events/servers');
|
||||
source.addEventListener('server-telemetry', (evt) => {
|
||||
const data = JSON.parse((evt as MessageEvent).data) as { entries: GameTelemetry[]; snapshot?: boolean };
|
||||
const data = JSON.parse((evt as MessageEvent).data) as { entries: GameTelemetry[]; snapshot?: boolean; serverNow?: number };
|
||||
syncServerTime(data.serverNow);
|
||||
telemetryByServer.update((previous) => {
|
||||
const next = data.snapshot ? new Map<string, GameTelemetry>() : new Map(previous);
|
||||
for (const entry of data.entries) next.set(entry.server, entry);
|
||||
@@ -62,7 +83,8 @@ export function startServerStream(): (() => void) | undefined {
|
||||
});
|
||||
});
|
||||
source.addEventListener('server-status', (evt) => {
|
||||
const data = JSON.parse((evt as MessageEvent).data) as { entries: ServerStatus[] };
|
||||
const data = JSON.parse((evt as MessageEvent).data) as { entries: ServerStatus[]; serverNow?: number };
|
||||
syncServerTime(data.serverNow);
|
||||
statusByServer.update(() => {
|
||||
const m = new Map<string, ServerStatus>();
|
||||
for (const e of data.entries) m.set(e.server, e);
|
||||
@@ -71,7 +93,7 @@ export function startServerStream(): (() => void) | undefined {
|
||||
statusReceivedAt.set(Date.now());
|
||||
});
|
||||
if (tickInterval === null) {
|
||||
tickInterval = setInterval(() => _uiNow.set(Date.now()), 1000);
|
||||
tickInterval = setInterval(() => _uiNow.set(serverTimeNow()), 1000);
|
||||
}
|
||||
return () => { source?.close(); source = null; if (tickInterval) clearInterval(tickInterval); tickInterval = null; };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user