Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/astro/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
23 changes: 22 additions & 1 deletion packages/astro/src/integration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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',
});
}
},
},
};
Expand Down
39 changes: 39 additions & 0 deletions packages/astro/src/integration/tunnel.ts
Original file line number Diff line number Diff line change
@@ -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 });
}
};
30 changes: 29 additions & 1 deletion packages/astro/src/integration/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>;
};
};

/**
* 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.
Expand All @@ -140,4 +167,5 @@ export type SentryOptions = SdkInitPaths &
Pick<BrowserOptions, 'replaysSessionSampleRate' | 'replaysOnErrorSampleRate'> &
SourceMapsOptions &
InstrumentationOptions &
SdkEnabledOptions;
SdkEnabledOptions &
SdkTunnelOptions;
30 changes: 30 additions & 0 deletions packages/astro/src/integration/virtual-imports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import type { Plugin } from 'vite';
import type { SentryOptions } from './types';

function resolveVirtualModuleId<T extends string>(id: T): `\0${T}` {
return `\0${id}`;
}

export type VirtualImportsParams = Pick<Required<SentryOptions>['tunnelRoute'], 'host' | 'projectIds'>;

export const virtualImportsPlugin = ({ host = 'sentry.io', projectIds }: VirtualImportsParams): Plugin => {
const modules: Record<string, string> = {
'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];
},
};
};
3 changes: 3 additions & 0 deletions packages/astro/src/integration/virtual.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare module 'virtual:@sentry/astro/tunnel-config' {
export const config: import('./tunnel').VirtualImportsParams;
}
1 change: 1 addition & 0 deletions packages/astro/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"include": ["src/**/*"],

"compilerOptions": {
"lib": ["ES2019"]
// package-specific options
}
}