fix(edge): fire-and-forget Sentry, 2s timeout, response check (#2559)

* fix(edge): fire-and-forget Sentry reporting, add 2s timeout and response check

- Remove await from captureEdgeException call sites so Sentry never
  blocks the response path when already on an error
- Add AbortSignal.timeout(2000) to bound latency on Sentry outage or
  slow edge egress
- Check response.ok and emit console.warn on non-2xx so misconfigured
  DSN or auth key is diagnosable rather than silently missing

* fix(sentry): add allowUrls to block DSN pollution from foreign origins

A foreign app (conservation-dash.web.app, biosentinel@2.4.0) was sending
errors to our Sentry project by reusing the public DSN. allowUrls drops
events from non-worldmonitor origins at the SDK level before ingestion.

* fix(edge): void fire-and-forget calls, improve non-2xx Sentry diagnostics
This commit is contained in:
Elie Habib
2026-03-31 07:28:35 +04:00
committed by GitHub
parent ac56d20635
commit 663522ba2b
3 changed files with 19 additions and 4 deletions

View File

@@ -28,8 +28,9 @@ export async function captureEdgeException(err, context = {}) {
const errMsg = err instanceof Error ? err.message : String(err);
const errType = err instanceof Error ? err.constructor.name : 'Error';
try {
await fetch(_storeUrl, {
const res = await fetch(_storeUrl, {
method: 'POST',
signal: AbortSignal.timeout(2000),
headers: {
'Content-Type': 'application/json',
'X-Sentry-Auth': `Sentry sentry_version=7, sentry_key=${_key}`,
@@ -45,5 +46,15 @@ export async function captureEdgeException(err, context = {}) {
tags: { runtime: 'edge' },
}),
});
} catch {}
if (!res.ok) {
const hint = res.status === 401 || res.status === 403
? ' — check VITE_SENTRY_DSN and auth key'
: res.status === 429
? ' — rate limited by Sentry'
: ' — Sentry outage or transient error';
console.warn(`[sentry-edge] non-2xx response ${res.status}${hint}`);
}
} catch (fetchErr) {
console.warn('[sentry-edge] failed to deliver event:', fetchErr instanceof Error ? fetchErr.message : fetchErr);
}
}

View File

@@ -110,7 +110,7 @@ export default async function handler(req: Request): Promise<Response> {
return json(data, 200, corsHeaders);
} catch (err) {
console.error('[notification-channels] GET error:', err);
await captureEdgeException(err, { handler: 'notification-channels', method: 'GET' });
void captureEdgeException(err, { handler: 'notification-channels', method: 'GET' });
return json({ error: 'Failed to fetch' }, 500, corsHeaders);
}
}
@@ -187,7 +187,7 @@ export default async function handler(req: Request): Promise<Response> {
return json({ error: 'Unknown action' }, 400, corsHeaders);
} catch (err) {
console.error('[notification-channels] POST error:', err);
await captureEdgeException(err, { handler: 'notification-channels', method: 'POST' });
void captureEdgeException(err, { handler: 'notification-channels', method: 'POST' });
return json({ error: 'Operation failed' }, 500, corsHeaders);
}
}

View File

@@ -16,6 +16,10 @@ Sentry.init({
: location.hostname.includes('vercel.app') ? 'preview'
: 'development',
enabled: Boolean(sentryDsn) && !location.hostname.startsWith('localhost') && !('__TAURI_INTERNALS__' in window),
allowUrls: [
/https?:\/\/(www\.|tech\.|finance\.|commodity\.|happy\.)?worldmonitor\.app/,
/https?:\/\/.*\.vercel\.app/,
],
sendDefaultPii: true,
tracesSampleRate: 0.1,
ignoreErrors: [