diff --git a/packages/sdk-core/src/BacktraceCoreClient.ts b/packages/sdk-core/src/BacktraceCoreClient.ts index af52966f..e9ff9c14 100644 --- a/packages/sdk-core/src/BacktraceCoreClient.ts +++ b/packages/sdk-core/src/BacktraceCoreClient.ts @@ -1,6 +1,6 @@ import { CoreClientSetup } from './builder/CoreClientSetup.js'; import { Events } from './common/Events.js'; -import { ReportEvents } from './events/ReportEvents.js'; +import { ClientEvents } from './events/ClientEvents.js'; import { BacktraceAttachment, BacktraceAttributeProvider, @@ -34,7 +34,9 @@ import { MetricsBuilder } from './modules/metrics/MetricsBuilder.js'; import { SingleSessionProvider } from './modules/metrics/SingleSessionProvider.js'; import { RateLimitWatcher } from './modules/rateLimiter/RateLimitWatcher.js'; -export abstract class BacktraceCoreClient { +export abstract class BacktraceCoreClient< + O extends BacktraceConfiguration = BacktraceConfiguration, +> extends Events { /** * Backtrace client instance */ @@ -112,7 +114,7 @@ export abstract class BacktraceCoreClient; + protected readonly attributeManager: AttributeManager; protected readonly attachmentManager: AttachmentManager; protected readonly fileSystem?: FileSystem; @@ -128,7 +130,7 @@ export abstract class BacktraceCoreClient) { - this.reportEvents = new Events(); + super(); this.options = setup.options; this.fileSystem = setup.fileSystem; @@ -316,7 +318,7 @@ export abstract class BacktraceCoreClient { - this.reportEvents.emit('after-send', report, backtraceData, submissionAttachments, submissionResult); + this.emit('after-send', report, backtraceData, submissionAttachments, submissionResult); return submissionResult; }); } @@ -403,7 +405,6 @@ export abstract class BacktraceCoreClient unknown; +interface EventCallback { + callback: (...args: A) => unknown; once?: boolean; } -export class Events< - E extends Record unknown> = Record< - string | number | symbol, - (...args: any[]) => unknown - >, -> { +/* eslint-disable @typescript-eslint/no-explicit-any */ +export type EventMap = Record; + +export class Events { private readonly _callbacks: Partial> = {}; - public on(event: N, callback: E[N]): this { + public on(event: N, callback: (...args: E[N]) => unknown): this { this.addCallback(event, { callback }); return this; } - public once(event: N, callback: E[N]): this { + public once(event: N, callback: (...args: E[N]) => unknown): this { this.addCallback(event, { callback, once: true }); return this; } - public off(event: N, callback: E[N]): this { + public off(event: N, callback: (...args: E[N]) => unknown): this { this.removeCallback(event, callback); return this; } - public emit(event: N, ...args: Parameters): boolean { + public emit(event: N, ...args: E[N]): boolean { const callbacks = this._callbacks[event]; if (!callbacks || !callbacks.length) { return false; @@ -48,7 +46,7 @@ export class Events< return true; } - private addCallback(event: keyof E, callback: EventCallback) { + private addCallback(event: keyof E, callback: EventCallback) { const list = this._callbacks[event]; if (list) { list.push(callback); @@ -57,7 +55,7 @@ export class Events< } } - private removeCallback(event: keyof E, callback: EventCallback['callback']) { + private removeCallback(event: keyof E, callback: EventCallback['callback']) { const list = this._callbacks[event]; if (!list) { return; diff --git a/packages/sdk-core/src/events/AttachmentEvents.ts b/packages/sdk-core/src/events/AttachmentEvents.ts index ff36c5d7..ed661321 100644 --- a/packages/sdk-core/src/events/AttachmentEvents.ts +++ b/packages/sdk-core/src/events/AttachmentEvents.ts @@ -1,5 +1,5 @@ import { BacktraceAttachment } from '../model/attachment/index.js'; export type AttachmentEvents = { - 'scoped-attachments-updated'(attachments: BacktraceAttachment[]): void; + 'scoped-attachments-updated': [attachments: BacktraceAttachment[]]; }; diff --git a/packages/sdk-core/src/events/AttributeEvents.ts b/packages/sdk-core/src/events/AttributeEvents.ts index c08974b0..94dc76e3 100644 --- a/packages/sdk-core/src/events/AttributeEvents.ts +++ b/packages/sdk-core/src/events/AttributeEvents.ts @@ -1,5 +1,5 @@ import { ReportData } from '../model/report/ReportData.js'; export type AttributeEvents = { - 'scoped-attributes-updated'(attributes: ReportData): void; + 'scoped-attributes-updated': [attributes: ReportData]; }; diff --git a/packages/sdk-core/src/events/ReportEvents.ts b/packages/sdk-core/src/events/ClientEvents.ts similarity index 69% rename from packages/sdk-core/src/events/ReportEvents.ts rename to packages/sdk-core/src/events/ClientEvents.ts index 362e6c4b..a7668921 100644 --- a/packages/sdk-core/src/events/ReportEvents.ts +++ b/packages/sdk-core/src/events/ClientEvents.ts @@ -3,13 +3,13 @@ import { BacktraceData } from '../model/data/index.js'; import { BacktraceReportSubmissionResult, BacktraceSubmissionResponse } from '../model/http/index.js'; import { BacktraceReport } from '../model/report/BacktraceReport.js'; -export type ReportEvents = { - 'before-skip'(report: BacktraceReport): void; - 'before-send'(report: BacktraceReport, data: BacktraceData, attachments: BacktraceAttachment[]): void; - 'after-send'( +export type ClientEvents = { + 'before-skip': [report: BacktraceReport]; + 'before-send': [report: BacktraceReport, data: BacktraceData, attachments: BacktraceAttachment[]]; + 'after-send': [ report: BacktraceReport, data: BacktraceData, attachments: BacktraceAttachment[], result: BacktraceReportSubmissionResult, - ): void; + ]; }; diff --git a/packages/sdk-core/src/modules/BacktraceModule.ts b/packages/sdk-core/src/modules/BacktraceModule.ts index 26e0061c..9b16144a 100644 --- a/packages/sdk-core/src/modules/BacktraceModule.ts +++ b/packages/sdk-core/src/modules/BacktraceModule.ts @@ -1,5 +1,3 @@ -import { Events } from '../common/Events.js'; -import { ReportEvents } from '../events/ReportEvents.js'; import { BacktraceConfiguration, BacktraceCoreClient, @@ -17,7 +15,6 @@ export interface BacktraceModuleBindData { readonly options: BacktraceConfiguration; readonly attributeManager: AttributeManager; readonly attachmentManager: AttachmentManager; - readonly reportEvents: Events; readonly reportSubmission: BacktraceReportSubmission; readonly requestHandler: BacktraceRequestHandler; readonly database?: BacktraceDatabase; diff --git a/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbsManager.ts b/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbsManager.ts index f0a01c10..e998b640 100644 --- a/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbsManager.ts +++ b/packages/sdk-core/src/modules/breadcrumbs/BreadcrumbsManager.ts @@ -66,7 +66,7 @@ export class BreadcrumbsManager implements BacktraceBreadcrumbs, BacktraceModule } } - public bind({ client, reportEvents, attachmentManager }: BacktraceModuleBindData): void { + public bind({ client, attachmentManager }: BacktraceModuleBindData): void { if (this._storage.getAttachmentProviders) { attachmentManager.addProviders(...this._storage.getAttachmentProviders()); } else { @@ -77,7 +77,7 @@ export class BreadcrumbsManager implements BacktraceBreadcrumbs, BacktraceModule [BREADCRUMB_ATTRIBUTE_NAME]: this._storage.lastBreadcrumbId, })); - reportEvents.on('before-skip', (report) => this.logReport(report)); + client.on('before-skip', (report) => this.logReport(report)); } public initialize() { diff --git a/packages/sdk-core/src/modules/database/BacktraceDatabase.ts b/packages/sdk-core/src/modules/database/BacktraceDatabase.ts index 054d35bf..38709be1 100644 --- a/packages/sdk-core/src/modules/database/BacktraceDatabase.ts +++ b/packages/sdk-core/src/modules/database/BacktraceDatabase.ts @@ -1,4 +1,5 @@ import { anySignal, createAbortController } from '../../common/AbortController.js'; +import { Events } from '../../common/Events.js'; import { IdGenerator } from '../../common/IdGenerator.js'; import { TimeHelper } from '../../common/TimeHelper.js'; import { BacktraceAttachment } from '../../model/attachment/index.js'; @@ -8,6 +9,7 @@ import { BacktraceReportSubmission } from '../../model/http/BacktraceReportSubmi import { BacktraceModule, BacktraceModuleBindData } from '../BacktraceModule.js'; import { SessionFiles } from '../storage/index.js'; import { BacktraceDatabaseContext } from './BacktraceDatabaseContext.js'; +import { BacktraceDatabaseEvents } from './BacktraceDatabaseEvents.js'; import { BacktraceDatabaseStorageProvider } from './BacktraceDatabaseStorageProvider.js'; import { AttachmentBacktraceDatabaseRecord, @@ -16,7 +18,7 @@ import { ReportBacktraceDatabaseRecord, } from './model/BacktraceDatabaseRecord.js'; -export class BacktraceDatabase implements BacktraceModule { +export class BacktraceDatabase extends Events implements BacktraceModule { /** * Determines if the database is enabled. */ @@ -45,6 +47,8 @@ export class BacktraceDatabase implements BacktraceModule { private readonly _requestHandler: BacktraceReportSubmission, private readonly _sessionFiles?: SessionFiles, ) { + super(); + this._databaseRecordContext = new BacktraceDatabaseContext(this._options?.maximumRetries); this._recordLimits = { report: this._options?.maximumNumberOfRecords ?? 8, @@ -82,7 +86,7 @@ export class BacktraceDatabase implements BacktraceModule { return true; } - public bind({ reportEvents }: BacktraceModuleBindData): void { + public bind({ client }: BacktraceModuleBindData): void { if (this._enabled) { return; } @@ -91,7 +95,7 @@ export class BacktraceDatabase implements BacktraceModule { return; } - reportEvents.on('before-send', (_, data, attachments) => { + client.on('before-send', (_, data, attachments) => { const record = this.add(data, attachments); if (!record || record.locked) { @@ -101,7 +105,7 @@ export class BacktraceDatabase implements BacktraceModule { record.locked = true; }); - reportEvents.on('after-send', (_, data, __, submissionResult) => { + client.on('after-send', (_, data, __, submissionResult) => { const record = this._databaseRecordContext.find( (record) => record.type === 'report' && record.data.uuid === data.uuid, ); @@ -151,6 +155,8 @@ export class BacktraceDatabase implements BacktraceModule { this._databaseRecordContext.add(record); this.lockSessionWithRecord(record); + this.emit('added', record); + return record; } @@ -189,6 +195,8 @@ export class BacktraceDatabase implements BacktraceModule { this._databaseRecordContext.add(record); this.lockSessionWithRecord(record); + this.emit('added', record); + return record; } @@ -238,6 +246,8 @@ export class BacktraceDatabase implements BacktraceModule { this._databaseRecordContext.remove(record); this._storageProvider.delete(record); this._sessionFiles?.unlockPreviousSessions(record.id); + + this.emit('removed', record); } } @@ -287,11 +297,15 @@ export class BacktraceDatabase implements BacktraceModule { try { record.locked = true; + this.emit('before-send', record); + const result = record.type === 'report' ? await this._requestHandler.send(record.data, record.attachments, signal) : await this._requestHandler.sendAttachment(record.rxid, record.attachment, signal); + this.emit('after-send', record, result); + if ( result.status === 'Ok' || result.status === 'Unsupported' || @@ -356,10 +370,15 @@ export class BacktraceDatabase implements BacktraceModule { for (const record of recordsToAdd) { this.lockSessionWithRecord(record); + this.emit('added', record); } } private async setupDatabaseAutoSend() { + if (!this._enabled) { + return; + } + if (this._options?.autoSend === false) { return; } diff --git a/packages/sdk-core/src/modules/database/BacktraceDatabaseEvents.ts b/packages/sdk-core/src/modules/database/BacktraceDatabaseEvents.ts new file mode 100644 index 00000000..7b40dc54 --- /dev/null +++ b/packages/sdk-core/src/modules/database/BacktraceDatabaseEvents.ts @@ -0,0 +1,13 @@ +import { BacktraceReportSubmissionResult } from '../../model/data/BacktraceSubmissionResult.js'; +import { BacktraceSubmissionResponse } from '../../model/http/index.js'; +import { BacktraceDatabaseRecord } from './model/BacktraceDatabaseRecord.js'; + +export type BacktraceDatabaseEvents = { + added: [record: BacktraceDatabaseRecord]; + removed: [record: BacktraceDatabaseRecord]; + 'before-send': [record: BacktraceDatabaseRecord]; + 'after-send': [ + record: BacktraceDatabaseRecord, + result: BacktraceReportSubmissionResult, + ]; +}; diff --git a/packages/sdk-core/src/modules/storage/SessionFiles.ts b/packages/sdk-core/src/modules/storage/SessionFiles.ts index 159a74a3..279614b6 100644 --- a/packages/sdk-core/src/modules/storage/SessionFiles.ts +++ b/packages/sdk-core/src/modules/storage/SessionFiles.ts @@ -11,7 +11,7 @@ interface FileSession { } type SessionEvents = { - unlocked(): void; + unlocked: []; }; const SESSION_MARKER_PREFIX = 'bt-session'; diff --git a/packages/sdk-core/tests/mocks/testHttpClient.ts b/packages/sdk-core/tests/mocks/testHttpClient.ts index 79aa8698..193c6b34 100644 --- a/packages/sdk-core/tests/mocks/testHttpClient.ts +++ b/packages/sdk-core/tests/mocks/testHttpClient.ts @@ -2,5 +2,5 @@ import { BacktraceReportSubmissionResult, BacktraceRequestHandler } from '../../ export const testHttpClient: BacktraceRequestHandler = { post: jest.fn().mockResolvedValue(Promise.resolve(BacktraceReportSubmissionResult.Ok('Ok'))), - postError: jest.fn().mockResolvedValue(Promise.resolve()), + postError: jest.fn().mockResolvedValue(Promise.resolve(BacktraceReportSubmissionResult.Ok('Ok'))), };