diff --git a/packages/cloudflare/README.md b/packages/cloudflare/README.md index 1dd73d7c3da9..f7de52a56e88 100644 --- a/packages/cloudflare/README.md +++ b/packages/cloudflare/README.md @@ -72,6 +72,37 @@ export const onRequest = [ ]; ``` +If you need to access the `context` object (for example to grab environmental variables), you can pass a function to +`sentryPagesPlugin` that takes the `context` object as an argument and returns `init` options: + +```javascript +export const onRequest = Sentry.sentryPagesPlugin(context => ({ + dsn: context.env.SENTRY_DSN, + tracesSampleRate: 1.0, +})); +``` + +If you do not have access to the `onRequest` middleware API, you can use the `wrapRequestHandler` API instead. + +Here is an example with SvelteKit: + +```javascript +// hooks.server.js +import * as Sentry from '@sentry/cloudflare'; + +export const handle = ({ event, resolve }) => { + const requestHandlerOptions = { + options: { + dsn: event.platform.env.SENTRY_DSN, + tracesSampleRate: 1.0, + }, + request: event.request, + context: event.platform.ctx, + }; + return Sentry.wrapRequestHandler(requestHandlerOptions, () => resolve(event)); +}; +``` + ## Setup (Cloudflare Workers) To use this SDK, wrap your handler with the `withSentry` function. This will initialize the SDK and hook into the diff --git a/packages/cloudflare/src/index.ts b/packages/cloudflare/src/index.ts index 867abd8e4a6e..2f77f96f4e33 100644 --- a/packages/cloudflare/src/index.ts +++ b/packages/cloudflare/src/index.ts @@ -88,6 +88,8 @@ export { export { withSentry } from './handler'; export { sentryPagesPlugin } from './pages-plugin'; +export { wrapRequestHandler } from './request'; + export { CloudflareClient } from './client'; export { getDefaultIntegrations } from './sdk'; diff --git a/packages/cloudflare/src/pages-plugin.ts b/packages/cloudflare/src/pages-plugin.ts index f2c46efd86f2..8bdc806b5693 100644 --- a/packages/cloudflare/src/pages-plugin.ts +++ b/packages/cloudflare/src/pages-plugin.ts @@ -7,23 +7,48 @@ import { wrapRequestHandler } from './request'; * * Initializes the SDK and wraps cloudflare pages requests with SDK instrumentation. * - * @example + * @example Simple usage + * * ```javascript * // functions/_middleware.js * import * as Sentry from '@sentry/cloudflare'; * * export const onRequest = Sentry.sentryPagesPlugin({ - * dsn: process.env.SENTRY_DSN, - * tracesSampleRate: 1.0, + * dsn: process.env.SENTRY_DSN, + * tracesSampleRate: 1.0, * }); * ``` + * + * @example Usage with handler function to access context for environmental variables + * + * ```javascript + * import * as Sentry from '@sentry/cloudflare'; + * + * const const onRequest = Sentry.sentryPagesPlugin((context) => ({ + * dsn: context.env.SENTRY_DSN, + * tracesSampleRate: 1.0, + * }) + * ``` + * + * @param handlerOrOptions Configuration options or a function that returns configuration options. + * @returns A plugin function that can be used in Cloudflare Pages. */ export function sentryPagesPlugin< Env = unknown, // eslint-disable-next-line @typescript-eslint/no-explicit-any Params extends string = any, Data extends Record = Record, ->(options: CloudflareOptions): PagesPluginFunction { + // Although it is not ideal to use `any` here, it makes usage more flexible for different setups. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + PluginParams = any, +>( + handlerOrOptions: + | CloudflareOptions + | ((context: EventPluginContext) => CloudflareOptions), +): PagesPluginFunction { setAsyncLocalStorageAsyncContextStrategy(); - return context => wrapRequestHandler({ options, request: context.request, context }, () => context.next()); + return context => { + const options = typeof handlerOrOptions === 'function' ? handlerOrOptions(context) : handlerOrOptions; + return wrapRequestHandler({ options, request: context.request, context }, () => context.next()); + }; } diff --git a/packages/cloudflare/test/pages-plugin.test.ts b/packages/cloudflare/test/pages-plugin.test.ts index 6e8b87351f8e..b1781dc397af 100644 --- a/packages/cloudflare/test/pages-plugin.test.ts +++ b/packages/cloudflare/test/pages-plugin.test.ts @@ -15,6 +15,28 @@ describe('sentryPagesPlugin', () => { vi.clearAllMocks(); }); + test('calls handler function if a function is provided', async () => { + const mockOptionsHandler = vi.fn().mockReturnValue(MOCK_OPTIONS); + const mockOnRequest = sentryPagesPlugin(mockOptionsHandler); + + const MOCK_CONTEXT = { + request: new Request('https://example.com'), + functionPath: 'test', + waitUntil: vi.fn(), + passThroughOnException: vi.fn(), + next: () => Promise.resolve(new Response('test')), + env: { ASSETS: { fetch: vi.fn() } }, + params: {}, + data: {}, + pluginArgs: MOCK_OPTIONS, + }; + + await mockOnRequest(MOCK_CONTEXT); + + expect(mockOptionsHandler).toHaveBeenCalledTimes(1); + expect(mockOptionsHandler).toHaveBeenLastCalledWith(MOCK_CONTEXT); + }); + test('passes through the response from the handler', async () => { const response = new Response('test'); const mockOnRequest = sentryPagesPlugin(MOCK_OPTIONS);