Skip to content
Open
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
5 changes: 5 additions & 0 deletions src/lib/sentry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// ABOUTME: Provides crash reporting with stack traces, issue grouping, and performance metrics

import * as Sentry from '@sentry/react';
import { shouldDropHandledMediaHttpClientEvent } from '@/lib/sentryHttpClientFilter';

/**
* Initialize Sentry error tracking
Expand Down Expand Up @@ -105,6 +106,10 @@ export function initializeSentry() {

// Don't send PII
beforeSend(event) {
if (shouldDropHandledMediaHttpClientEvent(event)) {
return null;
}

// Scrub any potential PII from the event
if (event.user) {
// Only keep anonymized user ID (pubkey is already pseudonymous)
Expand Down
138 changes: 138 additions & 0 deletions src/lib/sentryHttpClientFilter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { describe, expect, it } from 'vitest';
import { shouldDropHandledMediaHttpClientEvent } from '@/lib/sentryHttpClientFilter';

interface TestEventOptions {
url?: string;
statusCode?: number | string;
mechanismType?: string;
message?: string;
}

function createHttpClientEvent({
url = 'https://media.divine.video/hash/hls/master.m3u8',
statusCode = 401,
mechanismType = 'auto.http.client.fetch',
message,
}: TestEventOptions = {}) {
const resolvedMessage = message ?? `HTTP Client Error with status code: ${statusCode}`;

return {
message: resolvedMessage,
request: { url },
contexts: { response: { status_code: statusCode } },
exception: {
values: [
{
value: resolvedMessage,
mechanism: {
type: mechanismType,
},
},
],
},
};
}

describe('shouldDropHandledMediaHttpClientEvent', () => {
it('drops 401 failures for protected media assets', () => {
const event = createHttpClientEvent({
url: 'https://media.divine.video/43debb/hls/master.m3u8',
statusCode: 401,
});

expect(shouldDropHandledMediaHttpClientEvent(event)).toBe(true);
});

it('drops 403 failures for protected media assets', () => {
const event = createHttpClientEvent({
url: 'https://media.divine.video/43debb/downloads/default.mp4',
statusCode: 403,
});

expect(shouldDropHandledMediaHttpClientEvent(event)).toBe(true);
});

it('drops 404 failures for subtitle endpoints', () => {
const event = createHttpClientEvent({
url: 'https://media.divine.video/f423713/vtt',
statusCode: 404,
});

expect(shouldDropHandledMediaHttpClientEvent(event)).toBe(true);
});

it('drops 422 failures for subtitle files', () => {
const event = createHttpClientEvent({
url: 'https://media.divine.video/f423713/captions/en.vtt',
statusCode: 422,
});

expect(shouldDropHandledMediaHttpClientEvent(event)).toBe(true);
});

it('drops 404 failures for optional preview assets', () => {
const event = createHttpClientEvent({
url: 'https://media.divine.video/f423713/poster.jpg',
statusCode: 404,
});

expect(shouldDropHandledMediaHttpClientEvent(event)).toBe(true);
});

it('keeps media 404 failures for non-optional playback assets', () => {
const event = createHttpClientEvent({
url: 'https://media.divine.video/f423713/hls/master.m3u8',
statusCode: 404,
});

expect(shouldDropHandledMediaHttpClientEvent(event)).toBe(false);
});

it('keeps media 5xx failures visible', () => {
const event = createHttpClientEvent({
url: 'https://media.divine.video/f423713/hls/master.m3u8',
statusCode: 500,
});

expect(shouldDropHandledMediaHttpClientEvent(event)).toBe(false);
});

it('keeps failures from non-media hosts', () => {
const event = createHttpClientEvent({
url: 'https://api.divine.video/api/videos',
statusCode: 401,
});

expect(shouldDropHandledMediaHttpClientEvent(event)).toBe(false);
});

it('keeps non-http-client errors even when URL/status match', () => {
const event = createHttpClientEvent({
url: 'https://media.divine.video/43debb/hls/master.m3u8',
statusCode: 401,
mechanismType: 'generic',
message: 'Something else happened',
});

expect(shouldDropHandledMediaHttpClientEvent(event)).toBe(false);
});

it('uses message fallback when contexts.response.status_code is missing', () => {
const event = createHttpClientEvent({
url: 'https://media.divine.video/f423713/vtt',
statusCode: 422,
});
(event.contexts.response as { status_code?: number | string }).status_code = undefined;

expect(shouldDropHandledMediaHttpClientEvent(event)).toBe(true);
});

it('keeps events with invalid URLs', () => {
const event = createHttpClientEvent({
url: 'not-a-valid-url',
statusCode: 401,
});

expect(shouldDropHandledMediaHttpClientEvent(event)).toBe(false);
});
});
161 changes: 161 additions & 0 deletions src/lib/sentryHttpClientFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
type SentryExceptionValue = {
value?: unknown;
mechanism?: {
type?: unknown;
data?: Record<string, unknown>;
};
};

type SentryEventLike = {
message?: unknown;
request?: {
url?: unknown;
};
contexts?: {
response?: {
status_code?: unknown;
};
};
exception?: {
values?: SentryExceptionValue[];
};
};

const MEDIA_HOSTNAME = 'media.divine.video';
const HTTP_CLIENT_MESSAGE_RE = /^HTTP Client Error with status code:\s*(\d{3})$/i;
const OPTIONAL_IMAGE_EXTENSION_RE = /\.(?:avif|gif|jpe?g|png|webp)$/i;
const OPTIONAL_PREVIEW_PATH_SEGMENT_RE = /\/(?:poster|preview|thumbnail|thumb)(?:\/|$)/i;
const SUBTITLE_PATH_RE = /(?:\/vtt(?:\/|$)|\.vtt$)/i;

function toSafeString(value: unknown): string | null {
return typeof value === 'string' && value.length > 0 ? value : null;
}

function parseStatusFromHttpClientMessage(message: string | null): number | null {
if (!message) return null;
const match = message.match(HTTP_CLIENT_MESSAGE_RE);
if (!match) return null;
const statusCode = Number.parseInt(match[1], 10);
return Number.isFinite(statusCode) ? statusCode : null;
}

function extractStatusCode(event: SentryEventLike): number | null {
const rawStatusCode = event.contexts?.response?.status_code;
if (typeof rawStatusCode === 'number' && Number.isFinite(rawStatusCode)) {
return rawStatusCode;
}
if (typeof rawStatusCode === 'string' && rawStatusCode.trim().length > 0) {
const parsedStatusCode = Number.parseInt(rawStatusCode, 10);
if (Number.isFinite(parsedStatusCode)) {
return parsedStatusCode;
}
}

const statusFromMessage = parseStatusFromHttpClientMessage(toSafeString(event.message));
if (statusFromMessage !== null) {
return statusFromMessage;
}

const exceptionValues = event.exception?.values ?? [];
for (const value of exceptionValues) {
const statusFromException = parseStatusFromHttpClientMessage(toSafeString(value.value));
if (statusFromException !== null) {
return statusFromException;
}
}

return null;
}

function hasAutoHttpClientMechanism(event: SentryEventLike): boolean {
const exceptionValues = event.exception?.values ?? [];

return exceptionValues.some((value) => {
const mechanismType = toSafeString(value.mechanism?.type);
if (mechanismType?.startsWith('auto.http.client.')) {
return true;
}

const mechanismDataType = toSafeString(value.mechanism?.data?.type);
return mechanismDataType?.startsWith('auto.http.client.') ?? false;
});
}

function isHttpClientEvent(event: SentryEventLike): boolean {
if (hasAutoHttpClientMechanism(event)) {
return true;
}

if (parseStatusFromHttpClientMessage(toSafeString(event.message)) !== null) {
return true;
}

const exceptionValues = event.exception?.values ?? [];
return exceptionValues.some((value) => (
parseStatusFromHttpClientMessage(toSafeString(value.value)) !== null
));
}

function isSubtitlePath(pathname: string): boolean {
return SUBTITLE_PATH_RE.test(pathname);
}

function isOptionalPreviewPath(pathname: string): boolean {
if (OPTIONAL_PREVIEW_PATH_SEGMENT_RE.test(pathname)) {
return true;
}

return OPTIONAL_IMAGE_EXTENSION_RE.test(pathname);
}

/**
* Filters handled media fetch failures produced by Sentry's httpClientIntegration.
*
* Keep narrow allowlists to avoid hiding actionable production failures:
* - 401/403 on gated media assets
* - 404/422 on optional subtitles and preview/poster images
*/
export function shouldDropHandledMediaHttpClientEvent(event: SentryEventLike): boolean {
if (!isHttpClientEvent(event)) {
return false;
}

const requestUrl = toSafeString(event.request?.url);
if (!requestUrl) {
return false;
}

let parsedUrl: URL;
try {
parsedUrl = new URL(requestUrl);
} catch {
return false;
}

if (parsedUrl.hostname !== MEDIA_HOSTNAME) {
return false;
}

const statusCode = extractStatusCode(event);
if (statusCode === null) {
return false;
}

if (statusCode === 401 || statusCode === 403) {
return true;
}

if (statusCode !== 404 && statusCode !== 422) {
return false;
}

if (isSubtitlePath(parsedUrl.pathname)) {
return true;
}

if (isOptionalPreviewPath(parsedUrl.pathname)) {
return true;
}

return false;
}
Loading