const CACHE_TTL_MS = 5 * 60 * 1000; const releasesCache = new Map(); function getGitHubHeaders(): Record { const headers: Record = { Accept: "application/vnd.github+json" }; const token = process.env.GITHUB_TOKEN; if (token) headers.Authorization = `Bearer ${token}`; return headers; } async function fetchJSON(url: string): Promise { const res = await fetch(url, { headers: getGitHubHeaders(), signal: AbortSignal.timeout(15_000), }); if (!res.ok) throw new Error(`GitHub API ${res.status}: ${url}`); return res.json(); } async function cachedFetch(url: string): Promise { const cached = releasesCache.get(url); if (cached && Date.now() - cached.ts < CACHE_TTL_MS) return cached.data; const data = await fetchJSON(url); releasesCache.set(url, { data, ts: Date.now() }); return data; } interface ReleaseInfo { tag: string; version: string; assets: { name: string; url: string; size: number }[]; publishedAt: string; body: string; } function parseRelease(raw: any): ReleaseInfo { return { tag: raw.tag_name, version: raw.tag_name.replace(/^v/, ""), assets: (raw.assets || []).map((a: any) => ({ name: a.name, url: a.browser_download_url, size: a.size, })), publishedAt: raw.published_at, body: raw.body || "", }; } export async function getLatestRelease(): Promise { const raw = await cachedFetch( "https://api.github.com/repos/router-for-me/CLIProxyAPI/releases/latest" ); return parseRelease(raw); } export async function getReleaseByVersion(version: string): Promise { const tag = version.startsWith("v") ? version : `v${version}`; try { const raw = await cachedFetch( `https://api.github.com/repos/router-for-me/CLIProxyAPI/releases/tags/${tag}` ); return parseRelease(raw); } catch { return null; } } export async function getAvailableVersions(): Promise { const raw = await cachedFetch( "https://api.github.com/repos/router-for-me/CLIProxyAPI/releases?per_page=30" ); return (Array.isArray(raw) ? raw : []).map((r: any) => r.tag_name); } export async function getChecksums(version: string): Promise> { const tag = version.startsWith("v") ? version : `v${version}`; const url = `https://github.com/router-for-me/CLIProxyAPI/releases/download/${tag}/checksums.txt`; try { const res = await fetch(url, { signal: AbortSignal.timeout(10_000) }); if (!res.ok) return new Map(); const text = await res.text(); const map = new Map(); for (const line of text.split("\n")) { const parts = line.trim().split(/\s+/); if (parts.length >= 2) { map.set(parts[1], parts[0]); } } return map; } catch { return new Map(); } } export function clearCache(): void { releasesCache.clear(); }