fix(maps): increase Overpass timeout and add instance-specific User-Agent

The POI search endpoint (/api/maps/pois) returned 502 errors because:

1. OVERPASS_TIMEOUT_MS (12s) was shorter than mirror response times
   (kumi.systems takes ~25.7s to first byte). Increased to 25s to match
   the [timeout:20] query timeout.

2. The static User-Agent string was indistinguishable between instances,
   making rate-limiting and throttling more likely. The new userAgent()
   function appends the instance's APP_URL so each deployment identifies
   itself uniquely, following Overpass API best practices.
This commit is contained in:
Hardik-369 2026-06-27 10:04:07 +05:30 committed by Maurice
parent a5ba246cb8
commit 544a76d2da

View File

@ -68,7 +68,11 @@ interface GooglePlaceDetails extends GooglePlaceResult {
// ── Constants ──────────────────────────────────────────────────────────────── // ── Constants ────────────────────────────────────────────────────────────────
const UA = 'TREK Travel Planner (https://github.com/mauriceboe/TREK)'; function userAgent(): string {
const base = 'TREK Travel Planner (https://github.com/mauriceboe/TREK)';
try { const url = getAppUrl(); if (url) return `${base}; ${url}`; } catch { /* ignore */ }
return base;
}
// TREK's internal language codes mostly coincide with valid BCP-47 codes, but a // TREK's internal language codes mostly coincide with valid BCP-47 codes, but a
// couple don't: 'br' is Brazilian Portuguese here (BCP-47 'pt-BR'; bare 'br' is // couple don't: 'br' is Brazilian Portuguese here (BCP-47 'pt-BR'; bare 'br' is
@ -153,7 +157,7 @@ export async function searchNominatim(query: string, lang?: string) {
'accept-language': toApiLang(lang), 'accept-language': toApiLang(lang),
}); });
const response = await fetch(`https://nominatim.openstreetmap.org/search?${params}`, { const response = await fetch(`https://nominatim.openstreetmap.org/search?${params}`, {
headers: { 'User-Agent': UA }, headers: { 'User-Agent': userAgent() },
}); });
if (!response.ok) { if (!response.ok) {
const text = await response.text().catch(() => ''); const text = await response.text().catch(() => '');
@ -188,7 +192,7 @@ export async function lookupNominatim(osmType: string, osmId: string, lang?: str
}); });
try { try {
const res = await fetch(`https://nominatim.openstreetmap.org/lookup?${params}`, { const res = await fetch(`https://nominatim.openstreetmap.org/lookup?${params}`, {
headers: { 'User-Agent': UA }, headers: { 'User-Agent': userAgent() },
}); });
if (!res.ok) return null; if (!res.ok) return null;
const data = await res.json() as NominatimResult[]; const data = await res.json() as NominatimResult[];
@ -213,7 +217,7 @@ export async function fetchOverpassDetails(osmType: string, osmId: string): Prom
try { try {
const res = await fetch('https://overpass-api.de/api/interpreter', { const res = await fetch('https://overpass-api.de/api/interpreter', {
method: 'POST', method: 'POST',
headers: { 'User-Agent': UA, 'Content-Type': 'application/x-www-form-urlencoded' }, headers: { 'User-Agent': userAgent(), 'Content-Type': 'application/x-www-form-urlencoded' },
body: `data=${encodeURIComponent(query)}`, body: `data=${encodeURIComponent(query)}`,
}); });
if (!res.ok) return null; if (!res.ok) return null;
@ -290,7 +294,7 @@ const OVERPASS_MIRRORS = [
]; ];
// Per-mirror cap. Because mirrors race in parallel this is also the worst-case // Per-mirror cap. Because mirrors race in parallel this is also the worst-case
// total wait before every mirror is given up on and a 502 is returned. // total wait before every mirror is given up on and a 502 is returned.
const OVERPASS_TIMEOUT_MS = 12000; const OVERPASS_TIMEOUT_MS = 25000;
// Largest viewport side we send to Overpass. A country/continent-sized bbox makes // Largest viewport side we send to Overpass. A country/continent-sized bbox makes
// Overpass scan millions of elements and time out; clamping to a centred window // Overpass scan millions of elements and time out; clamping to a centred window
// keeps the query cheap so the explore pill returns fast at ANY zoom level. // keeps the query cheap so the explore pill returns fast at ANY zoom level.
@ -319,7 +323,7 @@ async function overpassFetch(query: string): Promise<OverpassPoiElement[]> {
try { try {
const res = await fetch(url, { const res = await fetch(url, {
method: 'POST', method: 'POST',
headers: { 'User-Agent': UA, 'Content-Type': 'application/x-www-form-urlencoded' }, headers: { 'User-Agent': userAgent(), 'Content-Type': 'application/x-www-form-urlencoded' },
body, body,
signal: ctrl.signal, signal: ctrl.signal,
}); });
@ -515,7 +519,7 @@ export async function fetchWikimediaPhoto(lat: number, lng: number, name?: strin
pilimit: '1', pilimit: '1',
redirects: '1', redirects: '1',
}); });
const res = await fetch(`https://en.wikipedia.org/w/api.php?${searchParams}`, { headers: { 'User-Agent': UA } }); const res = await fetch(`https://en.wikipedia.org/w/api.php?${searchParams}`, { headers: { 'User-Agent': userAgent() } });
if (res.ok) { if (res.ok) {
const data = await res.json() as { query?: { pages?: Record<string, { thumbnail?: { source?: string } }> } }; const data = await res.json() as { query?: { pages?: Record<string, { thumbnail?: { source?: string } }> } };
const pages = data.query?.pages; const pages = data.query?.pages;
@ -544,7 +548,7 @@ export async function fetchWikimediaPhoto(lat: number, lng: number, name?: strin
iiurlwidth: '400', iiurlwidth: '400',
}); });
try { try {
const res = await fetch(`https://commons.wikimedia.org/w/api.php?${params}`, { headers: { 'User-Agent': UA } }); const res = await fetch(`https://commons.wikimedia.org/w/api.php?${params}`, { headers: { 'User-Agent': userAgent() } });
if (!res.ok) return null; if (!res.ok) return null;
const data = await res.json() as { query?: { pages?: Record<string, WikiCommonsPage & { imageinfo?: { mime?: string }[] }> } }; const data = await res.json() as { query?: { pages?: Record<string, WikiCommonsPage & { imageinfo?: { mime?: string }[] }> } };
const pages = data.query?.pages; const pages = data.query?.pages;
@ -993,7 +997,7 @@ export async function reverseGeocode(lat: string, lng: string, lang?: string): P
'accept-language': toApiLang(lang), 'accept-language': toApiLang(lang),
}); });
const response = await fetch(`https://nominatim.openstreetmap.org/reverse?${params}`, { const response = await fetch(`https://nominatim.openstreetmap.org/reverse?${params}`, {
headers: { 'User-Agent': UA }, headers: { 'User-Agent': userAgent() },
}); });
if (!response.ok) return { name: null, address: null }; if (!response.ok) return { name: null, address: null };
const data = await response.json() as { name?: string; display_name?: string; address?: Record<string, string> }; const data = await response.json() as { name?: string; display_name?: string; address?: Record<string, string> };