Skip to content

Commit 3eeef09

Browse files
AbhiPrasadmydea
andauthored
feat(core): Add lifecycle hooks (#7370)
Co-authored-by: Francesco Novy <[email protected]>
1 parent 2996c62 commit 3eeef09

File tree

3 files changed

+107
-1
lines changed

3 files changed

+107
-1
lines changed

packages/core/src/baseclient.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type {
1717
SessionAggregates,
1818
Severity,
1919
SeverityLevel,
20+
Transaction,
2021
TransactionEvent,
2122
Transport,
2223
} from '@sentry/types';
@@ -97,6 +98,9 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
9798
/** Holds flushable */
9899
private _outcomes: { [key: string]: number } = {};
99100

101+
// eslint-disable-next-line @typescript-eslint/ban-types
102+
private _hooks: Record<string, Function[]> = {};
103+
100104
/**
101105
* Initializes this client instance.
102106
*
@@ -351,6 +355,38 @@ export abstract class BaseClient<O extends ClientOptions> implements Client<O> {
351355
}
352356
}
353357

358+
// Keep on() & emit() signatures in sync with types' client.ts interface
359+
360+
/** @inheritdoc */
361+
public on(hook: 'startTransaction' | 'finishTransaction', callback: (transaction: Transaction) => void): void;
362+
363+
/** @inheritdoc */
364+
public on(hook: 'beforeEnvelope', callback: (envelope: Envelope) => void): void;
365+
366+
/** @inheritdoc */
367+
public on(hook: string, callback: unknown): void {
368+
if (!this._hooks[hook]) {
369+
this._hooks[hook] = [];
370+
}
371+
372+
// @ts-ignore We assue the types are correct
373+
this._hooks[hook].push(callback);
374+
}
375+
376+
/** @inheritdoc */
377+
public emit(hook: 'startTransaction' | 'finishTransaction', transaction: Transaction): void;
378+
379+
/** @inheritdoc */
380+
public emit(hook: 'beforeEnvelope', envelope: Envelope): void;
381+
382+
/** @inheritdoc */
383+
public emit(hook: string, ...rest: unknown[]): void {
384+
if (this._hooks[hook]) {
385+
// @ts-ignore we cannot enforce the callback to match the hook
386+
this._hooks[hook].forEach(callback => callback(...rest));
387+
}
388+
}
389+
354390
/** Updates existing session based on the provided event */
355391
protected _updateSessionFromEvent(session: Session, event: Event): void {
356392
let crashed = false;

packages/core/test/lib/base.test.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Event, Span } from '@sentry/types';
1+
import type { Client, Envelope, Event, Span, Transaction } from '@sentry/types';
22
import { dsnToString, logger, SentryError, SyncPromise } from '@sentry/utils';
33

44
import { Hub, makeSession, Scope } from '../../src';
@@ -1730,4 +1730,47 @@ describe('BaseClient', () => {
17301730
expect(clearedOutcomes4.length).toEqual(0);
17311731
});
17321732
});
1733+
1734+
describe('hooks', () => {
1735+
const options = getDefaultTestClientOptions({ dsn: PUBLIC_DSN });
1736+
1737+
// Make sure types work for both Client & BaseClient
1738+
const scenarios = [
1739+
['BaseClient', new TestClient(options)],
1740+
['Client', new TestClient(options) as Client],
1741+
] as const;
1742+
1743+
describe.each(scenarios)('with client %s', (_, client) => {
1744+
it('should call a startTransaction hook', () => {
1745+
expect.assertions(1);
1746+
1747+
const mockTransaction = {
1748+
traceId: '86f39e84263a4de99c326acab3bfe3bd',
1749+
} as Transaction;
1750+
1751+
client.on?.('startTransaction', transaction => {
1752+
expect(transaction).toEqual(mockTransaction);
1753+
});
1754+
1755+
client.emit?.('startTransaction', mockTransaction);
1756+
});
1757+
1758+
it('should call a beforeEnvelope hook', () => {
1759+
expect.assertions(1);
1760+
1761+
const mockEnvelope = [
1762+
{
1763+
event_id: '12345',
1764+
},
1765+
{},
1766+
] as Envelope;
1767+
1768+
client.on?.('beforeEnvelope', envelope => {
1769+
expect(envelope).toEqual(mockEnvelope);
1770+
});
1771+
1772+
client.emit?.('beforeEnvelope', mockEnvelope);
1773+
});
1774+
});
1775+
});
17331776
});

packages/types/src/client.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import type { EventDropReason } from './clientreport';
22
import type { DataCategory } from './datacategory';
33
import type { DsnComponents } from './dsn';
4+
import type { Envelope } from './envelope';
45
import type { Event, EventHint } from './event';
56
import type { Integration, IntegrationClass } from './integration';
67
import type { ClientOptions } from './options';
78
import type { Scope } from './scope';
89
import type { SdkMetadata } from './sdkmetadata';
910
import type { Session, SessionAggregates } from './session';
1011
import type { Severity, SeverityLevel } from './severity';
12+
import type { Transaction } from './transaction';
1113
import type { Transport } from './transport';
1214

1315
/**
@@ -147,4 +149,29 @@ export interface Client<O extends ClientOptions = ClientOptions> {
147149
* @param event The dropped event.
148150
*/
149151
recordDroppedEvent(reason: EventDropReason, dataCategory: DataCategory, event?: Event): void;
152+
153+
// HOOKS
154+
// TODO(v8): Make the hooks non-optional.
155+
156+
/**
157+
* Register a callback for transaction start and finish.
158+
*/
159+
on?(hook: 'startTransaction' | 'finishTransaction', callback: (transaction: Transaction) => void): void;
160+
161+
/**
162+
* Register a callback for transaction start and finish.
163+
*/
164+
on?(hook: 'beforeEnvelope', callback: (envelope: Envelope) => void): void;
165+
166+
/**
167+
* Fire a hook event for transaction start and finish. Expects to be given a transaction as the
168+
* second argument.
169+
*/
170+
emit?(hook: 'startTransaction' | 'finishTransaction', transaction: Transaction): void;
171+
172+
/*
173+
* Fire a hook event for envelope creation and sending. Expects to be given an envelope as the
174+
* second argument.
175+
*/
176+
emit?(hook: 'beforeEnvelope', envelope: Envelope): void;
150177
}

0 commit comments

Comments
 (0)