99 lines
3.5 KiB
TypeScript
99 lines
3.5 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|||
|
|
import { kumaUrls, parseKumaStatus, ServerStatus, StatusPageConfig } from '../utils/kumaStatus';
|
||
|
|
|
||
|
|
/** Normal poll, and while a problem is showing (so recovery clears quickly). */
|
||
|
|
export const STATUS_POLL_MS = 5 * 60 * 1000;
|
||
|
|
export const STATUS_POLL_PROBLEM_MS = 60 * 1000;
|
||
|
|
const STATUS_FETCH_TIMEOUT_MS = 10 * 1000;
|
||
|
|
const STATUS_BACKOFF_MAX_MS = 15 * 60 * 1000;
|
||
|
|
|
||
|
|
const fetchJson = async (url: string, signal: AbortSignal): Promise<unknown> => {
|
||
|
|
const res = await fetch(url, { cache: 'no-store', credentials: 'omit', signal });
|
||
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||
|
|
return res.json();
|
||
|
|
};
|
||
|
|
|
||
|
|
export const hasProblem = (s: ServerStatus | null): boolean =>
|
||
|
|
!!s &&
|
||
|
|
(s.homeserver === 'down' || s.calls === 'down' || s.login === 'down' || s.maintenance.length > 0);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* [Gitea #124] Poll the homeserver's Kuma status page (none configured → no
|
||
|
|
* requests, null). Only while the page is visible; every 5 min, every 60 s
|
||
|
|
* while something is wrong, and at once when `urgent` turns true (this
|
||
|
|
* client's connection dropped). Any failure → null ("no banner"), with
|
||
|
|
* backoff: Kuma being unreachable must never look like Matrix being down.
|
||
|
|
*/
|
||
|
|
export const useServerStatus = (
|
||
|
|
page: StatusPageConfig | undefined,
|
||
|
|
urgent = false,
|
||
|
|
): ServerStatus | null => {
|
||
|
|
const [status, setStatus] = useState<ServerStatus | null>(null);
|
||
|
|
const pageKey = page ? `${page.url}/${page.slug}` : '';
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
if (!page) {
|
||
|
|
setStatus(null);
|
||
|
|
return undefined;
|
||
|
|
}
|
||
|
|
const urls = kumaUrls(page);
|
||
|
|
let timer: ReturnType<typeof setTimeout> | undefined;
|
||
|
|
let controller: AbortController | undefined;
|
||
|
|
let failures = 0;
|
||
|
|
let stopped = false;
|
||
|
|
let last: ServerStatus | null = null;
|
||
|
|
|
||
|
|
const schedule = (ms: number) => {
|
||
|
|
if (timer) clearTimeout(timer);
|
||
|
|
timer = setTimeout(poll, ms);
|
||
|
|
};
|
||
|
|
|
||
|
|
async function poll() {
|
||
|
|
if (stopped) return;
|
||
|
|
if (typeof document !== 'undefined' && document.visibilityState === 'hidden') return;
|
||
|
|
controller?.abort();
|
||
|
|
controller = new AbortController();
|
||
|
|
const abort = controller;
|
||
|
|
const timeout = setTimeout(() => abort.abort(), STATUS_FETCH_TIMEOUT_MS);
|
||
|
|
try {
|
||
|
|
const [pageJson, heartbeatJson] = await Promise.all([
|
||
|
|
fetchJson(urls.page, abort.signal),
|
||
|
|
fetchJson(urls.heartbeat, abort.signal),
|
||
|
|
]);
|
||
|
|
if (stopped) return;
|
||
|
|
failures = 0;
|
||
|
|
last = parseKumaStatus(pageJson, heartbeatJson, page!.groups);
|
||
|
|
setStatus(last);
|
||
|
|
// While this client's connection is lost, keep asking every minute: that
|
||
|
|
// is how it learns Kuma has checked the server since the drop.
|
||
|
|
schedule(hasProblem(last) || urgent ? STATUS_POLL_PROBLEM_MS : STATUS_POLL_MS);
|
||
|
|
} catch {
|
||
|
|
if (stopped) return;
|
||
|
|
failures += 1;
|
||
|
|
last = null;
|
||
|
|
setStatus(null);
|
||
|
|
schedule(Math.min(STATUS_POLL_PROBLEM_MS * 2 ** failures, STATUS_BACKOFF_MAX_MS));
|
||
|
|
} finally {
|
||
|
|
clearTimeout(timeout);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
const onVisible = () => {
|
||
|
|
if (document.visibilityState === 'visible') poll();
|
||
|
|
};
|
||
|
|
document.addEventListener('visibilitychange', onVisible);
|
||
|
|
poll();
|
||
|
|
|
||
|
|
return () => {
|
||
|
|
stopped = true;
|
||
|
|
if (timer) clearTimeout(timer);
|
||
|
|
controller?.abort();
|
||
|
|
document.removeEventListener('visibilitychange', onVisible);
|
||
|
|
};
|
||
|
|
// pageKey identifies the page; the object itself is rebuilt from config.
|
||
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||
|
|
}, [pageKey, urgent]);
|
||
|
|
|
||
|
|
return status;
|
||
|
|
};
|