diff --git a/packages/types/src/envelope.ts b/packages/types/src/envelope.ts index 2a70cea96667..aa7655db9cea 100644 --- a/packages/types/src/envelope.ts +++ b/packages/types/src/envelope.ts @@ -18,12 +18,12 @@ export type BaseEnvelopeItemHeaders = { length?: number; }; -export type BaseEnvelopeItem = [IH, P]; // P is for payload +type BaseEnvelopeItem = [IH, P]; // P is for payload -export type BaseEnvelope< - EH extends BaseEnvelopeHeaders, - I extends BaseEnvelopeItem, -> = [EH, I[]]; +type BaseEnvelope> = [ + EH, + I[], +]; type EventItemHeaders = { type: 'event' | 'transaction' }; type AttachmentItemHeaders = { type: 'attachment'; filename: string }; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 996f53c4c685..13651b7cd78e 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -6,9 +6,7 @@ export { DsnComponents, DsnLike, DsnProtocol } from './dsn'; export { DebugImage, DebugImageType, DebugMeta } from './debugMeta'; export { AttachmentItem, - BaseEnvelope, BaseEnvelopeHeaders, - BaseEnvelopeItem, BaseEnvelopeItemHeaders, ClientReportEnvelope, ClientReportItem, diff --git a/packages/utils/src/envelope.ts b/packages/utils/src/envelope.ts new file mode 100644 index 000000000000..7552bd339784 --- /dev/null +++ b/packages/utils/src/envelope.ts @@ -0,0 +1,38 @@ +import { Envelope } from '@sentry/types'; + +/** + * Creates an envelope. + * Make sure to always explicitly provide the generic to this function + * so that the envelope types resolve correctly. + */ +export function createEnvelope(headers: E[0], items: E[1] = []): E { + return [headers, items] as E; +} + +/** + * Add an item to an envelope. + * Make sure to always explicitly provide the generic to this function + * so that the envelope types resolve correctly. + */ +export function addItemToEnvelope(envelope: E, newItem: E[1][number]): E { + const [headers, items] = envelope; + return [headers, [...items, newItem]] as E; +} + +/** + * Serializes an envelope into a string. + */ +export function serializeEnvelope(envelope: Envelope): string { + const [headers, items] = envelope; + const serializedHeaders = JSON.stringify(headers); + + // Have to cast items to any here since Envelope is a union type + // Fixed in Typescript 4.2 + // TODO: Remove any[] cast when we upgrade to TS 4.2 + // https://github.com/microsoft/TypeScript/issues/36390 + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (items as any[]).reduce((acc, item: typeof items[number]) => { + const [itemHeaders, payload] = item; + return `${acc}\n${JSON.stringify(itemHeaders)}\n${JSON.stringify(payload)}`; + }, serializedHeaders); +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index 900e50653037..511d8a1315ca 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -21,3 +21,4 @@ export * from './supports'; export * from './syncpromise'; export * from './time'; export * from './env'; +export * from './envelope'; diff --git a/packages/utils/test/envelope.test.ts b/packages/utils/test/envelope.test.ts new file mode 100644 index 000000000000..64cdbe5e39ca --- /dev/null +++ b/packages/utils/test/envelope.test.ts @@ -0,0 +1,47 @@ +import { EventEnvelope } from '@sentry/types'; + +import { addItemToEnvelope, createEnvelope, serializeEnvelope } from '../src/envelope'; +import { parseEnvelope } from './testutils'; + +describe('envelope', () => { + describe('createEnvelope()', () => { + const testTable: Array<[string, Parameters[0], Parameters[1]]> = [ + ['creates an empty envelope', {}, []], + ['creates an envelope with a header but no items', { dsn: 'https://public@example.com/1', sdk: {} }, []], + ]; + it.each(testTable)('%s', (_: string, headers, items) => { + const env = createEnvelope(headers, items); + expect(env).toHaveLength(2); + expect(env[0]).toStrictEqual(headers); + expect(env[1]).toStrictEqual(items); + }); + }); + + describe('serializeEnvelope()', () => { + it('serializes an envelope', () => { + const env = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, []); + expect(serializeEnvelope(env)).toMatchInlineSnapshot( + `"{\\"event_id\\":\\"aa3ff046696b4bc6b609ce6d28fde9e2\\",\\"sent_at\\":\\"123\\"}"`, + ); + }); + }); + + describe('addItemToEnvelope()', () => { + it('adds an item to an envelope', () => { + const env = createEnvelope({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }, []); + const parsedEnvelope = parseEnvelope(serializeEnvelope(env)); + expect(parsedEnvelope).toHaveLength(1); + expect(parsedEnvelope[0]).toEqual({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }); + + const newEnv = addItemToEnvelope(env, [ + { type: 'event' }, + { event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }, + ]); + const parsedNewEnvelope = parseEnvelope(serializeEnvelope(newEnv)); + expect(parsedNewEnvelope).toHaveLength(3); + expect(parsedNewEnvelope[0]).toEqual({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2', sent_at: '123' }); + expect(parsedNewEnvelope[1]).toEqual({ type: 'event' }); + expect(parsedNewEnvelope[2]).toEqual({ event_id: 'aa3ff046696b4bc6b609ce6d28fde9e2' }); + }); + }); +}); diff --git a/packages/utils/test/testutils.ts b/packages/utils/test/testutils.ts index 6130ee7ca365..aa3c5485eec1 100644 --- a/packages/utils/test/testutils.ts +++ b/packages/utils/test/testutils.ts @@ -11,3 +11,7 @@ export const testOnlyIfNodeVersionAtLeast = (minVersion: number): jest.It => { return it; }; + +export function parseEnvelope(env: string): Array> { + return env.split('\n').map(e => JSON.parse(e)); +}