SkateSpots/netlify/functions/latest-release.mjs
SkateSpots 06c1d644d9
Some checks are pending
Deploy to GitHub Pages / deploy (push) Waiting to run
release: 1.7.87
2026-11-02 16:57:04 +07:00

72 lines
2.6 KiB
JavaScript

// Netlify Function: GET /api/latest-release
//
// The download page must not carry its own hard-coded version/hash. Forgejo is
// the release source of truth; this small same-origin proxy lets the static page
// read the newest release without relying on third-party CORS.
const OWNER = 'skatespots';
const REPO = 'SkateSpots';
const API = `https://git.skate-spots.org/api/v1/repos/${OWNER}/${REPO}/releases/latest`;
const headers = {
'content-type': 'application/json; charset=utf-8',
// Short cache means a new Forgejo release appears without a redeploy, while
// still shielding the Forgejo API from every page view.
'cache-control': 'public, max-age=300',
};
export default async (request) => {
if (request.method !== 'GET' && request.method !== 'HEAD') {
return new Response(JSON.stringify({ error: 'method not allowed' }), {
status: 405,
headers: { ...headers, allow: 'GET, HEAD' },
});
}
try {
const releaseRes = await fetch(API, {
headers: { accept: 'application/json' },
signal: AbortSignal.timeout(8000),
});
if (!releaseRes.ok) throw new Error(`Forgejo release API returned ${releaseRes.status}`);
const release = await releaseRes.json();
const version = String(release.tag_name || '').replace(/^v/, '');
const assets = Array.isArray(release.assets) ? release.assets : [];
const apk = assets.find((asset) => /\.apk$/i.test(asset.name || ''));
const shaAsset = assets.find((asset) => /\.apk\.sha256$/i.test(asset.name || ''));
if (!version || !apk?.browser_download_url) throw new Error('Latest release has no APK asset');
let sha256 = '';
if (shaAsset?.browser_download_url) {
try {
const shaRes = await fetch(shaAsset.browser_download_url, {
signal: AbortSignal.timeout(5000),
});
if (shaRes.ok) {
const text = await shaRes.text();
sha256 = text.trim().split(/\s+/)[0]?.toLowerCase() || '';
}
} catch {
// The APK is still useful if the checksum asset is briefly unavailable.
}
}
return new Response(JSON.stringify({
version,
apkUrl: apk.browser_download_url,
sha256,
size: apk.size || 0,
publishedAt: release.published_at || release.created_at || null,
}), { headers });
} catch (error) {
return new Response(JSON.stringify({
error: 'latest release unavailable',
detail: error instanceof Error ? error.message : String(error),
}), {
status: 502,
headers: { ...headers, 'cache-control': 'no-store' },
});
}
};
export const config = { path: '/api/latest-release' };