diff --git a/packages/astro/package.json b/packages/astro/package.json index 429ed9ee587b..3daa7ef225fc 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -40,6 +40,12 @@ "import": "./build/esm/integration/middleware/index.js", "require": "./build/cjs/integration/middleware/index.js", "types": "./build/types/integration/middleware/index.types.d.ts" + }, + "./tunnel": { + "node": "./build/esm/integration/tunnel.js", + "import": "./build/esm/integration/tunnel.js", + "require": "./build/cjs/integration/tunnel.js", + "types": "./build/types/integration/tunnel.types.d.ts" } }, "publishConfig": { diff --git a/packages/astro/src/integration/index.ts b/packages/astro/src/integration/index.ts index 15e48cb49f12..7b9128548737 100644 --- a/packages/astro/src/integration/index.ts +++ b/packages/astro/src/integration/index.ts @@ -13,7 +13,15 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => { name: PKG_NAME, hooks: { // eslint-disable-next-line complexity - 'astro:config:setup': async ({ updateConfig, injectScript, addMiddleware, config, command, logger }) => { + 'astro:config:setup': async ({ + updateConfig, + injectScript, + addMiddleware, + config, + command, + logger, + injectRoute, + }) => { // The third param here enables loading of all env vars, regardless of prefix // see: https://main.vitejs.dev/config/#using-environment-variables-in-config @@ -111,6 +119,19 @@ export const sentryAstro = (options: SentryOptions = {}): AstroIntegration => { entrypoint: '@sentry/astro/middleware', }); } + + if (options.tunnelRoute) { + if (options.tunnelRoute.projectIds.length === 0) { + throw new Error( + 'When using the `tunnelRoute` configuration, you need to provide at least one project Id in `projectIds`', + ); + } + + injectRoute({ + pattern: options.tunnelRoute.pattern, + entryPoint: '@sentry/astro/tunnel', + }); + } }, }, }; diff --git a/packages/astro/src/integration/tunnel.ts b/packages/astro/src/integration/tunnel.ts new file mode 100644 index 000000000000..210bfd505074 --- /dev/null +++ b/packages/astro/src/integration/tunnel.ts @@ -0,0 +1,39 @@ +import type { APIRoute } from 'astro'; +import { config } from 'virtual:@sentry/astro/tunnel-config'; + +export const prerender = false; + +export const POST: APIRoute = async ({ request }) => { + try { + const envelope = await request.text(); + const piece = envelope.split('\n')[0]; + const header = JSON.parse(piece); + + const dsn = new URL(header.dsn); + if (dsn.hostname !== config.host) { + throw new Error(`Invalid Sentry host: ${dsn.hostname}`); + } + + const projectId = dsn.pathname.substring(1); + if (!config.projectIds.includes(projectId)) { + throw new Error(`Invalid Project ID: ${projectId}`); + } + + const url = `https://${config.host}/api/${projectId}/envelope/`; + const res = await fetch(url, { + method: 'POST', + body: envelope, + headers: { + 'Content-Type': 'application/x-sentry-envelope', + }, + }); + + if (!res.ok) { + throw new Error(await res.text()); + } + + return Response(null, { status: 204 }); + } catch (error) { + return Response.json({ message: (error as Error).message }, { status: 400 }); + } +}; diff --git a/packages/astro/src/integration/types.ts b/packages/astro/src/integration/types.ts index 6f182427ee47..17de741d5d8c 100644 --- a/packages/astro/src/integration/types.ts +++ b/packages/astro/src/integration/types.ts @@ -126,6 +126,33 @@ type SdkEnabledOptions = { }; }; +type SdkTunnelOptions = { + /** + * Options for tunneling. + */ + tunnelRoute?: { + /** + * Where the route should be output in the browser, for example `/api/sy`. It's recommended + * not to put terms related to Sentry or tracking as they can be flagged and blocked by + * ad-blockers. + * + * @see https://docs.astro.build/en/reference/integrations-reference/#injectroute-option + */ + pattern: string; + /** + * The project slug of your Sentry project. + * + * @default "sentry.io" + */ + host?: string; + /** + * Restricts to what project your tunnel can forward Sentry events to. There should be at least + * one project id. + */ + projectIds: Array; + }; +}; + /** * A subset of Sentry SDK options that can be set via the `sentryAstro` integration. * Some options (e.g. integrations) are set by default and cannot be changed here. @@ -140,4 +167,5 @@ export type SentryOptions = SdkInitPaths & Pick & SourceMapsOptions & InstrumentationOptions & - SdkEnabledOptions; + SdkEnabledOptions & + SdkTunnelOptions; diff --git a/packages/astro/src/integration/virtual-imports.ts b/packages/astro/src/integration/virtual-imports.ts new file mode 100644 index 000000000000..3d512977a525 --- /dev/null +++ b/packages/astro/src/integration/virtual-imports.ts @@ -0,0 +1,30 @@ +import type { Plugin } from 'vite'; +import type { SentryOptions } from './types'; + +function resolveVirtualModuleId(id: T): `\0${T}` { + return `\0${id}`; +} + +export type VirtualImportsParams = Pick['tunnelRoute'], 'host' | 'projectIds'>; + +export const virtualImportsPlugin = ({ host = 'sentry.io', projectIds }: VirtualImportsParams): Plugin => { + const modules: Record = { + 'virtual:@sentry/astro/tunnel-config': `export const config = ${JSON.stringify({ host, projectIds })}`, + }; + + /** Mapping names prefixed with `\0` to their original form. */ + const resolutionMap = Object.fromEntries( + (Object.keys(modules) as (keyof typeof modules)[]).map(key => [resolveVirtualModuleId(key), key]), + ); + + return { + name: '@sentry/astro/virtual', + resolveId(id) { + if (id in modules) return resolveVirtualModuleId(id); + }, + load(id) { + const resolution = resolutionMap[id]; + if (resolution) return modules[resolution]; + }, + }; +}; diff --git a/packages/astro/src/integration/virtual.d.ts b/packages/astro/src/integration/virtual.d.ts new file mode 100644 index 000000000000..3a76435c2993 --- /dev/null +++ b/packages/astro/src/integration/virtual.d.ts @@ -0,0 +1,3 @@ +declare module 'virtual:@sentry/astro/tunnel-config' { + export const config: import('./tunnel').VirtualImportsParams; +} diff --git a/packages/astro/tsconfig.json b/packages/astro/tsconfig.json index bf45a09f2d71..7e1fe0e7f721 100644 --- a/packages/astro/tsconfig.json +++ b/packages/astro/tsconfig.json @@ -4,6 +4,7 @@ "include": ["src/**/*"], "compilerOptions": { + "lib": ["ES2019"] // package-specific options } }