diff --git a/src/client.ts b/src/client.ts index 6b39b71b..96f58b3b 100644 --- a/src/client.ts +++ b/src/client.ts @@ -1,6 +1,8 @@ import { eventFromException, eventFromMessage } from '@sentry/browser'; import { BaseClient } from '@sentry/core'; import type { + ClientReportEnvelope, + ClientReportItem, Envelope, Event, EventHint, @@ -10,12 +12,14 @@ import type { Thread, UserFeedback, } from '@sentry/types'; -import { logger, SentryError } from '@sentry/utils'; +import { dateTimestampInSeconds, logger, SentryError } from '@sentry/utils'; import type { CapacitorClientOptions } from './options'; import { mergeOutcomes } from './utils/outcome'; import { NATIVE } from './wrapper'; +const EnvelopeItemsIndex = 1; + /** * The Sentry Capacitor SDK Client. * @@ -108,8 +112,7 @@ export class CapacitorClient extends BaseClient { this._outcomesBuffer = mergeOutcomes(this._outcomesBuffer, outcomes); if (this._options.sendClientReports) { - // TODO: Implement Cleint Report. - // this._attachClientReportTo(this._outcomesBuffer, envelope as ClientReportEnvelope); + this._attachClientReportTo(this._outcomesBuffer, envelope as ClientReportEnvelope); } let shouldClearOutcomesBuffer = true; @@ -163,6 +166,20 @@ export class CapacitorClient extends BaseClient { console.log('Sentry Warning, could not connect to Sentry native SDK.\nIf you do not want to use the native component please pass `enableNative: false` in the options.\nVisit: https://docs.sentry.io/platforms/javascript/guides/capacitor/configuration/options/#hybrid-sdk-options for more details.'); } } - -// TODO: implement Attaches clients report. + /** + * Attaches a client report from outcomes to the envelope. + */ + private _attachClientReportTo(outcomes: Outcome[], envelope: ClientReportEnvelope): void { + if (outcomes.length > 0) { + const clientReportItem: ClientReportItem = [ + { type: 'client_report' }, + { + timestamp: dateTimestampInSeconds(), + discarded_events: outcomes, + }, + ]; + + envelope[EnvelopeItemsIndex].push(clientReportItem); + } + } } diff --git a/test/client.test.ts b/test/client.test.ts index cf3af434..682c083c 100644 --- a/test/client.test.ts +++ b/test/client.test.ts @@ -1,9 +1,16 @@ -import type { Envelope, Transport } from '@sentry/types'; +import type { Envelope, Outcome, Transport } from '@sentry/types'; import { CapacitorClient } from '../src/client'; import type { CapacitorClientOptions } from '../src/options'; import { NativeTransport } from '../src/transports/native'; import { NATIVE } from '../src/wrapper'; +import { + envelopeItemHeader, + envelopeItemPayload, + envelopeItems, + firstArg, + getSyncPromiseRejectOnFirstCall, +} from './testutils'; interface MockedCapacitor { Platform: { @@ -68,6 +75,8 @@ jest.mock('../src/plugin', () => { }; }); +import { rejectedSyncPromise,SentryError } from '@sentry/utils'; + import * as Plugin from '../src/plugin'; @@ -294,35 +303,35 @@ describe('Tests CapacitorClient', () => { }); }); */ - /* TODO: Fix SDKInfo - describe('event data enhancement', () => { - test('event contains sdk default information', async () => { - const mockedSend = jest.fn, [Envelope]>().mockResolvedValue(undefined); - const mockedTransport = (): Transport => ({ - send: mockedSend, - flush: jest.fn().mockResolvedValue(true), - }); - const client = new CapacitorClient({ - ...DEFAULT_OPTIONS, - dsn: EXAMPLE_DSN, - transport: mockedTransport, - }); - - client.captureEvent({ message: 'test event' }); - - expect(mockedSend).toBeCalled(); - const actualEvent: Event | undefined = ( - mockedSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload] - ); - expect(actualEvent?.sdk?.packages).toEqual([ - { - name: SDK_PACKAGE_NAME, - version: SDK_VERSION, - }, - ]); + /* TODO: Fix SDKInfo +describe('event data enhancement', () => { + test('event contains sdk default information', async () => { + const mockedSend = jest.fn, [Envelope]>().mockResolvedValue(undefined); + const mockedTransport = (): Transport => ({ + send: mockedSend, + flush: jest.fn().mockResolvedValue(true), + }); + const client = new CapacitorClient({ + ...DEFAULT_OPTIONS, + dsn: EXAMPLE_DSN, + transport: mockedTransport, }); + client.captureEvent({ message: 'test event' }); + + expect(mockedSend).toBeCalled(); + const actualEvent: Event | undefined = ( + mockedSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemPayload] + ); + expect(actualEvent?.sdk?.packages).toEqual([ + { + name: SDK_PACKAGE_NAME, + version: SDK_VERSION, + }, + ]); }); + +}); */ describe('normalizes events', () => { /* TODO: Fix later @@ -359,7 +368,6 @@ describe('Tests CapacitorClient', () => { */ }); - /* TODO: To be fixed on Client Report implementation. describe('clientReports', () => { test('does not send client reports if disabled', () => { const mockTransportSend = jest.fn((_envelope: Envelope) => Promise.resolve()); @@ -380,7 +388,6 @@ describe('Tests CapacitorClient', () => { expectOnlyMessageEventInEnvelope(mockTransportSend); }); - /* TODO: Implement Client Report test('send client reports on event envelope', () => { const mockTransportSend = jest.fn((_envelope: Envelope) => Promise.resolve()); const client = new CapacitorClient({ @@ -431,9 +438,7 @@ describe('Tests CapacitorClient', () => { expectOnlyMessageEventInEnvelope(mockTransportSend); }); -*/ - /* - TODO: To be implemented + test('keeps outcomes in case envelope fails to send', () => { const mockTransportSend = jest.fn((_envelope: Envelope) => rejectedSyncPromise(new SentryError('Test'))); const client = new CapacitorClient({ @@ -454,10 +459,7 @@ describe('Tests CapacitorClient', () => { { reason: 'before_send', category: 'error', quantity: 1 }, ]); }); - */ - /* - TODO: To be implemented. test('sends buffered client reports on second try', () => { const mockTransportSend = getSyncPromiseRejectOnFirstCall<[Envelope]>(new SentryError('Test')); const client = new CapacitorClient({ @@ -493,9 +495,19 @@ describe('Tests CapacitorClient', () => { ); expect((client as unknown as { _outcomesBuffer: Outcome[] })._outcomesBuffer).toEqual([]); }); - */ - }); + function expectOnlyMessageEventInEnvelope(transportSend: jest.Mock) { + expect(transportSend).toBeCalledTimes(1); + expect(transportSend.mock.calls[0][firstArg][envelopeItems]).toHaveLength(1); + expect(transportSend.mock.calls[0][firstArg][envelopeItems][0][envelopeItemHeader]).toEqual( + expect.objectContaining({ type: 'event' }), + ); + } + + function mockDroppedEvent(client: CapacitorClient) { + client.recordDroppedEvent('before_send', 'error'); + } + }); function mockedOptions(options: Partial): CapacitorClientOptions { return { @@ -508,3 +520,4 @@ describe('Tests CapacitorClient', () => { ...options, }; } +});