feat(web): Add Cloudflare Workers geolocation fallback

Add a minimal cfgeo Cloudflare Worker that returns coarse location
from Cloudflare's edge geolocation data. Use it as a fast default
location on load, preferring browser geolocation or an explicit
search/URL selection whenever those are available.
This commit is contained in:
abdus committed 2026-10-08 21:11:07 +02:00
1 parent e295a997a1
commit dc25633f61
5 files changed
+84 -5

No files matched your search

+2
View File
@@ -0,0 +1,2 @@
.wrangler/
node_modules/
+11
View File
@@ -0,0 +1,11 @@
{
"name": "cfgeo",
"private": true,
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy"
},
"devDependencies": {
"wrangler": "^3.90.0"
}
}
+23
View File
@@ -0,0 +1,23 @@
export default {
async fetch(request) {
const cf = request.cf || {};
const body = {
latitude: cf.latitude ? Number(cf.latitude) : null,
longitude: cf.longitude ? Number(cf.longitude) : null,
city: cf.city ?? null,
region: cf.region ?? null,
country: cf.country ?? null,
timezone: cf.timezone ?? null,
postalCode: cf.postalCode ?? null,
};
return new Response(JSON.stringify(body), {
headers: {
'content-type': 'application/json',
'access-control-allow-origin': '*',
'cache-control': 'no-store',
},
});
},
};
+3
View File
@@ -0,0 +1,3 @@
name = "cfgeo"
main = "src/index.js"
compatibility_date = "2024-01-01"
+45 -5
View File
@@ -1,3 +1,6 @@
const CFGEO_ENDPOINT = "https://cfgeo.abdusco.workers.dev/";
const CFGEO_TIMEOUT_MS = 2000;
const app = () => ({
futureTimes: [],
selectedLocation: Alpine.$persist(null),
@@ -34,12 +37,29 @@ const app = () => ({
}, 500);
if (!urlCoords) {
try {
const coords = await getUserLocation();
await this.selectNearestLocation(coords.latitude, coords.longitude, {updateURL: true});
} catch (_error) {
// Ignore geolocation errors and rely on manual search.
await this.resolveDefaultLocation();
}
},
async resolveDefaultLocation() {
let browserResolved = false;
const geoPromise = getUserLocation()
.then((coords) => {
browserResolved = true;
return coords;
})
.catch(() => null);
if (!this.selectedLocation) {
const cfCoords = await withTimeout(getCFGeoLocation(), CFGEO_TIMEOUT_MS);
if (cfCoords && !browserResolved) {
await this.selectNearestLocation(cfCoords.latitude, cfCoords.longitude, {updateURL: false});
}
}
const geoCoords = await geoPromise;
if (geoCoords) {
await this.selectNearestLocation(geoCoords.latitude, geoCoords.longitude, {updateURL: true});
}
},
@@ -396,6 +416,26 @@ function getUserLocation() {
});
}
async function getCFGeoLocation() {
if (!CFGEO_ENDPOINT) {
return null;
}
const data = await fetchJSON(CFGEO_ENDPOINT);
if (typeof data.latitude !== "number" || typeof data.longitude !== "number") {
return null;
}
return {latitude: data.latitude, longitude: data.longitude};
}
function withTimeout(promise, ms) {
return Promise.race([
promise,
new Promise((resolve) => setTimeout(() => resolve(null), ms))
]).catch(() => null);
}
function normalizeLanguageTag(tag) {
return String(tag).toLowerCase().split("-")[0];
}