INS-1501: Sentry integration. (#4817)

* Install Sentry.

* Improve fetch typings.

* INS-1501: Sentry integration

* Split Sentry logic between UI and main.

* TSDoc

* Move sentry DSN into config.json.

* Fix doc comments.

(Doc'ing code like it's 1997.)

* Make long comments hilariously unreadable.

* Remove unused segment event.

* Re-export app environment.

Co-authored-by: Dimitri Mitropoulos <dimitrimitropoulos@gmail.com>
This commit is contained in:
John Chadwick
2022-06-02 16:41:06 +00:00
committed by GitHub
parent c49111b63a
commit ccdc84daba
15 changed files with 399 additions and 63 deletions

View File

@@ -15,6 +15,7 @@
"development": "rTOCSvGV23cHGJyb3HI9EUQDNA6ar7ay",
"production": "4l7QUfACrIcqvC913hiIwAA2BDYP2OJ1"
},
"sentryDsn": "https://aaec2e800e644070a8daba5b7ad02c16@o1147619.ingest.sentry.io/6311804",
"plugins": [
"insomnia-plugin-base64",
"insomnia-plugin-hash",

View File

@@ -14,6 +14,7 @@
"@grpc/proto-loader": "^0.5.5",
"@hapi/hawk": "^8.0.0",
"@jest/globals": "^28.1.0",
"@sentry/electron": "^3.0.7",
"@stoplight/spectral": "^5.9.0",
"analytics-node": "^6.0.0",
"aws4": "^1.11.0",
@@ -3217,6 +3218,118 @@
"join-component": "^1.1.0"
}
},
"node_modules/@sentry/browser": {
"version": "6.19.2",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.19.2.tgz",
"integrity": "sha512-5VC44p5Vu2eJhVT39nLAJFgha5MjHDYCyZRR1ieeZt3a++otojPGBBAKNAtrEMGV+A2Z9AoneD6ZnDVlyb3GKg==",
"dependencies": {
"@sentry/core": "6.19.2",
"@sentry/types": "6.19.2",
"@sentry/utils": "6.19.2",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@sentry/core": {
"version": "6.19.2",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.19.2.tgz",
"integrity": "sha512-yu1R3ewBT4udmB4v7sc4biQZ0Z0rfB9+TzB5ZKoCftbe6kqXjFMMaFRYNUF9HicVldKAsBktgkWw3+yfqGkw/A==",
"dependencies": {
"@sentry/hub": "6.19.2",
"@sentry/minimal": "6.19.2",
"@sentry/types": "6.19.2",
"@sentry/utils": "6.19.2",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@sentry/electron": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@sentry/electron/-/electron-3.0.7.tgz",
"integrity": "sha512-Rahi1jgvjHnx1jGkkPPvDCxSCAME7xc2eBcFCLb4R/WDuNblR7tgJUuAtzv9JpxUgRHy1oLNct0wcvIu1mcXoA==",
"dependencies": {
"@sentry/browser": "6.19.2",
"@sentry/core": "6.19.2",
"@sentry/node": "6.19.2",
"@sentry/types": "6.19.2",
"@sentry/utils": "6.19.2",
"deepmerge": "^4.2.2",
"tslib": "^2.3.1"
}
},
"node_modules/@sentry/electron/node_modules/tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
},
"node_modules/@sentry/hub": {
"version": "6.19.2",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.19.2.tgz",
"integrity": "sha512-W7KCgNBgdBIMagOxy5J5KQPe+maYxSqfE8a5ncQ3R8BcZDQEKnkW/1FplNbfRLZqA/tL/ndKb7pTPqVtzsbARw==",
"dependencies": {
"@sentry/types": "6.19.2",
"@sentry/utils": "6.19.2",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@sentry/minimal": {
"version": "6.19.2",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.19.2.tgz",
"integrity": "sha512-ClwxKm77iDHET7kpzv1JvzDx1er5DoNu+EUjst0kQzARIrXvu9xuZuE2/CnBWycQWqw8o3HoGoKz65uIhsUCzQ==",
"dependencies": {
"@sentry/hub": "6.19.2",
"@sentry/types": "6.19.2",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@sentry/node": {
"version": "6.19.2",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-6.19.2.tgz",
"integrity": "sha512-Z1qREpTpYHxaeWjc1zMUk8ZTAp1WbxMiI2TVNc+a14DVT19Z2xNXb06MiRfeLgNc9lVGdmzR62dPmMBjVgPJYg==",
"dependencies": {
"@sentry/core": "6.19.2",
"@sentry/hub": "6.19.2",
"@sentry/types": "6.19.2",
"@sentry/utils": "6.19.2",
"cookie": "^0.4.1",
"https-proxy-agent": "^5.0.0",
"lru_map": "^0.3.3",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@sentry/types": {
"version": "6.19.2",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.19.2.tgz",
"integrity": "sha512-XO5qmVBdTs+7PdCz7fAwn1afWxSnRE2KLBFg5/vOdKosPSSHsSHUURSkxiEZc2QsR+JpRB4AeQ26AkIRX38qTg==",
"engines": {
"node": ">=6"
}
},
"node_modules/@sentry/utils": {
"version": "6.19.2",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.19.2.tgz",
"integrity": "sha512-2DQQ2OJaxjtyxGq5FmMlqb6hptsqMs2xoBiVRMkTS/rvyTrk1oQdKZ8ePwjtgX3nJ728ni3IXIyXV+vfGp4EBw==",
"dependencies": {
"@sentry/types": "6.19.2",
"tslib": "^1.9.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@sideway/address": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.2.tgz",
@@ -6946,7 +7059,6 @@
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -7528,7 +7640,6 @@
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -15169,6 +15280,11 @@
"node": "*"
}
},
"node_modules/lru_map": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz",
"integrity": "sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0="
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -22677,6 +22793,99 @@
"join-component": "^1.1.0"
}
},
"@sentry/browser": {
"version": "6.19.2",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-6.19.2.tgz",
"integrity": "sha512-5VC44p5Vu2eJhVT39nLAJFgha5MjHDYCyZRR1ieeZt3a++otojPGBBAKNAtrEMGV+A2Z9AoneD6ZnDVlyb3GKg==",
"requires": {
"@sentry/core": "6.19.2",
"@sentry/types": "6.19.2",
"@sentry/utils": "6.19.2",
"tslib": "^1.9.3"
}
},
"@sentry/core": {
"version": "6.19.2",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.19.2.tgz",
"integrity": "sha512-yu1R3ewBT4udmB4v7sc4biQZ0Z0rfB9+TzB5ZKoCftbe6kqXjFMMaFRYNUF9HicVldKAsBktgkWw3+yfqGkw/A==",
"requires": {
"@sentry/hub": "6.19.2",
"@sentry/minimal": "6.19.2",
"@sentry/types": "6.19.2",
"@sentry/utils": "6.19.2",
"tslib": "^1.9.3"
}
},
"@sentry/electron": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/@sentry/electron/-/electron-3.0.7.tgz",
"integrity": "sha512-Rahi1jgvjHnx1jGkkPPvDCxSCAME7xc2eBcFCLb4R/WDuNblR7tgJUuAtzv9JpxUgRHy1oLNct0wcvIu1mcXoA==",
"requires": {
"@sentry/browser": "6.19.2",
"@sentry/core": "6.19.2",
"@sentry/node": "6.19.2",
"@sentry/types": "6.19.2",
"@sentry/utils": "6.19.2",
"deepmerge": "^4.2.2",
"tslib": "^2.3.1"
},
"dependencies": {
"tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
}
}
},
"@sentry/hub": {
"version": "6.19.2",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.19.2.tgz",
"integrity": "sha512-W7KCgNBgdBIMagOxy5J5KQPe+maYxSqfE8a5ncQ3R8BcZDQEKnkW/1FplNbfRLZqA/tL/ndKb7pTPqVtzsbARw==",
"requires": {
"@sentry/types": "6.19.2",
"@sentry/utils": "6.19.2",
"tslib": "^1.9.3"
}
},
"@sentry/minimal": {
"version": "6.19.2",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.19.2.tgz",
"integrity": "sha512-ClwxKm77iDHET7kpzv1JvzDx1er5DoNu+EUjst0kQzARIrXvu9xuZuE2/CnBWycQWqw8o3HoGoKz65uIhsUCzQ==",
"requires": {
"@sentry/hub": "6.19.2",
"@sentry/types": "6.19.2",
"tslib": "^1.9.3"
}
},
"@sentry/node": {
"version": "6.19.2",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-6.19.2.tgz",
"integrity": "sha512-Z1qREpTpYHxaeWjc1zMUk8ZTAp1WbxMiI2TVNc+a14DVT19Z2xNXb06MiRfeLgNc9lVGdmzR62dPmMBjVgPJYg==",
"requires": {
"@sentry/core": "6.19.2",
"@sentry/hub": "6.19.2",
"@sentry/types": "6.19.2",
"@sentry/utils": "6.19.2",
"cookie": "^0.4.1",
"https-proxy-agent": "^5.0.0",
"lru_map": "^0.3.3",
"tslib": "^1.9.3"
}
},
"@sentry/types": {
"version": "6.19.2",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.19.2.tgz",
"integrity": "sha512-XO5qmVBdTs+7PdCz7fAwn1afWxSnRE2KLBFg5/vOdKosPSSHsSHUURSkxiEZc2QsR+JpRB4AeQ26AkIRX38qTg=="
},
"@sentry/utils": {
"version": "6.19.2",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.19.2.tgz",
"integrity": "sha512-2DQQ2OJaxjtyxGq5FmMlqb6hptsqMs2xoBiVRMkTS/rvyTrk1oQdKZ8ePwjtgX3nJ728ni3IXIyXV+vfGp4EBw==",
"requires": {
"@sentry/types": "6.19.2",
"tslib": "^1.9.3"
}
},
"@sideway/address": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.2.tgz",
@@ -25672,8 +25881,7 @@
"cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"dev": true
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="
},
"copy-to-clipboard": {
"version": "3.3.1",
@@ -26132,8 +26340,7 @@
"deepmerge": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==",
"dev": true
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
},
"defer-to-connect": {
"version": "1.1.3",
@@ -31959,6 +32166,11 @@
}
}
},
"lru_map": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz",
"integrity": "sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0="
},
"lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",

