Skip to content

Commit

Permalink
fix(angular): remove afterSendEvent listener once root injector is …
Browse files Browse the repository at this point in the history
…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.
  • Loading branch information
arturovt committed Jul 5, 2024
1 parent a2dcb28 commit 3bb8cc8
Showing 1 changed file with 15 additions and 11 deletions.
26 changes: 15 additions & 11 deletions packages/angular/src/errorhandler.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -80,21 +80,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
*/
Expand All @@ -118,17 +125,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 });
Expand Down

0 comments on commit 3bb8cc8

Please sign in to comment.