From c44ac4be1296c1d22a8969e9c89b7dc4c92bd1a7 Mon Sep 17 00:00:00 2001 From: arturovt Date: Fri, 5 Jul 2024 17:19:22 +0300 Subject: [PATCH] fix(angular): remove `afterSendEvent` listener once root injector is destroyed In this commit, we added cleanup logic to handle the removal of `afterSendEvent`, which is set up within the Angular error handler. This fixes memory leaks that occur when the event is still being handled after the root view is removed. --- packages/angular/src/errorhandler.ts | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/packages/angular/src/errorhandler.ts b/packages/angular/src/errorhandler.ts index 28c06e1e6bfd..b23b352db170 100644 --- a/packages/angular/src/errorhandler.ts +++ b/packages/angular/src/errorhandler.ts @@ -1,5 +1,5 @@ import { HttpErrorResponse } from '@angular/common/http'; -import type { ErrorHandler as AngularErrorHandler } from '@angular/core'; +import type { ErrorHandler as AngularErrorHandler, OnDestroy } from '@angular/core'; import { Inject, Injectable } from '@angular/core'; import * as Sentry from '@sentry/browser'; import type { ReportDialogOptions } from '@sentry/browser'; @@ -81,21 +81,28 @@ function isErrorOrErrorLikeObject(value: unknown): value is Error { * Implementation of Angular's ErrorHandler provider that can be used as a drop-in replacement for the stock one. */ @Injectable({ providedIn: 'root' }) -class SentryErrorHandler implements AngularErrorHandler { +class SentryErrorHandler implements AngularErrorHandler, OnDestroy { protected readonly _options: ErrorHandlerOptions; - /* indicates if we already registered our the afterSendEvent handler */ - private _registeredAfterSendEventHandler; + /** The cleanup function is executed when the injector is destroyed. */ + private _removeAfterSendEventListener?: VoidFunction; public constructor(@Inject('errorHandlerOptions') options?: ErrorHandlerOptions) { - this._registeredAfterSendEventHandler = false; - this._options = { logErrors: true, ...options, }; } + /** + * Method executed when the injector is destroyed. + */ + public ngOnDestroy(): void { + if (this._removeAfterSendEventListener) { + this._removeAfterSendEventListener(); + } + } + /** * Method called for every value captured through the ErrorHandler */ @@ -119,17 +126,14 @@ class SentryErrorHandler implements AngularErrorHandler { if (this._options.showDialog) { const client = Sentry.getClient(); - if (client && !this._registeredAfterSendEventHandler) { - client.on('afterSendEvent', (event: Event) => { + if (client && !this._removeAfterSendEventListener) { + this._removeAfterSendEventListener = client.on('afterSendEvent', (event: Event) => { if (!event.type && event.event_id) { runOutsideAngular(() => { Sentry.showReportDialog({ ...this._options.dialogOptions, eventId: event.event_id! }); }); } }); - - // We only want to register this hook once in the lifetime of the error handler - this._registeredAfterSendEventHandler = true; } else if (!client) { runOutsideAngular(() => { Sentry.showReportDialog({ ...this._options.dialogOptions, eventId });