diff --git a/package.json b/package.json index f3cb140c..ea7400f1 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "@angular/platform-browser": "^8.2.11", "@angular/platform-browser-dynamic": "^8.2.11", "@angular/router": "^8.2.11", + "@sentry/browser": "^5.18.0", "angular-draggable-droppable": "^4.3.8", "angular-persistence": "^1.0.1", "angular-resizable-element": "^3.2.6", @@ -71,7 +72,6 @@ "ngx-electron": "^2.1.1", "node-fetch": "^2.6.0", "notosans-fontface": "^1.1.0", - "raven-js": "^3.27.2", "rxjs": "^6.5.3", "rxjs-compat": "^6.5.3", "save-html-as-image": "^1.2.0", diff --git a/scripts/build.py b/scripts/build.py index 4c46e250..d92591bc 100644 --- a/scripts/build.py +++ b/scripts/build.py @@ -277,13 +277,11 @@ def build_command(arguments): ] excludes = [ - "raven.deprecation", # reported problem in raven package (6.4.0) "distutils", # issue on macOS "tkinter", # issue on Windows ] packages = [ - "raven", "psutil", "asyncio", "packaging", # needed for linux diff --git a/src/app/app.module.ts b/src/app/app.module.ts index c99e98e2..3256ca89 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -1,4 +1,3 @@ -import * as Raven from 'raven-js'; import { BrowserModule, Title } from '@angular/platform-browser'; import { NgModule, ErrorHandler } from '@angular/core'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -64,7 +63,6 @@ import { ProgressService } from './common/progress/progress.service'; import { version } from './version'; import { ToasterErrorHandler } from './common/error-handlers/toaster-error-handler'; import { environment } from '../environments/environment'; -import { RavenState } from './common/error-handlers/raven-state-communicator'; import { ServerDiscoveryComponent } from './components/servers/server-discovery/server-discovery.component'; import { ServerDatabase } from './services/server.database'; import { CreateSnapshotDialogComponent } from './components/snapshots/create-snapshot-dialog/create-snapshot-dialog.component'; @@ -281,15 +279,6 @@ import { ChangeHostnameActionComponent } from './components/project-map/context- import { ChangeHostnameDialogComponent } from './components/project-map/change-hostname-dialog/change-hostname-dialog.component'; import { ApplianceInfoDialogComponent } from './components/project-map/new-template-dialog/appliance-info-dialog/appliance-info-dialog.component'; -if (environment.production) { - Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', { - shouldSendCallback: () => { - return RavenState.shouldSend; - }, - release: version - }).install(); -} - @NgModule({ declarations: [ AppComponent, diff --git a/src/app/common/error-handlers/raven-error-handler.spec.ts b/src/app/common/error-handlers/raven-error-handler.spec.ts deleted file mode 100644 index a751793a..00000000 --- a/src/app/common/error-handlers/raven-error-handler.spec.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { TestBed } from '@angular/core/testing'; -import { PersistenceService } from 'angular-persistence'; - -import { SettingsService } from '../../services/settings.service'; -import { RavenErrorHandler } from './raven-error-handler'; -import { environment } from '../../../environments/environment'; - -describe('RavenErrorHandler', () => { - let handler: RavenErrorHandler; - let settingsService: SettingsService; - const inProductionOriginal = environment.production; - - beforeEach(() => { - TestBed.configureTestingModule({ - providers: [SettingsService, PersistenceService, RavenErrorHandler] - }); - - settingsService = TestBed.get(SettingsService); - handler = TestBed.get(RavenErrorHandler); - }); - - afterEach(() => { - environment.production = inProductionOriginal; - }); - - it('should create', () => { - expect(handler).toBeTruthy(); - }); - - it('should handle error', () => { - settingsService.set('crash_reports', true); - environment.production = true; - expect(handler.shouldSend()).toBeTruthy(); - }); - - it('should not handle when crash reports are disabled', () => { - settingsService.set('crash_reports', false); - expect(handler.shouldSend()).toBeFalsy(); - }); -}); diff --git a/src/app/common/error-handlers/raven-error-handler.ts b/src/app/common/error-handlers/raven-error-handler.ts deleted file mode 100644 index ccb5885a..00000000 --- a/src/app/common/error-handlers/raven-error-handler.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { ErrorHandler, Inject, Injector } from '@angular/core'; - -import { SettingsService } from '../../services/settings.service'; -import { environment } from '../../../environments/environment'; -import { RavenState } from './raven-state-communicator'; - -export class RavenErrorHandler implements ErrorHandler { - constructor(@Inject(Injector) protected injector: Injector) {} - - handleError(err: any): void { - RavenState.shouldSend = this.shouldSend(); - - console.error(err.originalError || err); - } - - shouldSend() { - const settingsService: SettingsService = this.injector.get(SettingsService); - return environment.production && settingsService.get('crash_reports'); - } -} diff --git a/src/app/common/error-handlers/raven-state-communicator.ts b/src/app/common/error-handlers/raven-state-communicator.ts deleted file mode 100644 index 700f810b..00000000 --- a/src/app/common/error-handlers/raven-state-communicator.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class RavenStateCommunicator { - public shouldSend = true; -} - -export var RavenState = new RavenStateCommunicator(); diff --git a/src/app/common/error-handlers/sentry-error-handler.ts b/src/app/common/error-handlers/sentry-error-handler.ts new file mode 100644 index 00000000..48acaa66 --- /dev/null +++ b/src/app/common/error-handlers/sentry-error-handler.ts @@ -0,0 +1,71 @@ +import { BrowserModule } from "@angular/platform-browser"; +import { NgModule, ErrorHandler, Injectable, Injector, Inject } from "@angular/core"; +import { HttpErrorResponse } from "@angular/common/http"; + +import { environment } from "../../../environments/environment"; +import { AppComponent } from "../../app.component"; + +import * as Sentry from "@sentry/browser"; +import { BrowserOptions, init } from '@sentry/browser'; + +const config = { + dsn: "https://b2b1cfd9b043491eb6b566fd8acee358@o19455.ingest.sentry.io/842726" +}; +init(config as BrowserOptions); + +@Injectable() +export class SentryErrorHandler implements ErrorHandler { + constructor(@Inject(Injector) protected injector: Injector) {} + + extractError(error) { + // Try to unwrap zone.js error. + // https://github.com/angular/angular/blob/master/packages/core/src/util/errors.ts + if (error && error.ngOriginalError) { + error = error.ngOriginalError; + } + + // We can handle messages and Error objects directly. + if (typeof error === "string" || error instanceof Error) { + return error; + } + + // If it's http module error, extract as much information from it as we can. + if (error instanceof HttpErrorResponse) { + // The `error` property of http exception can be either an `Error` object, which we can use directly... + if (error.error instanceof Error) { + return error.error; + } + + // ... or an`ErrorEvent`, which can provide us with the message but no stack... + if (error.error instanceof ErrorEvent) { + return error.error.message; + } + + // ...or the request body itself, which we can use as a message instead. + if (typeof error.error === "string") { + return `Server returned code ${error.status} with body "${error.error}"`; + } + + // If we don't have any detailed information, fallback to the request message itself. + return error.message; + } + + // Skip if there's no error, and let user decide what to do with it. + return null; + } + + handleError(error) { + let extractedError = this.extractError(error) || "Handled unknown error"; + + // Capture handled exception and send it to Sentry. + const eventId = Sentry.captureException(extractedError); + + // When in development mode, log the error to console for immediate feedback. + if (!environment.production) { + console.error(extractedError); + } + + // Optionally show user dialog to provide details on what happened. + Sentry.showReportDialog({ eventId }); + } +} diff --git a/src/app/common/error-handlers/toaster-error-handler.spec.ts b/src/app/common/error-handlers/toaster-error-handler.spec.ts index a2b3d675..9876b5d2 100644 --- a/src/app/common/error-handlers/toaster-error-handler.spec.ts +++ b/src/app/common/error-handlers/toaster-error-handler.spec.ts @@ -2,10 +2,10 @@ import { TestBed } from '@angular/core/testing'; import { ToasterService } from '../../services/toaster.service'; import { MockedToasterService } from '../../services/toaster.service.spec'; import { ToasterErrorHandler } from './toaster-error-handler'; -import { RavenErrorHandler } from './raven-error-handler'; import { SettingsService } from '../../services/settings.service'; import { MockedSettingsService } from '../../services/settings.service.spec'; import { Injector } from '@angular/core'; +import { SentryErrorHandler } from './sentry-error-handler'; class MockedToasterErrorHandler extends ToasterErrorHandler { handleError(err: any): void { @@ -23,7 +23,7 @@ describe('ToasterErrorHandler', () => { providers: [ { provide: ToasterService, useClass: MockedToasterService }, { provide: SettingsService, useClass: MockedSettingsService }, - RavenErrorHandler, + SentryErrorHandler, ToasterErrorHandler ] }); diff --git a/src/app/common/error-handlers/toaster-error-handler.ts b/src/app/common/error-handlers/toaster-error-handler.ts index 555cb22c..85026cfb 100644 --- a/src/app/common/error-handlers/toaster-error-handler.ts +++ b/src/app/common/error-handlers/toaster-error-handler.ts @@ -1,7 +1,7 @@ -import { RavenErrorHandler } from './raven-error-handler'; import { ToasterService } from '../../services/toaster.service'; +import { SentryErrorHandler } from './sentry-error-handler'; -export class ToasterErrorHandler extends RavenErrorHandler { +export class ToasterErrorHandler extends SentryErrorHandler { handleError(err: any): void { super.handleError(err); if (!err) return; diff --git a/yarn.lock b/yarn.lock index 43a6d7c1..53def6b9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7823,11 +7823,6 @@ range-parser@^1.2.0, range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raven-js@^3.27.2: - version "3.27.2" - resolved "https://registry.npmjs.org/raven-js/-/raven-js-3.27.2.tgz#6c33df952026cd73820aa999122b7b7737a66775" - integrity sha512-mFWQcXnhRFEQe5HeFroPaEghlnqy7F5E2J3Fsab189ondqUzcjwSVi7el7F36cr6PvQYXoZ1P2F5CSF2/azeMQ== - raw-body@2.4.0: version "2.4.0" resolved "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332"