|
1 | | -import type { Handle, HandleServerError } from '@sveltejs/kit'; |
2 | | - |
3 | | -// Parse DSN into the store endpoint and auth header Sentry expects. |
4 | | -// DSN format: https://<key>@<host>/<project_id> |
5 | | -function parseDsn(dsn: string): { url: string; key: string } | null { |
6 | | - try { |
7 | | - const u = new URL(dsn); |
8 | | - const key = u.username; |
9 | | - const projectId = u.pathname.replace('/', ''); |
10 | | - const host = u.host; |
11 | | - return { url: `https://${host}/api/${projectId}/store/`, key }; |
12 | | - } catch { |
13 | | - return null; |
14 | | - } |
15 | | -} |
16 | | - |
17 | | -async function captureToSentry( |
18 | | - dsn: string, |
19 | | - payload: { message?: string; exception?: unknown; level: string; request?: unknown } |
20 | | -): Promise<void> { |
21 | | - const parsed = parseDsn(dsn); |
22 | | - if (!parsed) return; |
23 | | - const event = { |
24 | | - timestamp: new Date().toISOString(), |
25 | | - platform: 'javascript', |
26 | | - level: payload.level, |
27 | | - ...(payload.message ? { message: payload.message } : {}), |
28 | | - ...(payload.exception ? { exception: payload.exception } : {}), |
29 | | - ...(payload.request ? { request: payload.request } : {}), |
30 | | - }; |
31 | | - await fetch(parsed.url, { |
32 | | - method: 'POST', |
33 | | - headers: { |
34 | | - 'Content-Type': 'application/json', |
35 | | - 'X-Sentry-Auth': `Sentry sentry_version=7, sentry_key=${parsed.key}, sentry_client=openboot/1.0`, |
36 | | - }, |
37 | | - body: JSON.stringify(event), |
38 | | - signal: AbortSignal.timeout(5_000), |
39 | | - }).catch(() => {}); // fire-and-forget, never block the response |
40 | | -} |
| 1 | +import type { Handle } from '@sveltejs/kit'; |
41 | 2 | import { RESERVED_ALIASES } from '$lib/server/validation'; |
42 | 3 | import { getConfigForHookAlias, getConfigForInstall, getConfigForHookSlug } from '$lib/server/db'; |
43 | 4 | import { serveInstallByAlias, serveInstallBySlug } from '$lib/server/alias'; |
@@ -87,43 +48,6 @@ function isVersionOlderThan(version: string, minVersion: string): boolean { |
87 | 48 | return aMaj < bMaj || (aMaj === bMaj && (aMin < bMin || (aMin === bMin && aPat < bPat))); |
88 | 49 | } |
89 | 50 |
|
90 | | -// Only report errors from real app routes — everything else (scanner probes, |
91 | | -// unknown paths) is ignored. Allowlist beats blocklist: no more whack-a-mole. |
92 | | -const MONITORED_PATH_PATTERNS = [ |
93 | | - /^\/$/, // homepage |
94 | | - /^\/api\//, // all API endpoints |
95 | | - /^\/dashboard/, // dashboard + edit pages |
96 | | - /^\/docs/, // docs |
97 | | - /^\/cli-auth/, // CLI auth page |
98 | | - /^\/login/, // login page |
99 | | - /^\/explore/, // explore page |
100 | | - /^\/preview/, // preview page |
101 | | - /^\/install/, // install route |
102 | | - /^\/sitemap\.xml$/, // sitemap |
103 | | - /^\/[a-z0-9][a-z0-9-]{0,38}$/i, // short alias (e.g. /dev) |
104 | | - /^\/[a-z0-9_-]+\/[a-z0-9_-]+/i, // /:username/:slug and sub-routes |
105 | | -]; |
106 | | - |
107 | | -function isMonitoredPath(pathname: string): boolean { |
108 | | - return MONITORED_PATH_PATTERNS.some((p) => p.test(pathname)); |
109 | | -} |
110 | | - |
111 | | -export const handleError: HandleServerError = async ({ error, event, status }) => { |
112 | | - // Only report real server errors (5xx). 4xx (including 404 from bot probes) are not alertable. |
113 | | - if (status < 500) return; |
114 | | - if (!isMonitoredPath(event.url.pathname)) return; |
115 | | - const dsn = event.platform?.env?.SENTRY_DSN; |
116 | | - if (dsn) { |
117 | | - await captureToSentry(dsn, { |
118 | | - level: 'error', |
119 | | - exception: { |
120 | | - values: [{ type: 'Error', value: error instanceof Error ? error.message : String(error) }], |
121 | | - }, |
122 | | - request: { url: event.url.href, method: event.request.method }, |
123 | | - }); |
124 | | - } |
125 | | -}; |
126 | | - |
127 | 51 | export const handle: Handle = async ({ event, resolve }) => { |
128 | 52 | const path = event.url.pathname; |
129 | 53 |
|
@@ -181,16 +105,6 @@ export const handle: Handle = async ({ event, resolve }) => { |
181 | 105 | } |
182 | 106 |
|
183 | 107 | const response = await resolve(event); |
184 | | - |
185 | | - const dsn = event.platform?.env?.SENTRY_DSN; |
186 | | - if (response.status >= 500 && dsn && isMonitoredPath(event.url.pathname)) { |
187 | | - await captureToSentry(dsn, { |
188 | | - level: 'error', |
189 | | - message: `HTTP ${response.status} ${event.request.method} ${event.url.pathname}`, |
190 | | - request: { url: event.url.href, method: event.request.method }, |
191 | | - }); |
192 | | - } |
193 | | - |
194 | 108 | const securedResponse = withSecurityHeaders(response); |
195 | 109 |
|
196 | 110 | // Version negotiation: if CLI sends X-OpenBoot-Version, check compatibility. |
@@ -221,17 +135,3 @@ export const handle: Handle = async ({ event, resolve }) => { |
221 | 135 |
|
222 | 136 | return securedResponse; |
223 | 137 | }; |
224 | | - |
225 | | -// Weekly canary: fires every Monday 9am UTC (see wrangler.toml). |
226 | | -// Sends a test event to Sentry to verify the full alerting chain is working. |
227 | | -export const scheduled: App.Scheduled = async ({ platform }) => { |
228 | | - const dsn = platform?.env?.SENTRY_DSN; |
229 | | - if (!dsn) return; |
230 | | - await captureToSentry(dsn, { |
231 | | - level: 'info', |
232 | | - message: 'Weekly canary — alerting system is working', |
233 | | - }); |
234 | | - console.log('[canary] sent weekly test event to Sentry'); |
235 | | -}; |
236 | | - |
237 | | - |
0 commit comments