src/services/project-meta.js
Rama main · réplica de lectura del repositorio original.
// Star count for the top bar's GitHub chip. GitHub allows 60 unauthenticated API calls an hour
// per address, so one wizard session that opens many pages, or several sessions, could use them up
// and the chip showed "?". Keep a good count for 15 minutes, share one request between pages that
// load together, ask Relmio's website (which caches the same count) when GitHub refuses, and keep
// the last good count when both fail.
const GITHUB_URL = "https://api.github.com/repos/Demonbane18/relmio";
const WEBSITE_URL = "https://relmio.jpfusin.tech/api/project-meta";
export const PROJECT_META_TTL_MS = 15 * 60_000;
const TIMEOUT_MS = 5_000;
// After both sources fail, answer from the last count for a minute instead of waiting on them again.
export const PROJECT_META_RETRY_MS = 60_000;
const validStars = (value) => (Number.isSafeInteger(value) && value >= 0 ? value : null);
async function readStars(fetchImpl, url, headers, pick) {
try {
const response = await fetchImpl(url, { headers, redirect: "error", signal: AbortSignal.timeout(TIMEOUT_MS) });
return response.ok ? validStars(pick(await response.json())) : null;
} catch {
return null;
}
}
export function createProjectMetaReader({ version, fetchImpl = fetch, now = Date.now } = {}) {
const headers = { "User-Agent": `relmio/${version}` };
let cached = null;
let pending = null;
let retryAt = 0;
async function refresh() {
const stars = await readStars(fetchImpl, GITHUB_URL, { ...headers, Accept: "application/vnd.github+json" },
(body) => body?.stargazers_count)
?? await readStars(fetchImpl, WEBSITE_URL, { ...headers, Accept: "application/json" }, (body) => body?.stars);
if (stars !== null) cached = { stars, at: now() };
else retryAt = now() + PROJECT_META_RETRY_MS;
return cached?.stars ?? null;
}
return async function getProjectMeta() {
const at = now();
if ((cached && at - cached.at < PROJECT_META_TTL_MS) || at < retryAt) return { stars: cached?.stars ?? null, version };
pending ??= refresh().finally(() => { pending = null; });
return { stars: await pending, version };
};
}