koala73/worldmonitor · warning · EmbedMapFrameUnavailableError
Embed map frame temporarily unavailable
Error message
Embed map frame temporarily unavailable
What it means
fetchEmbedMapFrame treats HTTP 429 and any 5xx from the embed map frame endpoint as temporary unavailability and throws EmbedMapFrameUnavailableError, carrying a retry-after delay derived from the response. Unlike a generic failure, this error signals the caller may retry after the suggested delay.
Solutions
- Wait for the retry-after delay carried by EmbedMapFrameUnavailableError, then retry the frame request.
- Implement exponential backoff with jitter instead of immediate retries to avoid deepening the rate limit.
- Cache the last successfully fetched frame and render it stale while retrying in the background.
- Check the embed endpoint's server logs/metrics if 5xx persists (deployment or upstream issue).
Example fix
// before
const frame = await fetchEmbedMapFrame(params);
// after
try {
const frame = await fetchEmbedMapFrame(params);
} catch (e) {
if (e instanceof EmbedMapFrameUnavailableError) {
setTimeout(() => fetchEmbedMapFrame(params).then(render), e.retryAfterMs);
render(staleFrame);
return;
}
throw e;
} Defensive patterns
Strategy: retry
Type guard
const isEmbedUnavailable = (e: unknown): e is EmbedMapFrameUnavailableError => e instanceof EmbedMapFrameUnavailableError;
Try / catch
try {
const frame = await fetchEmbedMapFrame(params);
} catch (e) {
if (isEmbedUnavailable(e)) {
setTimeout(() => void fetchEmbedMapFrame(params).then(render).catch(console.warn), e.retryAfterMs);
render(cachedFrame ?? placeholder);
return;
}
throw e;
} Prevention
- Honor Retry-After and add exponential backoff with jitter.
- Cache the last good frame for stale-while-revalidate rendering.
- Space out embed requests to stay under rate limits.
When it happens
Trigger: The GET request for an embed map frame returns status 429 (rate limited) or >= 500 (server error) from the edge/backend serving embed frames.
Common situations: Rate limiting after many embed requests from one origin/IP; backend or Railway worker down or redeploying; upstream data dependency of the frame endpoint failing; load spikes causing 502/503 from the proxy.
Related errors
- Exa ${endpoint.slice(1)} failed HTTP ${response.status}: ${d
- Wikidata request failed with status ${response.status}
- HTTP ${response.status}
- Cloudflare Radar API error: ${resp.status}
- NHC layer ${layerId}: ${cause.message}
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/f7d2b9421735c869.
Report an issue: GitHub.
Appendix: source
Thrown at src/embed/embed-fetch.ts:184
const headers: Record<string, string> = { Accept: 'application/json' };
let target: string;
if (grant) {
headers['X-WorldMonitor-Grant'] = grant.token;
const url = new URL(EMBED_MAP_FRAME_PATH, origin);
url.searchParams.set('layers', canonicalizeEmbedLayers(layerIds).join(','));
target = url.toString();
} else {
target = `${origin}${EMBED_MAP_FRAME_PATH}?${publicFrameSearch(layerIds)}`;
}
const resp = await globalThis.fetch(target, {
method: 'GET',
headers,
credentials: 'omit',
});
if (resp.status === 429 || resp.status >= 500) {
throw new EmbedMapFrameUnavailableError(frameRetryAfterMs(resp));
}
if (!resp.ok) throw new Error(`Embed map frame request failed: ${resp.status}`);
return await resp.json() as EmbedMapFrameResponse;
}
/**
* The cacheable query for the free layers among `layerIds`, falling back to
* the plain uncacheable query when none qualify.
*
* Paid layers are dropped from the keyless URL rather than carried into it:
* a keyless caller cannot receive them either way, and naming them would
* multiply the shared key space past its seven free subsets. The frame marks
* them un-ready from their absence in the response, which is the same outcome
* an explicit `not-entitled` would produce.
*/
function publicFrameSearch(layerIds: readonly EmbedLayerId[]): string {
const free = getEmbedPanelFreeTier('map');
const eligible = freeView on GitHub (pinned to 7d06c8633d)