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())));
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())));