fix: the diagnostics tab keeps its focus, its level and its consent
Deep-review round 1 of E22. - After a 429 the stream sent one POST per new event while a full batch waited; it now stays quiet for 15 s. Past the queue cap the oldest events go, as the comment always said, a refused batch included. - A batch refused after the level was lowered came back into the queue of a later consent in the same tab; each consent is its own session. - A `me` fetch that left before a level or preference change could put the old value back for up to a minute. `updateMeCache` writes the cache and cancels that fetch; usePref goes through it too. - The consent box's buttons and "revert now" took the focus with them to <body>; it returns to the level picker. Watched until the button is gone — in Chrome the saved level arrives in two renders, and a one-shot check spent itself on the first (jsdom never showed it). - The countdown line is no longer a live region re-read every minute. - "Saját napló" rows were keyed by index on a newest-first list, so every new event remounted every row and dropped the focus off a row button.
This commit is contained in:
@@ -10,7 +10,8 @@
|
||||
// refetch), or when the server answers that it is off.
|
||||
// A lost batch is dropped, not retried in a loop: this is a diagnostic stream, not a ledger, and a
|
||||
// retry storm against a struggling server is the last thing a diagnosis needs. The one exception is
|
||||
// being told to slow down (429): that batch goes back to the front of the queue.
|
||||
// being told to slow down (429): that batch goes back to the front of the queue, and nothing is sent
|
||||
// for BACKOFF_MS — not even when a full batch is waiting, or every new event would send one.
|
||||
import { api, DIAGNOSTICS_EVENTS_PATH, HttpError, type DiagnosticsState } from "./api";
|
||||
import { log, setLevel, subscribe, type LogEvent } from "./log";
|
||||
|
||||
@@ -19,6 +20,8 @@ const FLUSH_MS = 5_000;
|
||||
const BATCH_MAX = 100;
|
||||
/** Waiting events kept while the server is slow or away; past it the oldest go. */
|
||||
const QUEUE_MAX = 1_000;
|
||||
/** Quiet time after a 429. The server counts batches per minute, per account — every tab of it. */
|
||||
const BACKOFF_MS = 15_000;
|
||||
|
||||
/** The level actually in force: the consented one until it lapses. */
|
||||
export function levelInForce(state: DiagnosticsState | null | undefined, now = Date.now()) {
|
||||
@@ -74,16 +77,27 @@ let queue: LogEvent[] = [];
|
||||
let unsubscribe: (() => void) | null = null;
|
||||
let flushTimer: number | null = null;
|
||||
let inFlight = false;
|
||||
let quietUntil = 0;
|
||||
// Which consent a batch was taken under. A batch still in flight when the level is lowered must not
|
||||
// come back into the queue of a LATER consent: it was captured under the one just withdrawn.
|
||||
let session = 0;
|
||||
|
||||
const isOwnRequest = (event: LogEvent) =>
|
||||
event.event === "http.request" && event.attrs?.path === DIAGNOSTICS_EVENTS_PATH;
|
||||
|
||||
const keepNewest = () => {
|
||||
if (queue.length > QUEUE_MAX) queue.splice(0, queue.length - QUEUE_MAX);
|
||||
};
|
||||
|
||||
function startStreaming(): void {
|
||||
if (unsubscribe) return;
|
||||
session += 1;
|
||||
queue = [];
|
||||
quietUntil = 0;
|
||||
unsubscribe = subscribe((event) => {
|
||||
if (!event || isOwnRequest(event)) return;
|
||||
queue.push(event);
|
||||
if (queue.length > QUEUE_MAX) queue.splice(0, queue.length - QUEUE_MAX);
|
||||
keepNewest();
|
||||
if (queue.length >= BATCH_MAX) void flush();
|
||||
});
|
||||
flushTimer = window.setInterval(() => void flush(), FLUSH_MS);
|
||||
@@ -101,16 +115,20 @@ function stopStreaming(): void {
|
||||
}
|
||||
|
||||
async function flush(): Promise<void> {
|
||||
if (inFlight || !queue.length) return;
|
||||
if (inFlight || !queue.length || Date.now() < quietUntil) return;
|
||||
inFlight = true;
|
||||
const takenIn = session;
|
||||
const batch = queue.splice(0, BATCH_MAX);
|
||||
try {
|
||||
await api.sendLogEvents(batch);
|
||||
} catch (err) {
|
||||
if (err instanceof HttpError && err.status === 429) {
|
||||
queue.unshift(...batch);
|
||||
if (queue.length > QUEUE_MAX) queue.length = QUEUE_MAX;
|
||||
} else if (err instanceof HttpError && err.status === 409) {
|
||||
quietUntil = Date.now() + BACKOFF_MS;
|
||||
if (takenIn === session && unsubscribe) {
|
||||
queue.unshift(...batch);
|
||||
keepNewest();
|
||||
}
|
||||
} else if (err instanceof HttpError && err.status === 409 && takenIn === session) {
|
||||
lapse(); // the server has it off: withdrawn in another tab, or lapsed by its clock
|
||||
}
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user