sentry.ts (65 lines of code) (raw):
import * as Sentry from '@sentry/react-native';
import DeviceInfo from 'react-native-device-info';
import appPackage from './package.json';
import {hash, anonymizeYTApiEntityId} from 'util/util';
import type {ErrorEvent, EventHint} from '@sentry/core';
export const hasSentryDsn = () => appPackage.config.SENTRY_DSN.length > 0;
const getDeviceId = async () => DeviceInfo.getUniqueId();
export const initSentry = async (dsn: string) => {
Sentry.init({
dsn,
sendDefaultPii: false,
integrations: [Sentry.breadcrumbsIntegration({console: false})],
tracesSampleRate: 0.1,
profilesSampleRate: 0.1,
// @ts-ignore
beforeSend(event, hint) {
if (isInvalidTokenError(hint) || !isAppCodeError(event)) {
return null;
}
return sanitizeEventUrls(event);
},
});
const deviceId = await getDeviceId();
Sentry.setUser({
id: hash(deviceId, 12),
tablet: DeviceInfo.isTablet(),
geo: {
country_code: '',
region: '',
city: '',
},
});
};
export const captureException = (error: Error) => {
if (hasSentryDsn()) {
Sentry.captureException(error);
}
};
function isAppCodeError(event: ErrorEvent) {
const frames = event.exception?.values?.[0]?.stacktrace?.frames;
if (Array.isArray(frames)) {
return frames.some(frame => !frame.filename?.includes('node_modules'));
}
return true;
}
function isInvalidTokenError(hint: EventHint) {
const error = hint?.originalException;
if (!error || typeof error !== 'object') {
return false;
}
if ('status' in error && typeof error.status === 'number') {
return error.status === 401;
}
return false;
}
function sanitizeEventUrls(event: ErrorEvent) {
const ev = {...event};
if (Array.isArray(ev.breadcrumbs)) {
ev.breadcrumbs.forEach(b => {
if((b.category === 'xhr' || b.category === 'fetch') && b.data?.url) {
b.data.url = anonymizeYTApiEntityId(b.data.url);
}
});
}
return ev;
}