File size: 2,950 Bytes
35743bd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
const CACHE_TTL_MS = 5 * 60 * 1000;
const releasesCache = new Map<string, { data: any; ts: number }>();

function getGitHubHeaders(): Record<string, string> {
  const headers: Record<string, string> = { 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<any> {
  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<any> {
  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<ReleaseInfo> {
  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<ReleaseInfo | null> {
  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<string[]> {
  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<Map<string, string>> {
  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<string, string>();
    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();
}