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 = () => ({
|
const app = () => ({
|
||||||
futureTimes: [],
|
futureTimes: [],
|
||||||
selectedLocation: Alpine.$persist(null),
|
selectedLocation: Alpine.$persist(null),
|
||||||
@@ -34,13 +37,30 @@ const app = () => ({
|
|||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
if (!urlCoords) {
|
if (!urlCoords) {
|
||||||
try {
|
await this.resolveDefaultLocation();
|
||||||
const coords = await getUserLocation();
|
}
|
||||||
await this.selectNearestLocation(coords.latitude, coords.longitude, {updateURL: true});
|
},
|
||||||
} catch (_error) {
|
|
||||||
// Ignore geolocation errors and rely on manual search.
|
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() {
|
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) {
|
function normalizeLanguageTag(tag) {
|
||||||
return String(tag).toLowerCase().split("-")[0];
|
return String(tag).toLowerCase().split("-")[0];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user