diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts index aab69c085048..8020a35c7377 100644 --- a/packages/sveltekit/src/server/handle.ts +++ b/packages/sveltekit/src/server/handle.ts @@ -1,14 +1,15 @@ /* eslint-disable @sentry-internal/sdk/no-optional-chaining */ import type { Span } from '@sentry/core'; -import { trace } from '@sentry/core'; +import { getActiveTransaction, trace } from '@sentry/core'; import { captureException } from '@sentry/node'; import { addExceptionMechanism, baggageHeaderToDynamicSamplingContext, + dynamicSamplingContextToSentryBaggageHeader, extractTraceparentData, objectify, } from '@sentry/utils'; -import type { Handle } from '@sveltejs/kit'; +import type { Handle, ResolveOptions } from '@sveltejs/kit'; import * as domain from 'domain'; function sendErrorToSentry(e: unknown): unknown { @@ -34,6 +35,20 @@ function sendErrorToSentry(e: unknown): unknown { return objectifiedErr; } +export const transformPageChunk: NonNullable = ({ html }) => { + const transaction = getActiveTransaction(); + if (transaction) { + const traceparentData = transaction.toTraceparent(); + const dynamicSamplingContext = dynamicSamplingContextToSentryBaggageHeader(transaction.getDynamicSamplingContext()); + const content = ` + + %sveltekit.head%`; + return html.replace('%sveltekit.head%', content); + } + + return html; +}; + /** * A SvelteKit handle function that wraps the request for Sentry error and * performance monitoring. @@ -68,7 +83,7 @@ export const sentryHandle: Handle = ({ event, resolve }) => { }, }, async (span?: Span) => { - const res = await resolve(event); + const res = await resolve(event, { transformPageChunk }); if (span) { span.setHttpStatus(res.status); } diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index cf17b56aaa90..f260d2daf310 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -4,7 +4,7 @@ import type { Transaction } from '@sentry/types'; import type { Handle } from '@sveltejs/kit'; import { vi } from 'vitest'; -import { sentryHandle } from '../../src/server/handle'; +import { sentryHandle, transformPageChunk } from '../../src/server/handle'; import { getDefaultNodeClientOptions } from '../utils'; const mockCaptureException = vi.fn(); @@ -94,22 +94,22 @@ function resolve(type: Type, isError: boolean): Parameters[0]['resolve'] let hub: Hub; let client: NodeClient; -describe('handleSentry', () => { - beforeAll(() => { - addTracingExtensions(); - }); +beforeAll(() => { + addTracingExtensions(); +}); - beforeEach(() => { - mockScope = new Scope(); - const options = getDefaultNodeClientOptions({ tracesSampleRate: 1.0 }); - client = new NodeClient(options); - hub = new Hub(client); - makeMain(hub); +beforeEach(() => { + mockScope = new Scope(); + const options = getDefaultNodeClientOptions({ tracesSampleRate: 1.0 }); + client = new NodeClient(options); + hub = new Hub(client); + makeMain(hub); - mockCaptureException.mockClear(); - mockAddExceptionMechanism.mockClear(); - }); + mockCaptureException.mockClear(); + mockAddExceptionMechanism.mockClear(); +}); +describe('handleSentry', () => { describe.each([ // isSync, isError, expectedResponse [Type.Sync, true, undefined], @@ -247,5 +247,48 @@ describe('handleSentry', () => { ); } }); + + it('calls `transformPageChunk`', async () => { + const mockResolve = vi.fn().mockImplementation(resolve(type, isError)); + const event = mockEvent(); + try { + await sentryHandle({ event, resolve: mockResolve }); + } catch (e) { + expect(e).toBeInstanceOf(Error); + expect(e.message).toEqual(type); + } + + expect(mockResolve).toHaveBeenCalledTimes(1); + expect(mockResolve).toHaveBeenCalledWith(event, { transformPageChunk: expect.any(Function) }); + }); + }); +}); + +describe('transformPageChunk', () => { + const html = ` + + + + + + %sveltekit.head% + + +
%sveltekit.body%
+ + `; + + it('does not add meta tags if no active transaction', () => { + const transformed = transformPageChunk({ html, done: true }); + expect(transformed).toEqual(html); + }); + + it('adds meta tags if there is an active transaction', () => { + const transaction = hub.startTransaction({ name: 'test' }); + hub.getScope().setSpan(transaction); + const transformed = transformPageChunk({ html, done: true }) as string; + + expect(transformed.includes('