53 lines · 1.9 KB · TypeScript
RawDownload
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())));