mattermost-mobile/app/utils/sentry.ts
Mattermost Build 1b5ec1e00b
Update dependencies and upgrade to RN 0.76.5 (#8421) (#8482)
* update dev deps

* partial update dependencies

* update watermelondb

* update rn to 0.76.5, expo to 52.0.18 and others

* upgrade android firebase

* upgrade detox deps

* fix package-lock.json

* update emm and paste-input

* update turbo-log

* update network library

* fix tests

* review feedback

* fix Keyboard blocking signIn button

* Fall back to iphone 14 iOS 17.2 simulator as app crashes on iOS 17.4

* changes in deleteCredentialsForServer is causing a crash

* withOptions x2 clearly is wrong

* re-add cli-platform-apple fix

* fix: RN 0.76.5 issue with bottom sheet disappearing (#8478)

* experiment, using view vs BottomSheetView

* revert previous & try disabling enableDynamicSizing

* revert an unintended removal

---------

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Co-authored-by: Rahim Rahman <rahim.rahman@mattermost.com>
(cherry picked from commit 2f3dfbbbfa)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2025-01-16 16:22:32 +02:00

216 lines
6 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Platform} from 'react-native';
import {Navigation} from 'react-native-navigation';
import Config from '@assets/config.json';
import ClientError from '@client/rest/error';
import DatabaseManager from '@database/manager';
import {getConfig} from '@queries/servers/system';
import {getCurrentUser} from '@queries/servers/user';
import {getFullErrorMessage} from '@utils/errors';
import {isBetaApp} from '@utils/general';
import {logError, logWarning} from './log';
import type {Database} from '@nozbe/watermelondb';
import type {Breadcrumb, ErrorEvent} from '@sentry/types';
export const BREADCRUMB_UNCAUGHT_APP_ERROR = 'uncaught-app-error';
export const BREADCRUMB_UNCAUGHT_NON_ERROR = 'uncaught-non-error';
let Sentry: any;
export function initializeSentry() {
if (!Config.SentryEnabled) {
return;
}
if (!Sentry) {
Sentry = require('@sentry/react-native');
}
const dsn = getDsn();
if (!dsn) {
logWarning('Sentry is enabled, but not configured on this platform');
return;
}
const mmConfig = {
environment: isBetaApp ? 'beta' : 'production',
tracesSampleRate: isBetaApp ? 1.0 : 0.2,
sampleRate: isBetaApp ? 1.0 : 0.2,
attachStacktrace: Boolean(isBetaApp), // For Beta, stack traces are automatically attached to all messages logged
};
const eventFilter = Array.isArray(Config.SentryOptions?.severityLevelFilter) ? Config.SentryOptions.severityLevelFilter : [];
const sentryOptions = {...Config.SentryOptions};
Reflect.deleteProperty(sentryOptions, 'severityLevelFilter');
Sentry.init({
dsn,
sendDefaultPii: false,
...mmConfig,
...sentryOptions,
enableCaptureFailedRequests: false,
integrations: [
Sentry.reactNativeNavigationIntegration({navigation: Navigation}),
],
beforeSend: (event: ErrorEvent) => {
if (isBetaApp || (event?.level && eventFilter.includes(event.level))) {
return event;
}
return null;
},
});
}
function getDsn() {
if (Platform.OS === 'android') {
return Config.SentryDsnAndroid;
} else if (Platform.OS === 'ios') {
return Config.SentryDsnIos;
}
return '';
}
export function captureException(error: unknown) {
if (!Config.SentryEnabled) {
return;
}
if (!error) {
logWarning('captureException called with missing arguments', error);
return;
}
Sentry.captureException(error);
}
export function captureJSException(error: unknown, isFatal: boolean) {
if (!Config.SentryEnabled) {
return;
}
if (!error) {
logWarning('captureJSException called with missing arguments', error);
return;
}
if (error instanceof ClientError) {
captureClientErrorAsBreadcrumb(error, isFatal);
} else {
captureException(error);
}
}
function captureClientErrorAsBreadcrumb(error: ClientError, isFatal: boolean) {
const isAppError = Boolean(error.server_error_id);
const breadcrumb: Breadcrumb = {
category: isAppError ? BREADCRUMB_UNCAUGHT_APP_ERROR : BREADCRUMB_UNCAUGHT_NON_ERROR,
data: {
isFatal: String(isFatal),
},
level: 'warning',
message: getFullErrorMessage(error),
};
if (breadcrumb.data) {
if (error.server_error_id) {
breadcrumb.data.server_error_id = error.server_error_id;
}
if (error.status_code) {
breadcrumb.data.status_code = error.status_code;
}
const match = (/^(?:https?:\/\/)[^/]+(\/.*)$/).exec(error.url);
if (match && match.length >= 2) {
breadcrumb.data.url = match[1];
}
}
try {
Sentry.addBreadcrumb(breadcrumb);
} catch (e) {
// Do nothing since this is only here to make sure we don't crash when handling an exception
logWarning('Failed to capture breadcrumb of non-error', e);
}
}
const getUserContext = async (database: Database) => {
const currentUser = {
id: 'currentUserId',
locale: 'en',
roles: 'multi-server-test-role',
};
const user = await getCurrentUser(database);
return {
userID: user?.id ?? currentUser.id,
email: '',
username: '',
locale: user?.locale ?? currentUser.locale,
roles: user?.roles ?? currentUser.roles,
};
};
const getExtraContext = async (database: Database) => {
const context = {
config: {},
currentChannel: {},
currentTeam: {},
};
const config = await getConfig(database);
if (config) {
context.config = {
BuildDate: config.BuildDate,
BuildEnterpriseReady: config.BuildEnterpriseReady,
BuildHash: config.BuildHash,
BuildHashEnterprise: config.BuildHashEnterprise,
BuildNumber: config.BuildNumber,
};
}
return context;
};
const getBuildTags = async (database: Database) => {
const tags = {
serverBuildHash: '',
serverBuildNumber: '',
};
const config = await getConfig(database);
if (config) {
tags.serverBuildHash = config.BuildHash;
tags.serverBuildNumber = config.BuildNumber;
}
return tags;
};
export const addSentryContext = async (serverUrl: string) => {
if (!Config.SentryEnabled || !Sentry) {
return;
}
try {
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
const userContext = await getUserContext(database);
Sentry.setContext('User-Information', userContext);
const buildContext = await getBuildTags(database);
Sentry.setContext('App-Build Information', buildContext);
const extraContext = await getExtraContext(database);
Sentry.setContext('Server-Information', extraContext);
} catch (e) {
logError(`addSentryContext for serverUrl ${serverUrl}`, e);
}
};