diff --git a/cfgeo/.gitignore b/cfgeo/.gitignore new file mode 100644 index 0000000..41a2578 --- /dev/null +++ b/cfgeo/.gitignore @@ -0,0 +1,2 @@ +.wrangler/ +node_modules/ diff --git a/cfgeo/package.json b/cfgeo/package.json new file mode 100644 index 0000000..383140a --- /dev/null +++ b/cfgeo/package.json @@ -0,0 +1,11 @@ +{ + "name": "cfgeo", + "private": true, + "scripts": { + "dev": "wrangler dev", + "deploy": "wrangler deploy" + }, + "devDependencies": { + "wrangler": "^3.90.0" + } +} diff --git a/cfgeo/src/index.js b/cfgeo/src/index.js new file mode 100644 index 0000000..44ea871 --- /dev/null +++ b/cfgeo/src/index.js @@ -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', + }, + }); + }, +}; diff --git a/cfgeo/wrangler.toml b/cfgeo/wrangler.toml new file mode 100644 index 0000000..43623b9 --- /dev/null +++ b/cfgeo/wrangler.toml @@ -0,0 +1,3 @@ +name = "cfgeo" +main = "src/index.js" +compatibility_date = "2024-01-01" diff --git a/templates/static/main.js b/templates/static/main.js index 899984e..ad159b1 100644 --- a/templates/static/main.js +++ b/templates/static/main.js @@ -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]; }