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:
1 parent
e295a997a1
commit
dc25633f61
5 files changed
+84
-5
No files matched your search
@@ -0,0 +1,2 @@
|
||||
.wrangler/
|
||||
node_modules/
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"name": "cfgeo",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "wrangler dev",
|
||||
"deploy": "wrangler deploy"
|
||||
},
|
||||
"devDependencies": {
|
||||
"wrangler": "^3.90.0"
|
||||
}
|
||||
}
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
name = "cfgeo"
|
||||
main = "src/index.js"
|
||||
compatibility_date = "2024-01-01"
|
||||
@@ -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,13 +37,30 @@ 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});
|
||||
}
|
||||
},
|
||||
|
||||
onHash() {
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user