From ccdc84dabaabb0e5ec0edefdd2001bb7e313e721 Mon Sep 17 00:00:00 2001 From: John Chadwick <86682572+johnwchadwick@users.noreply.github.com> Date: Thu, 2 Jun 2022 16:41:06 +0000 Subject: [PATCH] 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 --- packages/insomnia/config/config.json | 1 + packages/insomnia/package-lock.json | 224 +++++++++++++++++- packages/insomnia/package.json | 1 + packages/insomnia/src/account/fetch.ts | 39 ++- packages/insomnia/src/account/session.ts | 76 ++++-- packages/insomnia/src/common/analytics.ts | 1 - packages/insomnia/src/common/constants.ts | 3 +- packages/insomnia/src/common/sentry.ts | 9 + packages/insomnia/src/main.development.ts | 6 +- packages/insomnia/src/main/error-handling.ts | 8 - packages/insomnia/src/main/sentry.ts | 46 ++++ packages/insomnia/src/sync/vcs/vcs.ts | 2 +- packages/insomnia/src/ui/components/toast.tsx | 2 +- packages/insomnia/src/ui/index.tsx | 17 +- packages/insomnia/src/ui/sentry.ts | 27 +++ 15 files changed, 399 insertions(+), 63 deletions(-) create mode 100644 packages/insomnia/src/common/sentry.ts delete mode 100644 packages/insomnia/src/main/error-handling.ts create mode 100644 packages/insomnia/src/main/sentry.ts create mode 100644 packages/insomnia/src/ui/sentry.ts diff --git a/packages/insomnia/config/config.json b/packages/insomnia/config/config.json index 3f914e3fbe..7a190e5f02 100644 --- a/packages/insomnia/config/config.json +++ b/packages/insomnia/config/config.json @@ -15,6 +15,7 @@ "development": "rTOCSvGV23cHGJyb3HI9EUQDNA6ar7ay", "production": "4l7QUfACrIcqvC913hiIwAA2BDYP2OJ1" }, + "sentryDsn": "https://aaec2e800e644070a8daba5b7ad02c16@o1147619.ingest.sentry.io/6311804", "plugins": [ "insomnia-plugin-base64", "insomnia-plugin-hash", diff --git a/packages/insomnia/package-lock.json b/packages/insomnia/package-lock.json index 3809ba765a..60160b6c63 100644 --- a/packages/insomnia/package-lock.json +++ b/packages/insomnia/package-lock.json @@ -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", diff --git a/packages/insomnia/package.json b/packages/insomnia/package.json index 03ad44d01c..bd3bbded9f 100644 --- a/packages/insomnia/package.json +++ b/packages/insomnia/package.json @@ -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", diff --git a/packages/insomnia/src/account/fetch.ts b/packages/insomnia/src/account/fetch.ts index 5017608a3e..ada9ce2ea2 100644 --- a/packages/insomnia/src/account/fetch.ts +++ b/packages/insomnia/src/account/fetch.ts @@ -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(path: string, obj: unknown, sessionId: string | null, compressBody = false): Promise { + return _fetch('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(path: string, obj: unknown, sessionId: string | null, compressBody = false): Promise { + const response = await post(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(path: string, obj: unknown, sessionId: string | null, compressBody = false): Promise { + return _fetch('PUT', path, obj, sessionId, compressBody); } -async function _fetch(method, path, obj, sessionId, compressBody = false, retries = 0) { +export async function get(path: string, sessionId: string | null): Promise { + return _fetch('GET', path, null, sessionId); +} + +export async function getJson(path: string, sessionId: string | null): Promise { + const response = await get(path, sessionId); + if (typeof response === 'string') { + throw new Error('Unexpected plaintext response'); + } + return response; +} + +async function _fetch( + method: 'POST' | 'PUT' | 'GET', + path: string, + obj: unknown, + sessionId: string | null, + compressBody = false, + retries = 0 +): Promise { 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); diff --git a/packages/insomnia/src/account/session.ts b/packages/insomnia/src/account/session.ts index ed13e8f32c..853569791f 100644 --- a/packages/insomnia/src/account/session.ts +++ b/packages/insomnia/src/account/session.ts @@ -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 { + return fetch.getJson('/auth/whoami', sessionId || getCurrentSessionId()); } function _getAuthSalts(email) { @@ -255,7 +287,7 @@ function _getAuthSalts(email) { ); } -const _getSessionData = () => { +const _getSessionData = (): Partial | 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() { diff --git a/packages/insomnia/src/common/analytics.ts b/packages/insomnia/src/common/analytics.ts index deaafdd778..09417e6a9a 100644 --- a/packages/insomnia/src/common/analytics.ts +++ b/packages/insomnia/src/common/analytics.ts @@ -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', diff --git a/packages/insomnia/src/common/constants.ts b/packages/insomnia/src/common/constants.ts index 8bd126f007..5a29e3d24e 100644 --- a/packages/insomnia/src/common/constants.ts +++ b/packages/insomnia/src/common/constants.ts @@ -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( diff --git a/packages/insomnia/src/common/sentry.ts b/packages/insomnia/src/common/sentry.ts new file mode 100644 index 0000000000..df180fb039 --- /dev/null +++ b/packages/insomnia/src/common/sentry.ts @@ -0,0 +1,9 @@ +import type { ElectronOptions } from '@sentry/electron'; + +import { getAppEnvironment, getAppVersion, getSentryDsn } from './constants'; + +export const SENTRY_OPTIONS: Partial = { + dsn: getSentryDsn(), + environment: getAppEnvironment(), + release: getAppVersion(), +}; diff --git a/packages/insomnia/src/main.development.ts b/packages/insomnia/src/main.development.ts index 4ed4c5087c..95ad3a1a03 100644 --- a/packages/insomnia/src/main.development.ts +++ b/packages/insomnia/src/main.development.ts @@ -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(); diff --git a/packages/insomnia/src/main/error-handling.ts b/packages/insomnia/src/main/error-handling.ts deleted file mode 100644 index 36ce02396a..0000000000 --- a/packages/insomnia/src/main/error-handling.ts +++ /dev/null @@ -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); - }); -} diff --git a/packages/insomnia/src/main/sentry.ts b/packages/insomnia/src/main/sentry.ts new file mode 100644 index 0000000000..82107b5d75 --- /dev/null +++ b/packages/insomnia/src/main/sentry.ts @@ -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, + }); +} diff --git a/packages/insomnia/src/sync/vcs/vcs.ts b/packages/insomnia/src/sync/vcs/vcs.ts index 4cf7488cad..44dfaba3fa 100644 --- a/packages/insomnia/src/sync/vcs/vcs.ts +++ b/packages/insomnia/src/sync/vcs/vcs.ts @@ -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 || ''; } } diff --git a/packages/insomnia/src/ui/components/toast.tsx b/packages/insomnia/src/ui/components/toast.tsx index 79eaac754d..0fcd828161 100644 --- a/packages/insomnia/src/ui/components/toast.tsx +++ b/packages/insomnia/src/ui/components/toast.tsx @@ -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('/notification', data, session.getCurrentSessionId()); } catch (err) { console.warn('[toast] Failed to fetch user notifications', err); } diff --git a/packages/insomnia/src/ui/index.tsx b/packages/insomnia/src/ui/index.tsx index 13605f553e..1b42c946ec 100644 --- a/packages/insomnia/src/ui/index.tsx +++ b/packages/insomnia/src/ui/index.tsx @@ -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 diff --git a/packages/insomnia/src/ui/sentry.ts b/packages/insomnia/src/ui/sentry.ts new file mode 100644 index 0000000000..e13ca5bc1b --- /dev/null +++ b/packages/insomnia/src/ui/sentry.ts @@ -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(); +}