View File

@@ -45,6 +45,7 @@
"@grpc/proto-loader": "^0.5.5",
"@hapi/hawk": "^8.0.0",
"@jest/globals": "^28.1.0",
"@sentry/electron": "^3.0.7",
"@stoplight/spectral": "^5.9.0",
"analytics-node": "^6.0.0",
"aws4": "^1.11.0",

View File

@@ -15,19 +15,42 @@ export function onCommand(callback: Function) {
_commandListeners.push(callback);
}
export async function post(path, obj, sessionId, compressBody = false) {
return _fetch('POST', path, obj, sessionId, compressBody);
export async function post<T = any>(path: string, obj: unknown, sessionId: string | null, compressBody = false): Promise<T | string> {
return _fetch<T>('POST', path, obj, sessionId, compressBody);
}
export async function put(path, obj, sessionId, compressBody = false) {
return _fetch('PUT', path, obj, sessionId, compressBody);
export async function postJson<T = any>(path: string, obj: unknown, sessionId: string | null, compressBody = false): Promise<T> {
const response = await post<T>(path, obj, sessionId, compressBody);
if (typeof response === 'string') {
throw new Error('Unexpected plaintext response');
}
return response;
}
export async function get(path, sessionId) {
return _fetch('GET', path, null, sessionId);
export async function put<T = any>(path: string, obj: unknown, sessionId: string | null, compressBody = false): Promise<T | string> {
return _fetch<T>('PUT', path, obj, sessionId, compressBody);
}
async function _fetch(method, path, obj, sessionId, compressBody = false, retries = 0) {
export async function get<T = any>(path: string, sessionId: string | null): Promise<T | string> {
return _fetch<T>('GET', path, null, sessionId);
}
export async function getJson<T = any>(path: string, sessionId: string | null): Promise<T> {
const response = await get<T>(path, sessionId);
if (typeof response === 'string') {
throw new Error('Unexpected plaintext response');
}
return response;
}
async function _fetch<T = any>(
method: 'POST' | 'PUT' | 'GET',
path: string,
obj: unknown,
sessionId: string | null,
compressBody = false,
retries = 0
): Promise<T | string> {
if (sessionId === undefined) {
throw new Error(`No session ID provided to ${method}:${path}`);
}
@@ -59,7 +82,7 @@ async function _fetch(method, path, obj, sessionId, compressBody = false, retrie
config.headers['X-Session-Id'] = sessionId;
}
let response;
let response: Response | undefined;
const url = _getUrl(path);

View File

@@ -5,6 +5,39 @@ import * as fetch from './fetch';
type LoginCallback = (isLoggedIn: boolean) => void;
export interface WhoamiResponse {
sessionAge: number;
accountId: string;
email: string;
firstName: string;
lastName: string;
created: number;
publicKey: string;
encSymmetricKey: string;
encPrivateKey: string;
saltEnc: string;
isPaymentRequired: boolean;
isTrialing: boolean;
isVerified: boolean;
isAdmin: boolean;
trialEnd: string;
planName: string;
planId: string;
canManageTeams: boolean;
maxTeamMembers: number;
}
export interface SessionData {
accountId: string;
id: string;
email: string;
firstName: string;
lastName: string;
symmetricKey: unknown;
publicKey: unknown;
encPrivateKey: unknown;
}
const loginCallbacks: LoginCallback[] = [];
function _callCallbacks() {
@@ -75,7 +108,7 @@ export async function login(rawEmail: string, rawPassphrase: string) {
// Initialize the Session //
// ~~~~~~~~~~~~~~~~~~~~~~ //
// Compute K (used for session ID)
const sessionId = c.computeK().toString('hex');
const sessionId = (c.computeK() as Buffer).toString('hex');
// Get and store some extra info (salts and keys)
const {
publicKey,
@@ -119,7 +152,7 @@ export async function changePasswordWithToken(rawNewPassphrase, confirmationCode
.computeVerifier(
_getSrpParams(),
Buffer.from(saltAuth, 'hex'),
Buffer.from(newEmail, 'utf8'),
Buffer.from(newEmail || '', 'utf8'),
Buffer.from(newAuthSecret, 'hex'),
)
.toString('hex');
@@ -145,11 +178,11 @@ export function sendPasswordChangeCode() {
}
export function getPublicKey() {
return _getSessionData().publicKey;
return _getSessionData()?.publicKey;
}
export function getPrivateKey() {
const { symmetricKey, encPrivateKey } = _getSessionData();
const { symmetricKey, encPrivateKey } = _getSessionData() || {};
const privateKeyStr = crypt.decryptAES(symmetricKey, encPrivateKey);
return JSON.parse(privateKeyStr);
@@ -164,19 +197,19 @@ export function getCurrentSessionId() {
}
export function getAccountId() {
return _getSessionData().accountId;
return _getSessionData()?.accountId;
}
export function getEmail() {
return _getSessionData().email;
return _getSessionData()?.email;
}
export function getFirstName() {
return _getSessionData().firstName;
return _getSessionData()?.firstName;
}
export function getLastName() {
return _getSessionData().lastName;
return _getSessionData()?.lastName;
}
export function getFullName() {
@@ -205,16 +238,16 @@ export async function logout() {
/** Set data for the new session and store it encrypted with the sessionId */
export function setSessionData(
sessionId,
accountId,
firstName,
lastName,
email,
sessionId: string,
accountId: string,
firstName: string,
lastName: string,
email: string,
symmetricKey,
publicKey,
encPrivateKey,
) {
const dataStr = JSON.stringify({
const sessionData: SessionData = {
id: sessionId,
accountId: accountId,
symmetricKey: symmetricKey,
@@ -223,7 +256,8 @@ export function setSessionData(
email: email,
firstName: firstName,
lastName: lastName,
});
};
const dataStr = JSON.stringify(sessionData);
window.localStorage.setItem(_getSessionKey(sessionId), dataStr);
// NOTE: We're setting this last because the stuff above might fail
window.localStorage.setItem('currentSessionId', sessionId);
@@ -236,13 +270,11 @@ export async function listTeams() {
// Helper Functions //
// ~~~~~~~~~~~~~~~~ //
function _getSymmetricKey() {
const sessionData = _getSessionData();
return sessionData.symmetricKey;
return _getSessionData()?.symmetricKey;
}
function _whoami(sessionId = null) {
return fetch.get('/auth/whoami', sessionId || getCurrentSessionId());
function _whoami(sessionId: string | null = null): Promise<WhoamiResponse> {
return fetch.getJson<WhoamiResponse>('/auth/whoami', sessionId || getCurrentSessionId());
}
function _getAuthSalts(email) {
@@ -255,7 +287,7 @@ function _getAuthSalts(email) {
);
}
const _getSessionData = () => {
const _getSessionData = (): Partial<SessionData> | null => {
const sessionId = getCurrentSessionId();
if (!sessionId || !window) {
@@ -266,7 +298,7 @@ const _getSessionData = () => {
if (dataStr === null) {
return null;
}
return JSON.parse(dataStr);
return JSON.parse(dataStr) as SessionData;
};
function _unsetSessionData() {

View File

@@ -53,7 +53,6 @@ const sendSegment = async (segmentType: 'track' | 'page', options) => {
export enum SegmentEvent {
appStarted = 'App Started',
collectionCreate = 'Collection Created',
criticalError = 'Critical Error Encountered',
dataExport = 'Data Exported',
dataImport = 'Data Imported',
documentCreate = 'Document Created',

View File

@@ -20,9 +20,10 @@ export const getAppPlatform = () => process.platform;
export const isMac = () => getAppPlatform() === 'darwin';
export const isLinux = () => getAppPlatform() === 'linux';
export const isWindows = () => getAppPlatform() === 'win32';
const getAppEnvironment = () => process.env.INSOMNIA_ENV || 'production';
export const getAppEnvironment = () => process.env.INSOMNIA_ENV || 'production';
export const isDevelopment = () => getAppEnvironment() === 'development';
export const getSegmentWriteKey = () => appConfig.segmentWriteKeys[(isDevelopment() || env.PLAYWRIGHT) ? 'development' : 'production'];
export const getSentryDsn = () => appConfig.sentryDsn;
export const getAppBuildDate = () => new Date(process.env.BUILD_DATE ?? '').toLocaleDateString();
export const getBrowserUserAgent = () => encodeURIComponent(

View File

@@ -0,0 +1,9 @@
import type { ElectronOptions } from '@sentry/electron';
import { getAppEnvironment, getAppVersion, getSentryDsn } from './constants';
export const SENTRY_OPTIONS: Partial<ElectronOptions> = {
dsn: getSentryDsn(),
environment: getAppEnvironment(),
release: getAppVersion(),
};

View File

@@ -11,8 +11,8 @@ import { database } from './common/database';
import { disableSpellcheckerDownload } from './common/electron-helpers';
import log, { initializeLogging } from './common/log';
import { validateInsomniaConfig } from './common/validate-insomnia-config';
import * as errorHandling from './main/error-handling';
import * as grpcIpcMain from './main/grpc-ipc-main';
import { initializeSentry, sentryWatchAnalyticsEnabled } from './main/sentry';
import { checkIfRestartNeeded } from './main/squirrel-startup';
import * as updates from './main/updates';
import * as windowUtils from './main/window-utils';
@@ -23,6 +23,8 @@ import { authorizeUserInWindow } from './network/o-auth-2/misc';
import installPlugin from './plugins/install';
import type { ToastNotification } from './ui/components/toast';
initializeSentry();
// Handle potential auto-update
if (checkIfRestartNeeded()) {
process.exit(0);
@@ -84,7 +86,7 @@ app.on('ready', async () => {
// Init some important things first
await database.init(models.types());
await _createModelInstances();
errorHandling.init();
sentryWatchAnalyticsEnabled();
windowUtils.init();
await _launchApp();

View File

@@ -1,8 +0,0 @@
export function init() {
process.on('uncaughtException', err => {
console.error('[catcher] Uncaught exception:', err.stack);
});
process.on('unhandledRejection', (err: Error) => {
console.error('[catcher] Unhandled rejection:', err.stack);
});
}

View File

@@ -0,0 +1,46 @@
import * as Sentry from '@sentry/electron/main';
import type { SentryRequestType } from '@sentry/types';
import { database as db } from '../common/database';
import { SENTRY_OPTIONS } from '../common/sentry';
import * as models from '../models/index';
import { isSettings } from '../models/settings';
let enabled = false;
/**
* Watch setting for changes. This must be called after the DB is initialized.
*/
export function sentryWatchAnalyticsEnabled() {
models.settings.getOrCreate().then(settings => {
enabled = settings.enableAnalytics;
});
db.onChange(async changes => {
for (const change of changes) {
const [event, doc] = change;
if (isSettings(doc) && event === 'update') {
enabled = doc.enableAnalytics;
}
}
});
}
// TODO(johnwchadwick): We are vendoring ElectronOfflineNetTransport just to be able to control whether or not sending is allowed, because we don't have a choice right now. We should work with the upstream library to get similar functionality upstream. See getsentry/sentry-electron#489.
// https://github.com/getsentry/sentry-electron/issues/489
class ElectronSwitchableTransport extends Sentry.ElectronOfflineNetTransport {
protected _isRateLimited(requestType: SentryRequestType) {
if (!enabled) {
return true;
}
return super._isRateLimited(requestType);
}
}
export function initializeSentry() {
Sentry.init({
...SENTRY_OPTIONS,
transport: ElectronSwitchableTransport,
});
}

View File

@@ -869,7 +869,7 @@ export class VCS {
// the user created snapshots while not logged in
for (const snapshot of snapshots) {
if (snapshot.author === '') {
snapshot.author = accountId;
snapshot.author = accountId || '';
}
}

View File

@@ -145,7 +145,7 @@ export class Toast extends PureComponent<{}, State> {
updatesNotSupported: !updatesSupported(),
version: getAppVersion(),
};
notification = await fetch.post('/notification', data, session.getCurrentSessionId());
notification = await fetch.postJson<ToastNotification>('/notification', data, session.getCurrentSessionId());
} catch (err) {
console.warn('[toast] Failed to fetch user notifications', err);
}

View File

@@ -4,7 +4,6 @@ import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { SegmentEvent, trackSegmentEvent } from '../common/analytics';
import { getProductName, isDevelopment } from '../common/constants';
import { database as db } from '../common/database';
import { initializeLogging } from '../common/log';
@@ -14,9 +13,11 @@ import { init as initPlugins } from '../plugins';
import { applyColorScheme } from '../plugins/misc';
import App from './containers/app';
import { init as initStore } from './redux/modules';
import { initializeSentry } from './sentry';
import './css/index.less'; // this import must come after `App`. the reason is not yet known.
initializeSentry();
initializeLogging();
// Handy little helper
document.body.setAttribute('data-platform', process.platform);
@@ -24,7 +25,9 @@ document.title = getProductName();
(async function() {
await db.initClient();
await initPlugins();
const settings = await models.settings.getOrCreate();
if (settings.clearOAuth2SessionOnRestart) {
@@ -56,18 +59,6 @@ if (isDevelopment()) {
window.db = db;
}
// Catch uncaught errors and report them
if (window && !isDevelopment()) {
window.addEventListener('error', e => {
console.error('Uncaught Error', e.error || e);
trackSegmentEvent(SegmentEvent.criticalError, { detail: e?.message });
});
window.addEventListener('unhandledrejection', e => {
console.error('Unhandled Promise', e.reason);
trackSegmentEvent(SegmentEvent.criticalError, { detail: e?.reason });
});
}
function showUpdateNotification() {
console.log('[app] Update Available');
// eslint-disable-next-line no-new

View File

@@ -0,0 +1,27 @@
import * as Sentry from '@sentry/electron';
import { getAccountId, onLoginLogout } from '../account/session';
import { SENTRY_OPTIONS } from '../common/sentry';
/** Configures user info in Sentry scope. */
function sentryConfigureUserInfo() {
Sentry.configureScope(scope => {
const id = getAccountId();
if (id) {
scope.setUser({ id });
} else {
scope.setUser(null);
}
});
}
/** Watches user info for changes. */
function sentryWatchUserInfo() {
sentryConfigureUserInfo();
onLoginLogout(() => sentryConfigureUserInfo());
}
export function initializeSentry() {
Sentry.init(SENTRY_OPTIONS);
sentryWatchUserInfo();
}