Skip to content
Merged
34 changes: 15 additions & 19 deletions packages/browser/src/transports/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
sessionToSentryRequest,
} from '@sentry/core';
import {
ClientReport,
Event,
Outcome,
Response as SentryResponse,
Expand All @@ -17,7 +18,7 @@ import {
TransportOptions,
} from '@sentry/types';
import {
dateTimestampInSeconds,
createClientReportEnvelope,
dsnToString,
eventStatusFromHttpCode,
getGlobalObject,
Expand All @@ -26,6 +27,7 @@ import {
makePromiseBuffer,
parseRetryAfterHeader,
PromiseBuffer,
serializeEnvelope,
} from '@sentry/utils';

import { sendReport } from './utils';
Expand Down Expand Up @@ -127,26 +129,20 @@ export abstract class BaseTransport implements Transport {
logger.log(`Flushing outcomes:\n${JSON.stringify(outcomes, null, 2)}`);

const url = getEnvelopeEndpointWithUrlEncodedAuth(this._api.dsn, this._api.tunnel);
// Envelope header is required to be at least an empty object
const envelopeHeader = JSON.stringify({ ...(this._api.tunnel && { dsn: dsnToString(this._api.dsn) }) });
const itemHeaders = JSON.stringify({
type: 'client_report',
});
const item = JSON.stringify({
timestamp: dateTimestampInSeconds(),
discarded_events: Object.keys(outcomes).map(key => {
const [category, reason] = key.split(':');
return {
reason,
category,
quantity: outcomes[key],
};
}),
});
const envelope = `${envelopeHeader}\n${itemHeaders}\n${item}`;

const discardedEvents = Object.keys(outcomes).map(key => {
const [category, reason] = key.split(':');
return {
reason,
category,
quantity: outcomes[key],
};
// TODO: Improve types on discarded_events to get rid of cast
}) as ClientReport['discarded_events'];
const envelope = createClientReportEnvelope(discardedEvents, this._api.tunnel && dsnToString(this._api.dsn));

try {
sendReport(url, envelope);
sendReport(url, serializeEnvelope(envelope));
} catch (e) {
logger.error(e);
}
Expand Down
2 changes: 1 addition & 1 deletion packages/types/src/clientreport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import { Outcome } from './transport';

export type ClientReport = {
timestamp: number;
discarded_events: { reason: Outcome; category: SentryRequestType; quantity: number };
discarded_events: Array<{ reason: Outcome; category: SentryRequestType; quantity: number }>;
};
24 changes: 24 additions & 0 deletions packages/utils/src/clientreport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ClientReport, ClientReportEnvelope, ClientReportItem } from '@sentry/types';

import { createEnvelope } from './envelope';
import { dateTimestampInSeconds } from './time';

/**
* Creates client report envelope
* @param discarded_events An array of discard events
* @param dsn A DSN that can be set on the header. Optional.
*/
export function createClientReportEnvelope(
discarded_events: ClientReport['discarded_events'],
dsn?: string,
timestamp?: number,
): ClientReportEnvelope {
const clientReportItem: ClientReportItem = [
{ type: 'client_report' },
{
timestamp: timestamp || dateTimestampInSeconds(),
discarded_events,
},
];
return createEnvelope<ClientReportEnvelope>(dsn ? { dsn } : {}, [clientReportItem]);
}
1 change: 1 addition & 0 deletions packages/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export * from './syncpromise';
export * from './time';
export * from './env';
export * from './envelope';
export * from './clientreport';
51 changes: 51 additions & 0 deletions packages/utils/test/clientreport.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { ClientReport } from '@sentry/types';

import { createClientReportEnvelope } from '../src/clientreport';
import { serializeEnvelope } from '../src/envelope';

const DEFAULT_DISCARDED_EVENTS: Array<ClientReport['discarded_events']> = [
{
reason: 'before_send',
category: 'event',
quantity: 30,
},
{
reason: 'network_error',
category: 'transaction',
quantity: 23,
},
];

const MOCK_DSN = 'https://[email protected]/1';

describe('createClientReportEnvelope', () => {
const testTable: Array<
[string, Parameters<typeof createClientReportEnvelope>[0], Parameters<typeof createClientReportEnvelope>[1]]
> = [
['with no discard reasons', [], undefined],
['with a dsn', [], MOCK_DSN],
['with discard reasons', DEFAULT_DISCARDED_EVENTS, MOCK_DSN],
];
it.each(testTable)('%s', (_: string, discardedEvents, dsn) => {
const env = createClientReportEnvelope(discardedEvents, dsn);

expect(env[0]).toEqual(dsn ? { dsn } : {});

const items = env[1];
expect(items).toHaveLength(1);
const clientReportItem = items[0];

expect(clientReportItem[0]).toEqual({ type: 'client_report' });
expect(clientReportItem[1]).toEqual({ timestamp: expect.any(Number), discarded_events: discardedEvents });
});

it('serializes an envelope', () => {
const env = createClientReportEnvelope(DEFAULT_DISCARDED_EVENTS, MOCK_DSN, 123456);
const serializedEnv = serializeEnvelope(env);
expect(serializedEnv).toMatchInlineSnapshot(`
"{\\"dsn\\":\\"https://[email protected]/1\\"}
{\\"type\\":\\"client_report\\"}
{\\"timestamp\\":123456,\\"discarded_events\\":[{\\"reason\\":\\"before_send\\",\\"category\\":\\"event\\",\\"quantity\\":30},{\\"reason\\":\\"network_error\\",\\"category\\":\\"transaction\\",\\"quantity\\":23}]}"
`);
});
});