import { toSeverity } from "../describe"; import { fetchJson } from "../http"; import type { WeatherAlert } from "../types"; /** * Active US weather alerts from the National Weather Service (free, no key). * Docs: https://www.weather.gov/documentation/services-web-api * * NWS asks every app to identify itself with a User-Agent containing contact * info, so set NWS_CONTACT in .env.local (an email or website). * Outside the US this returns nothing, which is fine. */ interface NwsResponse { features: { properties: { event: string; headline: string | null; severity: string; senderName: string; onset: string | null; effective: string; ends: string | null; expires: string; description: string; instruction: string | null; }; }[]; } /** Rough boxes around US states and territories — skips pointless calls elsewhere. */ function mightBeInUS(lat: number, lon: number): boolean { const mainland = lat > 24 && lat < 50 && lon > -125 && lon < -66; const alaska = lat > 51 && lat < 72 && (lon < -129 || lon > 172); const hawaii = lat > 18 && lat < 23 && lon > -161 && lon < -154; const caribbean = lat > 17 && lat < 19 && lon > -68 && lon < -64; // Puerto Rico, USVI const pacific = lat > 13 && lat < 21 && lon > 144 && lon < 146; // Guam, Northern Marianas return mainland || alaska || hawaii || caribbean || pacific; } let warnedMissingContact = false; function buildNwsUserAgent(): string { const explicit = process.env.NWS_CONTACT?.trim(); if (explicit) return `WayfarerWeather/1.0 (${explicit})`; const deployedHost = process.env.VERCEL_PROJECT_PRODUCTION_URL?.trim() || process.env.VERCEL_URL?.trim(); if (deployedHost) return `WayfarerWeather/1.0 (https://${deployedHost})`; if (process.env.NODE_ENV === "production" && !warnedMissingContact) { warnedMissingContact = true; console.warn("[Wayfarer] Set NWS_CONTACT (email or site URL) so api.weather.gov requests include contact details."); } return "WayfarerWeather/1.0 (local-dev)"; } export async function getNwsAlerts(lat: number, lon: number): Promise { if (!mightBeInUS(lat, lon)) return []; const point = `${lat.toFixed(4)},${lon.toFixed(4)}`; const data = await fetchJson(`https://api.weather.gov/alerts/active?point=${point}`, "National Weather Service", { cacheSeconds: 300, // alerts matter; refresh more often headers: { "User-Agent": buildNwsUserAgent(), Accept: "application/geo+json" }, }); return data.features.map(({ properties: p }) => ({ title: p.headline ?? p.event, severity: toSeverity(p.severity), source: p.senderName, starts: p.onset ?? p.effective, ends: p.ends ?? p.expires, description: p.description, instruction: p.instruction, })); }