- Code: GPL-3.0-or-later (LICENSE, SPDX headers on all .kt/.aidl). Specs in docs/: CC-BY-4.0 (docs/LICENSE). Rationale in build-status.md; server decided GPL (not AGPL). - Root README for the public repo. - web/: Cloudflare Worker site for echo-lot.app. /apk resolves the newest APK from the Gitea latest-release API at request time (edge-cached 5 min), so tagging a release is the only publish step. /fdroid, /source, and a manual DOWNLOAD_URL fallback are wrangler vars. - .gitea/workflows/release.yml: tag-driven (v*) signed semver APK builds for the future production app in echolot-app/. Parked; the prober is deliberately not CI-built. - Fix UserService.kt: drop the explicit secondary constructor that conflicted with the implicit primary (never compiled before — first local build caught it). Prober now builds: :app:assembleDebug OK. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
64 lines
2.1 KiB
JavaScript
64 lines
2.1 KiB
JavaScript
// SPDX-FileCopyrightText: 2026 Echolot contributors
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
// Everything under public/ is served straight from the edge without invoking
|
|
// this Worker. The Worker exists for the short stable URLs (/apk, /fdroid,
|
|
// /source) — short enough for a QR code — and to resolve "/apk" to the newest
|
|
// release asset at request time, so tagging a release in Gitea is the only
|
|
// publish step. No site redeploy, no URL to update.
|
|
|
|
const STATIC_ROUTES = {
|
|
"/fdroid": "FDROID_URL",
|
|
"/source": "SOURCE_URL",
|
|
};
|
|
|
|
// Resolve the newest APK from the Gitea "latest release" API. Cached at the
|
|
// edge for 5 minutes so a release becomes visible quickly, while Gitea sees
|
|
// at most one API hit per POP per 5 min regardless of download traffic.
|
|
async function latestApkUrl(env) {
|
|
if (!env.GITEA_REPO_API) return null;
|
|
const res = await fetch(`${env.GITEA_REPO_API}/releases/latest`, {
|
|
headers: { Accept: "application/json", "User-Agent": "echolot-site" },
|
|
cf: { cacheTtl: 300, cacheEverything: true },
|
|
});
|
|
if (!res.ok) return null;
|
|
const rel = await res.json();
|
|
const apk = rel.assets?.find((a) => a.name?.endsWith(".apk"));
|
|
return apk?.browser_download_url ?? null;
|
|
}
|
|
|
|
function redirect(location) {
|
|
return new Response(null, {
|
|
status: 302,
|
|
headers: {
|
|
Location: location,
|
|
"Cache-Control": "no-store",
|
|
"Referrer-Policy": "no-referrer",
|
|
},
|
|
});
|
|
}
|
|
|
|
export default {
|
|
async fetch(request, env) {
|
|
const { pathname } = new URL(request.url);
|
|
const path = pathname.replace(/\/$/, "");
|
|
const fallback = new URL("/#install", request.url).toString();
|
|
|
|
if (path === "/apk" || path === "/download") {
|
|
// Order: live Gitea release → manual override → install section.
|
|
let target = null;
|
|
try {
|
|
target = await latestApkUrl(env);
|
|
} catch {
|
|
// Gitea unreachable — fall through rather than 500 on a download link.
|
|
}
|
|
return redirect(target || env.DOWNLOAD_URL || fallback);
|
|
}
|
|
|
|
const varName = STATIC_ROUTES[path];
|
|
if (varName) return redirect(env[varName] || fallback);
|
|
|
|
return env.ASSETS.fetch(request);
|
|
},
|
|
};
|