diff --git a/.changeset/smooth-pandas-press.md b/.changeset/smooth-pandas-press.md new file mode 100644 index 00000000..dd829d97 --- /dev/null +++ b/.changeset/smooth-pandas-press.md @@ -0,0 +1,5 @@ +--- +'@spotlightjs/core': patch +--- + +ref(core): Guard calls to symbolication endpoint diff --git a/.changeset/warm-countries-own.md b/.changeset/warm-countries-own.md new file mode 100644 index 00000000..1fcd341b --- /dev/null +++ b/.changeset/warm-countries-own.md @@ -0,0 +1,5 @@ +--- +'@spotlightjs/astro': patch +--- + +ref(astro): Guard requests and parsing in Vite Symbolication plugin diff --git a/packages/astro/src/vite/source-context.ts b/packages/astro/src/vite/source-context.ts index ed04eced..5d4cd070 100644 --- a/packages/astro/src/vite/source-context.ts +++ b/packages/astro/src/vite/source-context.ts @@ -35,7 +35,13 @@ export const sourceContextPlugin: () => Plugin = () => ({ }); req.on('end', async () => { - const stacktrace: SentryStackTrace = JSON.parse(requestBody); + const stacktrace = parseStackTrace(requestBody); + + if (!stacktrace) { + res.writeHead(500); + res.end(); + return; + } for (const frame of stacktrace.frames ?? []) { if ( @@ -43,8 +49,10 @@ export const sourceContextPlugin: () => Plugin = () => ({ // let's ignore dependencies for now with this naive check !frame.filename.includes('/node_modules/') ) { - const generatedCodeResponse = await fetch(frame.filename); - const generatedCode = await generatedCodeResponse.text(); + const generatedCode = await getGeneratedCodeFromServer(frame.filename); + if (!generatedCode) { + continue; + } // Extract the inline source map from the minified code const inlineSourceMapMatch = generatedCode.match( @@ -71,6 +79,23 @@ export const sourceContextPlugin: () => Plugin = () => ({ }, }); +async function getGeneratedCodeFromServer(filename: string): Promise { + try { + const generatedCodeResponse = await fetch(filename); + return generatedCodeResponse.text(); + } catch { + return undefined; + } +} + +function parseStackTrace(requestBody: string): SentryStackTrace | undefined { + try { + return JSON.parse(requestBody) as SentryStackTrace; + } catch { + return undefined; + } +} + async function applySourceContextToFrame(sourceMapContent: string, frame: ValidSentryStackFrame) { const consumer = await new SourceMap.SourceMapConsumer(sourceMapContent); diff --git a/packages/core/src/integrations/sentry/sentry-integration.ts b/packages/core/src/integrations/sentry/sentry-integration.ts index dc0e41ed..f0304b7b 100644 --- a/packages/core/src/integrations/sentry/sentry-integration.ts +++ b/packages/core/src/integrations/sentry/sentry-integration.ts @@ -11,13 +11,21 @@ export class Spotlight implements Integration { } for (const exception of event.exception.values ?? []) { - console.log('fetching', exception); - const stackTraceWithContextResponse = await fetch('/spotlight/contextlines', { - method: 'PUT', - body: JSON.stringify(exception.stacktrace), - }); - const stackTraceWithContext = await stackTraceWithContextResponse.json(); - exception.stacktrace = stackTraceWithContext; + try { + const stackTraceWithContextResponse = await fetch('/spotlight/contextlines', { + method: 'PUT', + body: JSON.stringify(exception.stacktrace), + }); + + if (!stackTraceWithContextResponse.ok || stackTraceWithContextResponse.status !== 200) { + continue; + } + + const stackTraceWithContext = await stackTraceWithContextResponse.json(); + exception.stacktrace = stackTraceWithContext; + } catch { + // Something went wrong, for now we just ignore it. + } } return event; });