diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9266d7c0dc..a1ee521509 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -24,6 +24,8 @@ "@ngrx/router-store": "^17.2.0", "@ngrx/store": "^17.2.0", "@openmina/shared": "^0.102.0", + "@sentry/angular": "^8.35.0", + "@sentry/tracing": "^7.114.0", "base-x": "^5.0.0", "bs58check": "^4.0.0", "buffer": "^6.0.3", @@ -5012,6 +5014,184 @@ "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", "dev": true }, + "node_modules/@sentry-internal/browser-utils": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.35.0.tgz", + "integrity": "sha512-uj9nwERm7HIS13f/Q52hF/NUS5Al8Ma6jkgpfYGeppYvU0uSjPkwMogtqoJQNbOoZg973tV8qUScbcWY616wNA==", + "dependencies": { + "@sentry/core": "8.35.0", + "@sentry/types": "8.35.0", + "@sentry/utils": "8.35.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/feedback": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.35.0.tgz", + "integrity": "sha512-7bjSaUhL0bDArozre6EiIhhdWdT/1AWNWBC1Wc5w1IxEi5xF7nvF/FfvjQYrONQzZAI3HRxc45J2qhLUzHBmoQ==", + "dependencies": { + "@sentry/core": "8.35.0", + "@sentry/types": "8.35.0", + "@sentry/utils": "8.35.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/replay": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.35.0.tgz", + "integrity": "sha512-3wkW03vXYMyWtTLxl9yrtkV+qxbnKFgfASdoGWhXzfLjycgT6o4/04eb3Gn71q9aXqRwH17ISVQbVswnRqMcmA==", + "dependencies": { + "@sentry-internal/browser-utils": "8.35.0", + "@sentry/core": "8.35.0", + "@sentry/types": "8.35.0", + "@sentry/utils": "8.35.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.35.0.tgz", + "integrity": "sha512-TUrH6Piv19kvHIiRyIuapLdnuwxk/Un/l1WDCQfq7mK9p1Pac0FkQ7Uufjp6zY3lyhDDZQ8qvCS4ioCMibCwQg==", + "dependencies": { + "@sentry-internal/replay": "8.35.0", + "@sentry/core": "8.35.0", + "@sentry/types": "8.35.0", + "@sentry/utils": "8.35.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/tracing": { + "version": "7.114.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.114.0.tgz", + "integrity": "sha512-dOuvfJN7G+3YqLlUY4HIjyWHaRP8vbOgF+OsE5w2l7ZEn1rMAaUbPntAR8AF9GBA6j2zWNoSo8e7GjbJxVofSg==", + "dependencies": { + "@sentry/core": "7.114.0", + "@sentry/types": "7.114.0", + "@sentry/utils": "7.114.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry-internal/tracing/node_modules/@sentry/core": { + "version": "7.114.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.114.0.tgz", + "integrity": "sha512-YnanVlmulkjgZiVZ9BfY9k6I082n+C+LbZo52MTvx3FY6RE5iyiPMpaOh67oXEZRWcYQEGm+bKruRxLVP6RlbA==", + "dependencies": { + "@sentry/types": "7.114.0", + "@sentry/utils": "7.114.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry-internal/tracing/node_modules/@sentry/types": { + "version": "7.114.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.114.0.tgz", + "integrity": "sha512-tsqkkyL3eJtptmPtT0m9W/bPLkU7ILY7nvwpi1hahA5jrM7ppoU0IMaQWAgTD+U3rzFH40IdXNBFb8Gnqcva4w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry-internal/tracing/node_modules/@sentry/utils": { + "version": "7.114.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.114.0.tgz", + "integrity": "sha512-319N90McVpupQ6vws4+tfCy/03AdtsU0MurIE4+W5cubHME08HtiEWlfacvAxX+yuKFhvdsO4K4BB/dj54ideg==", + "dependencies": { + "@sentry/types": "7.114.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/angular": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@sentry/angular/-/angular-8.35.0.tgz", + "integrity": "sha512-mHbmvt8R79TvVSidshdKgKDE6GMii3rHjBBJmdwfBpRPmr28/XsrcheX6IOooePzyJucEcjkYCkrtHlHGs4kzg==", + "dependencies": { + "@sentry/browser": "8.35.0", + "@sentry/core": "8.35.0", + "@sentry/types": "8.35.0", + "@sentry/utils": "8.35.0", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=14.18" + }, + "peerDependencies": { + "@angular/common": ">= 14.x <= 18.x", + "@angular/core": ">= 14.x <= 18.x", + "@angular/router": ">= 14.x <= 18.x", + "rxjs": "^6.5.5 || ^7.x" + } + }, + "node_modules/@sentry/browser": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.35.0.tgz", + "integrity": "sha512-WHfI+NoZzpCsmIvtr6ChOe7yWPLQyMchPnVhY3Z4UeC70bkYNdKcoj/4XZbX3m0D8+71JAsm0mJ9s9OC3Ue6MQ==", + "dependencies": { + "@sentry-internal/browser-utils": "8.35.0", + "@sentry-internal/feedback": "8.35.0", + "@sentry-internal/replay": "8.35.0", + "@sentry-internal/replay-canvas": "8.35.0", + "@sentry/core": "8.35.0", + "@sentry/types": "8.35.0", + "@sentry/utils": "8.35.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry/core": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.35.0.tgz", + "integrity": "sha512-Ci0Nmtw5ETWLqQJGY4dyF+iWh7PWKy6k303fCEoEmqj2czDrKJCp7yHBNV0XYbo00prj2ZTbCr6I7albYiyONA==", + "dependencies": { + "@sentry/types": "8.35.0", + "@sentry/utils": "8.35.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry/tracing": { + "version": "7.114.0", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-7.114.0.tgz", + "integrity": "sha512-eldEYGADReZ4jWdN5u35yxLUSTOvjsiZAYd4KBEpf+Ii65n7g/kYOKAjNl7tHbrEG1EsMW4nDPWStUMk1w+tfg==", + "dependencies": { + "@sentry-internal/tracing": "7.114.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@sentry/types": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-8.35.0.tgz", + "integrity": "sha512-AVEZjb16MlYPifiDDvJ19dPQyDn0jlrtC1PHs6ZKO+Rzyz+2EX2BRdszvanqArldexPoU1p5Bn2w81XZNXThBA==", + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry/utils": { + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-8.35.0.tgz", + "integrity": "sha512-MdMb6+uXjqND7qIPWhulubpSeHzia6HtxeJa8jYI09OCvIcmNGPydv/Gx/LZBwosfMHrLdTWcFH7Y7aCxrq7cg==", + "dependencies": { + "@sentry/types": "8.35.0" + }, + "engines": { + "node": ">=14.18" + } + }, "node_modules/@sigstore/bundle": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-2.3.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index 0f3726504a..1d27b45e75 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -31,6 +31,8 @@ "@ngrx/router-store": "^17.2.0", "@ngrx/store": "^17.2.0", "@openmina/shared": "^0.102.0", + "@sentry/angular": "^8.35.0", + "@sentry/tracing": "^7.114.0", "base-x": "^5.0.0", "bs58check": "^4.0.0", "buffer": "^6.0.3", diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 0364dca7f6..f2dbdf9b12 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -1,4 +1,4 @@ -import { ErrorHandler, Injectable, LOCALE_ID, NgModule } from '@angular/core'; +import { APP_INITIALIZER, ErrorHandler, Injectable, LOCALE_ID, NgModule, Provider } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; @@ -33,6 +33,8 @@ import { HttpClientModule } from '@angular/common/http'; import { NewNodeComponent } from './layout/new-node/new-node.component'; import { ReactiveFormsModule } from '@angular/forms'; import { WebNodeLandingPageComponent } from '@app/layout/web-node-landing-page/web-node-landing-page.component'; +import * as Sentry from '@sentry/angular'; +import { Router } from '@angular/router'; registerLocaleData(localeFr, 'fr'); registerLocaleData(localeEn, 'en'); @@ -41,7 +43,8 @@ registerLocaleData(localeEn, 'en'); export class AppGlobalErrorhandler implements ErrorHandler { constructor(private errorHandlerService: GlobalErrorHandlerService) {} - handleError(error: any) { + handleError(error: any): void { + Sentry.captureException(error); this.errorHandlerService.handleError(error); console.error(error); } @@ -88,6 +91,14 @@ export class AppGlobalErrorhandler implements ErrorHandler { THEME_PROVIDER, { provide: ErrorHandler, useClass: AppGlobalErrorhandler, deps: [GlobalErrorHandlerService] }, { provide: LOCALE_ID, useValue: 'en' }, + { provide: ErrorHandler, useValue: Sentry.createErrorHandler() }, + { provide: Sentry.TraceService, deps: [Router] }, + { + provide: APP_INITIALIZER, + useFactory: () => () => {}, + deps: [Sentry.TraceService], + multi: true, + }, ], bootstrap: [AppComponent], }) diff --git a/frontend/src/app/shared/constants/store-functions.ts b/frontend/src/app/shared/constants/store-functions.ts index 213e62bb80..337dbe5597 100644 --- a/frontend/src/app/shared/constants/store-functions.ts +++ b/frontend/src/app/shared/constants/store-functions.ts @@ -7,6 +7,7 @@ import { Selector, Store } from '@ngrx/store'; import { MinaState } from '@app/app.setup'; import { concatLatestFrom } from '@ngrx/effects'; import { TypedAction } from '@ngrx/store/src/models'; +import * as Sentry from '@sentry/angular'; export const catchErrorAndRepeat = (errType: MinaErrorType, type: string, payload?: any): OperatorFunction> => { return (source: Observable) => @@ -21,6 +22,7 @@ export const catchErrorAndRepeat = (errType: MinaErrorType, type: string, pay export const addError = (error: HttpErrorResponse | Error, type: MinaErrorType): ErrorAdd => { console.error(error); + Sentry.captureException(error, { tags: { type } }); return { type: ADD_ERROR, payload: { diff --git a/frontend/src/app/shared/types/core/environment/mina-env.type.ts b/frontend/src/app/shared/types/core/environment/mina-env.type.ts index 739031331a..b20e57998d 100644 --- a/frontend/src/app/shared/types/core/environment/mina-env.type.ts +++ b/frontend/src/app/shared/types/core/environment/mina-env.type.ts @@ -6,6 +6,10 @@ export interface MinaEnv { hideNodeStats?: boolean; canAddNodes?: boolean; showWebNodeLandingPage?: boolean; + sentry?: { + dsn: string; + tracingOrigins: string[]; + }; globalConfig?: { features?: FeaturesConfig; graphQL?: string; diff --git a/frontend/src/app/shared/types/web-node-demo/web-node-demo-loading-step.type.ts b/frontend/src/app/shared/types/web-node-demo/web-node-demo-loading-step.type.ts deleted file mode 100644 index d35d52d373..0000000000 --- a/frontend/src/app/shared/types/web-node-demo/web-node-demo-loading-step.type.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface WebNodeDemoLoadingStep { - name: string; - loaded: boolean; - attempt?: number; - step: number; -} diff --git a/frontend/src/app/shared/types/web-node-demo/web-node-demo-transaction.type.ts b/frontend/src/app/shared/types/web-node-demo/web-node-demo-transaction.type.ts deleted file mode 100644 index 0e51f4e4b4..0000000000 --- a/frontend/src/app/shared/types/web-node-demo/web-node-demo-transaction.type.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface WebNodeDemoTransaction { - from: string; - to: string; - amount: string | number; - fee: string | number; - memo: string; - nonce: string; - status: string; - statusText: string; - priv_key: string; - hash?: string; - height?: number; - includedTime?: string; - time: number; -} diff --git a/frontend/src/app/shared/types/web-node-demo/web-node-demo-wallet.type.ts b/frontend/src/app/shared/types/web-node-demo/web-node-demo-wallet.type.ts deleted file mode 100644 index f98f8f36c1..0000000000 --- a/frontend/src/app/shared/types/web-node-demo/web-node-demo-wallet.type.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface WebNodeDemoWallet { - publicKey: string; - privateKey: string; - balance: string; - nonce: string; -} diff --git a/frontend/src/app/shared/types/web-node-demo/web-node-demo-web-node.type.ts b/frontend/src/app/shared/types/web-node-demo/web-node-demo-web-node.type.ts deleted file mode 100644 index 0a9edf8cf8..0000000000 --- a/frontend/src/app/shared/types/web-node-demo/web-node-demo-web-node.type.ts +++ /dev/null @@ -1,6 +0,0 @@ -export interface WebNodeDemoWebNode { - network: string; - height: number; - timestamp: number; - hash: string; -} diff --git a/frontend/src/assets/environments/webnode.js b/frontend/src/assets/environments/webnode.js index 5e6d86b273..1f66bf4d31 100644 --- a/frontend/src/assets/environments/webnode.js +++ b/frontend/src/assets/environments/webnode.js @@ -14,6 +14,10 @@ export default { 'benchmarks': ['wallets'], }, }, + sentry: { + dsn: 'https://69aba72a6290383494290cf285ab13b3@o4508216158584832.ingest.de.sentry.io/4508216160616528', + tracingOrigins: ['https://www.openmina.com'], + }, configs: [ { name: 'Web Node', diff --git a/frontend/src/environments/environment.prod.ts b/frontend/src/environments/environment.prod.ts index 850fe74607..b511552cee 100644 --- a/frontend/src/environments/environment.prod.ts +++ b/frontend/src/environments/environment.prod.ts @@ -10,4 +10,5 @@ export const environment: Readonly = { hideToolbar: env.hideToolbar, canAddNodes: env.canAddNodes, showWebNodeLandingPage: env.showWebNodeLandingPage, + sentry: env.sentry, }; diff --git a/frontend/src/main.ts b/frontend/src/main.ts index fba7e4f90f..98fc5f9f26 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -1,6 +1,33 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from '@app/app.module'; +import { CONFIG } from '@shared/constants/config'; +import * as Sentry from '@sentry/angular'; +import type { ErrorEvent } from '@sentry/types/build/types/event'; + +if (CONFIG.production) { + initSentry(); +} platformBrowserDynamic().bootstrapModule(AppModule) .catch(err => console.error(err)); + +function initSentry(): void { + if (CONFIG.sentry) { + Sentry.init({ + dsn: CONFIG.sentry.dsn, + integrations: [ + Sentry.browserTracingIntegration(), + Sentry.replayIntegration(), + ], + tracesSampleRate: 1.0, + tracePropagationTargets: [...CONFIG.sentry?.tracingOrigins, ...CONFIG.configs.map((config) => config.url).filter(Boolean)], + replaysSessionSampleRate: 1.0, + replaysOnErrorSampleRate: 0.1, + beforeSend: (event: ErrorEvent) => { + event.fingerprint = [(Math.random() * 10000000).toString()]; + return event; + }, + }); + } +}