Geolocation at the edge
by landro
What are we building?
Check it out! https://weatherflare.adhocracy.workers.dev
A rendered on demand static HTML page rendered via Javascript on Cloudflare workers based on the visitors geo-location without the use of client side javascript or location data.
What geolocation data is available via Cloudflare headers?
request.cf.colo - The three-letter IATA airport code of the data center that the request hit, for example, "DFW".
country
request.cf.country - Country of the incoming request. The two-letter country code in the request. This is the same value as that provided in the CF-IPCountry header, for example, "US".
request.cf.isEUCountry - If the country of the incoming request is in the EU, this will return "1". Otherwise, this property will be omitted.
request.cf.city - City of the incoming request, for example, "Austin".
request.cf.continent - Continent of the incoming request, for example, "NA".
request.cf.latitude - Latitude of the incoming request, for example, "30.27130".
request.cf.longitude - Longitude of the incoming request, for example, "-97.74260".
request.cf.postalCode - Postal code of the incoming request, for example, "78701".
request.cf.metroCode - Metro code (DMA) of the incoming request, for example, "635".
request.cf.region - If known, the ISO 3166-2 name for the first level region associated with the IP address of the incoming request, for example, "Texas".
request.cf.regionCode - If known, the ISO 3166-2 code for the first-level region associated with the IP address of the incoming request, for example, "TX".
request.cf.timezone - Timezone of the incoming request, for example, "America/Chicago".
Check it out! https://weatherflare.adhocracy.workers.dev
Complete cloudflare worker code (no client side javascript)
| addEventListener('fetch', event => { | |
| event.respondWith(handleRequest(event.request)); | |
| }); | |
| async function handleRequest(request) { | |
| // https://api.weather.gov/points/38.8894,-77.0352 | |
| const { latitude, longitude } = request.cf; | |
| const headers = { | |
| 'content-type': 'application/json', | |
| 'User-Agent': ('landro.dev', 'jaylandro@hotmail.com') | |
| } | |
| const weatherLocationResponse = await fetch( | |
| `https://api.weather.gov/points/${latitude},${longitude}`, { headers: headers } | |
| ); | |
| console.log(weatherLocationResponse) | |
| const weatherLocationData = await weatherLocationResponse.json(); | |
| const forecastResponse = await fetch( | |
| weatherLocationData.properties.forecast, { headers: headers } | |
| ); | |
| const forecastData = await forecastResponse.json(); | |
| const forecast = forecastData.properties.periods; | |
| function iconHelper(shortForecastString) { | |
| if (shortForecastString.includes("Snow")) | |
| return '🌨'; | |
| if (shortForecastString.includes("thunderstorms")) | |
| return '⛈'; | |
| if (shortForecastString.includes("Partly Cloudy")) | |
| return '🌤'; | |
| if (shortForecastString.includes("Cloud")) | |
| return '☁️'; | |
| if (shortForecastString.includes("Light Rain")) | |
| return '🌦'; | |
| if (shortForecastString.includes("Rain")) | |
| return '🌧'; | |
| return '☀️'; | |
| } | |
| let html = ` | |
| <html> | |
| <head> | |
| <title>Geolocation: Weather</title> | |
| </head> | |
| <body> | |
| <style> | |
| body { | |
| padding: 6em; | |
| font-family: sans-serif; | |
| } | |
| h1 { | |
| color: #f6821f; | |
| } | |
| .header { | |
| grid-area: header; | |
| margin-bottom: 0; | |
| } | |
| .icon { | |
| grid-area: icon; | |
| font-size: 3em; | |
| font-style: normal; | |
| } | |
| .detailforecast { | |
| grid-area: detailforecast; | |
| line-height: 1.4; | |
| max-width: 500px; | |
| } | |
| .day { | |
| display: grid; | |
| grid-gap: 10px; | |
| grid-template-columns: 3.2em 3fr; | |
| grid-template-areas: | |
| "header header" | |
| "icon detailforecast"; | |
| margin-bottom: 1.2em; | |
| } | |
| </style> | |
| <div id="container"> | |
| <h1>Weather in ${weatherLocationData.properties.relativeLocation.properties.city}, ${weatherLocationData.properties.relativeLocation.properties.state}</h1> | |
| <main> | |
| <section> | |
| ${forecast.map(day => ` | |
| <div class="day"> | |
| <h3 class="header">${day.name}</h3> | |
| <i class="icon">${iconHelper(day.shortForecast)}</i> | |
| <span class="detailforecast">${day.detailedForecast}</span> | |
| </div> | |
| `).join(' ')} | |
| </section> | |
| </div> | |
| </body>`; | |
| return new Response(html, { | |
| headers: { | |
| 'content-type': 'text/html; charset=UTF-8;', | |
| }, | |
| }); | |
| } |