76 lines · 3.0 KB · TypeScript
RawDownload
// ---------------------------------------------------------------------------// Sentry tunnel — the browser SDK POSTs envelopes to /api/sentry-tunnel// (configured in routes/__root.tsx) to dodge adblockers and CSP. We parse the// envelope header to recover the DSN, validate against our own, and forward the// body to Sentry's ingest endpoint. See// https://docs.sentry.io/platforms/javascript/troubleshooting/#using-the-tunnel-option// --------------------------------------------------------------------------- import { env } from "cloudflare:workers";import { createMiddleware } from "@tanstack/react-start";import { Data, Effect, Schema } from "effect"; class SentryTunnelError extends Data.TaggedError("SentryTunnelError")<{  readonly cause?: unknown;}> {} const SentryEnvelopeHeader = Schema.Struct({  dsn: Schema.optional(Schema.String),}); const decodeSentryEnvelopeHeader = Schema.decodeUnknownEffect(  Schema.fromJsonString(SentryEnvelopeHeader),); const badSentryEnvelopeResponse = () => new Response("bad envelope", { status: 400 }); export const handleSentryTunnelRequest = (request: Request, configuredDsn: string) =>  Effect.gen(function* () {    const envelope = yield* Effect.tryPromise({      try: () => request.text(),      catch: (cause) => new SentryTunnelError({ cause }),    });    const firstLine = envelope.slice(0, envelope.indexOf("\n"));    const header = yield* decodeSentryEnvelopeHeader(firstLine).pipe(      Effect.mapError((cause) => new SentryTunnelError({ cause })),    );    const dsn = header.dsn;    if (!dsn) return new Response("missing dsn", { status: 400 });     const envelopeDsn = yield* Effect.try({      try: () => new URL(dsn),      catch: (cause) => new SentryTunnelError({ cause }),    });    const ourDsn = yield* Effect.try({      try: () => new URL(configuredDsn),      catch: (cause) => new SentryTunnelError({ cause }),    });    if (envelopeDsn.host !== ourDsn.host || envelopeDsn.pathname !== ourDsn.pathname) {      return new Response("dsn mismatch", { status: 400 });    }     const projectId = envelopeDsn.pathname.replace(/^\//, "");    const ingestUrl = `https://${envelopeDsn.host}/api/${projectId}/envelope/`;    return yield* Effect.tryPromise({      try: () =>        fetch(ingestUrl, {          method: "POST",          body: envelope,          headers: { "Content-Type": "application/x-sentry-envelope" },        }),      catch: (cause) => new SentryTunnelError({ cause }),    });  }).pipe(Effect.catch(() => Effect.succeed(badSentryEnvelopeResponse()))); export const sentryTunnelMiddleware = createMiddleware({ type: "request" }).server(  ({ pathname, request, next }) => {    if (pathname !== "/api/sentry-tunnel" || request.method !== "POST") {      return next();    }     const configuredDsn = (env as { SENTRY_DSN?: string }).SENTRY_DSN;    if (!configuredDsn) return new Response(null, { status: 204 });     return Effect.runPromise(handleSentryTunnelRequest(request, configuredDsn));  },);