diff --git a/packages/replay/jest.setup.ts b/packages/replay/jest.setup.ts index f7d2a1248c0c..b6031d4f3e46 100644 --- a/packages/replay/jest.setup.ts +++ b/packages/replay/jest.setup.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ import { getCurrentHub } from '@sentry/core'; -import { Transport } from '@sentry/types'; +import { ReplayRecordingData,Transport } from '@sentry/types'; import type { ReplayContainer, Session } from './src/types'; @@ -55,7 +55,7 @@ type SentReplayExpected = { replayEventPayload?: ReplayEventPayload; recordingHeader?: RecordingHeader; recordingPayloadHeader?: RecordingPayloadHeader; - events?: string | Uint8Array; + events?: ReplayRecordingData; }; // eslint-disable-next-line @typescript-eslint/explicit-function-return-type diff --git a/packages/replay/src/eventBuffer.ts b/packages/replay/src/eventBuffer.ts index b83942be4f67..1d670506de67 100644 --- a/packages/replay/src/eventBuffer.ts +++ b/packages/replay/src/eventBuffer.ts @@ -2,6 +2,7 @@ // TODO: figure out member access types and remove the line above import { captureException } from '@sentry/core'; +import { ReplayRecordingData } from '@sentry/types'; import { logger } from '@sentry/utils'; import type { EventBuffer, RecordingEvent, WorkerRequest, WorkerResponse } from './types'; @@ -95,7 +96,7 @@ export class EventBufferCompressionWorker implements EventBuffer { return this._eventBufferItemLength; } - public async addEvent(event: RecordingEvent, isCheckout?: boolean): Promise { + public async addEvent(event: RecordingEvent, isCheckout?: boolean): Promise { if (isCheckout) { // This event is a checkout, make sure worker buffer is cleared before // proceeding. @@ -159,7 +160,7 @@ export class EventBufferCompressionWorker implements EventBuffer { }); } - private _sendEventToWorker(event: RecordingEvent): Promise { + private _sendEventToWorker(event: RecordingEvent): Promise { const promise = this._postMessage({ id: this._getAndIncrementId(), method: 'addEvent', diff --git a/packages/replay/src/replay.ts b/packages/replay/src/replay.ts index f2e7e4f78162..7031e60f439d 100644 --- a/packages/replay/src/replay.ts +++ b/packages/replay/src/replay.ts @@ -1,6 +1,6 @@ /* eslint-disable max-lines */ // TODO: We might want to split this file up import { addGlobalEventProcessor, captureException, getCurrentHub, setContext } from '@sentry/core'; -import { Breadcrumb, Event } from '@sentry/types'; +import { Breadcrumb, ReplayEvent } from '@sentry/types'; import { addInstrumentationHandler, logger } from '@sentry/utils'; import debounce from 'lodash.debounce'; import { EventType, record } from 'rrweb'; @@ -927,7 +927,7 @@ export class ReplayContainer implements ReplayContainerInterface { return; } - const baseEvent: Event = { + const baseEvent: ReplayEvent = { // @ts-ignore private api type: REPLAY_EVENT_NAME, ...(includeReplayStartTimestamp ? { replay_start_timestamp: initialTimestamp / 1000 } : {}), diff --git a/packages/replay/src/types.ts b/packages/replay/src/types.ts index f9da9bc42cb8..c331273b135e 100644 --- a/packages/replay/src/types.ts +++ b/packages/replay/src/types.ts @@ -1,3 +1,5 @@ +import { ReplayRecordingData } from '@sentry/types'; + import type { eventWithTime, recordOptions } from './types/rrweb'; export type RecordingEvent = eventWithTime; @@ -47,7 +49,7 @@ export interface WorkerResponse { id: number; method: string; success: boolean; - response: string | Uint8Array; + response: ReplayRecordingData; } export interface SampleRates { @@ -211,7 +213,7 @@ export interface EventBuffer { readonly length: number; destroy(): void; addEvent(event: RecordingEvent, isCheckout?: boolean): void; - finish(): Promise; + finish(): Promise; } export type AddUpdateCallback = () => boolean | void; diff --git a/packages/replay/src/util/createPayload.ts b/packages/replay/src/util/createPayload.ts index 6567703022ef..b3b6615b1b40 100644 --- a/packages/replay/src/util/createPayload.ts +++ b/packages/replay/src/util/createPayload.ts @@ -1,3 +1,5 @@ +import { ReplayRecordingData } from '@sentry/types'; + import type { RecordedEvents } from '../types'; export function createPayload({ @@ -6,7 +8,7 @@ export function createPayload({ }: { events: RecordedEvents; headers: Record; -}): string | Uint8Array { +}): ReplayRecordingData { let payloadWithSequence; // XXX: newline is needed to separate sequence id from events diff --git a/packages/replay/src/util/createReplayEnvelope.ts b/packages/replay/src/util/createReplayEnvelope.ts index 8850974ec18b..3d950e06cb29 100644 --- a/packages/replay/src/util/createReplayEnvelope.ts +++ b/packages/replay/src/util/createReplayEnvelope.ts @@ -1,25 +1,22 @@ -import { DsnComponents, Envelope, Event } from '@sentry/types'; +import { DsnComponents, ReplayEnvelope, ReplayEvent, ReplayRecordingData } from '@sentry/types'; import { createEnvelope, createEventEnvelopeHeaders, getSdkMetadataForEnvelopeHeader } from '@sentry/utils'; export function createReplayEnvelope( - replayEvent: Event, - payloadWithSequence: string | Uint8Array, + replayEvent: ReplayEvent, + recordingData: ReplayRecordingData, dsn: DsnComponents, tunnel?: string, -): Envelope { - return createEnvelope( +): ReplayEnvelope { + return createEnvelope( createEventEnvelopeHeaders(replayEvent, getSdkMetadataForEnvelopeHeader(replayEvent), tunnel, dsn), [ - // @ts-ignore New types [{ type: 'replay_event' }, replayEvent], [ { - // @ts-ignore setting envelope type: 'replay_recording', - length: payloadWithSequence.length, + length: recordingData.length, }, - // @ts-ignore: Type 'string' is not assignable to type 'ClientReport'.ts(2322) - payloadWithSequence, + recordingData, ], ], ); diff --git a/packages/replay/src/util/getReplayEvent.ts b/packages/replay/src/util/getReplayEvent.ts index b6e839517441..3df3f3c5f3ac 100644 --- a/packages/replay/src/util/getReplayEvent.ts +++ b/packages/replay/src/util/getReplayEvent.ts @@ -1,5 +1,5 @@ import { Scope } from '@sentry/core'; -import { Client, Event } from '@sentry/types'; +import { Client, ReplayEvent } from '@sentry/types'; export async function getReplayEvent({ client, @@ -10,11 +10,11 @@ export async function getReplayEvent({ client: Client; scope: Scope; replayId: string; - event: Event; -}): Promise { + event: ReplayEvent; +}): Promise { // XXX: This event does not trigger `beforeSend` in SDK // @ts-ignore private api - const preparedEvent: Event | null = await client._prepareEvent(event, { event_id }, scope); + const preparedEvent: ReplayEvent | null = await client._prepareEvent(event, { event_id }, scope); if (preparedEvent) { // extract the SDK name because `client._prepareEvent` doesn't add it to the event diff --git a/packages/replay/test/unit/util/createReplayEnvelope.test.ts b/packages/replay/test/unit/util/createReplayEnvelope.test.ts index 76fd3348cecb..456c0863e3fc 100644 --- a/packages/replay/test/unit/util/createReplayEnvelope.test.ts +++ b/packages/replay/test/unit/util/createReplayEnvelope.test.ts @@ -1,4 +1,4 @@ -import { Event } from '@sentry/types'; +import { ReplayEvent } from '@sentry/types'; import { makeDsn } from '@sentry/utils'; import { createReplayEnvelope } from '../../../src/util/createReplayEnvelope'; @@ -6,7 +6,8 @@ import { createReplayEnvelope } from '../../../src/util/createReplayEnvelope'; describe('createReplayEnvelope', () => { const REPLAY_ID = 'MY_REPLAY_ID'; - const replayEvent = { + const replayEvent: ReplayEvent = { + // @ts-ignore private api type: 'replay_event', timestamp: 1670837008.634, error_ids: ['errorId'], @@ -41,7 +42,7 @@ describe('createReplayEnvelope', () => { }); it('creates an envelope for a given Replay event', () => { - const envelope = createReplayEnvelope(replayEvent as Event, payloadWithSequence, dsn); + const envelope = createReplayEnvelope(replayEvent, payloadWithSequence, dsn); expect(envelope).toEqual([ { @@ -73,7 +74,7 @@ describe('createReplayEnvelope', () => { }); it('creates an envelope with the `dsn` key in the header if `tunnel` is specified', () => { - const envelope = createReplayEnvelope(replayEvent as Event, payloadWithSequence, dsn, '/my-tunnel-endpoint'); + const envelope = createReplayEnvelope(replayEvent, payloadWithSequence, dsn, '/my-tunnel-endpoint'); expect(envelope).toEqual([ { diff --git a/packages/replay/test/unit/util/getReplayEvent.test.ts b/packages/replay/test/unit/util/getReplayEvent.test.ts index fadddae05d3b..31ab71747771 100644 --- a/packages/replay/test/unit/util/getReplayEvent.test.ts +++ b/packages/replay/test/unit/util/getReplayEvent.test.ts @@ -1,6 +1,6 @@ import { BrowserClient } from '@sentry/browser'; import { getCurrentHub, Hub, Scope } from '@sentry/core'; -import { Client, Event } from '@sentry/types'; +import { Client, ReplayEvent } from '@sentry/types'; import { REPLAY_EVENT_NAME } from '../../../src/constants'; import { getReplayEvent } from '../../../src/util/getReplayEvent'; @@ -25,7 +25,7 @@ describe('getReplayEvent', () => { expect(scope).toBeDefined(); const replayId = 'replay-ID'; - const event: Event = { + const event: ReplayEvent = { // @ts-ignore private api type: REPLAY_EVENT_NAME, timestamp: 1670837008.634, @@ -33,6 +33,7 @@ describe('getReplayEvent', () => { trace_ids: ['trace-ID'], urls: ['https://sentry.io/'], replay_id: replayId, + event_id: replayId, segment_id: 3, }; diff --git a/packages/types/src/datacategory.ts b/packages/types/src/datacategory.ts index 747db363e78e..a462957b3ccd 100644 --- a/packages/types/src/datacategory.ts +++ b/packages/types/src/datacategory.ts @@ -19,4 +19,7 @@ export type DataCategory = // SDK internal event, like client_reports | 'internal' // Profile event type - | 'profile'; + | 'profile' + // Replay event types + | 'replay_event' + | 'replay_recording'; diff --git a/packages/types/src/envelope.ts b/packages/types/src/envelope.ts index 6eb27dc0c4c8..74cb2764b7e3 100644 --- a/packages/types/src/envelope.ts +++ b/packages/types/src/envelope.ts @@ -1,6 +1,7 @@ import { ClientReport } from './clientreport'; import { DsnComponents } from './dsn'; import { Event } from './event'; +import { ReplayEvent, ReplayRecordingData } from './replay'; import { SdkInfo } from './sdkinfo'; import { Session, SessionAggregates } from './session'; import { Transaction } from './transaction'; @@ -27,7 +28,9 @@ export type EnvelopeItemType = | 'transaction' | 'attachment' | 'event' - | 'profile'; + | 'profile' + | 'replay_event' + | 'replay_recording'; export type BaseEnvelopeHeaders = { [key: string]: unknown; @@ -62,6 +65,8 @@ type UserFeedbackItemHeaders = { type: 'user_report' }; type SessionItemHeaders = { type: 'session' }; type SessionAggregatesItemHeaders = { type: 'sessions' }; type ClientReportItemHeaders = { type: 'client_report' }; +type ReplayEventItemHeaders = { type: 'replay_event' }; +type ReplayRecordingItemHeaders = { type: 'replay_recording'; length: number }; export type EventItem = BaseEnvelopeItem; export type AttachmentItem = BaseEnvelopeItem; @@ -70,14 +75,18 @@ export type SessionItem = | BaseEnvelopeItem | BaseEnvelopeItem; export type ClientReportItem = BaseEnvelopeItem; +type ReplayEventItem = BaseEnvelopeItem; +type ReplayRecordingItem = BaseEnvelopeItem; export type EventEnvelopeHeaders = { event_id: string; sent_at: string; trace?: DynamicSamplingContext }; type SessionEnvelopeHeaders = { sent_at: string }; type ClientReportEnvelopeHeaders = BaseEnvelopeHeaders; +type ReplayEnvelopeHeaders = BaseEnvelopeHeaders; export type EventEnvelope = BaseEnvelope; export type SessionEnvelope = BaseEnvelope; export type ClientReportEnvelope = BaseEnvelope; +export type ReplayEnvelope = [ReplayEnvelopeHeaders, [ReplayEventItem, ReplayRecordingItem]]; -export type Envelope = EventEnvelope | SessionEnvelope | ClientReportEnvelope; +export type Envelope = EventEnvelope | SessionEnvelope | ClientReportEnvelope | ReplayEnvelope; export type EnvelopeItem = Envelope[1][number]; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index c07403474a73..56d5379f9e34 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -19,6 +19,7 @@ export type { EventEnvelope, EventEnvelopeHeaders, EventItem, + ReplayEnvelope, SessionEnvelope, SessionItem, UserFeedbackItem, @@ -39,6 +40,7 @@ export type { ExtractedNodeRequestData, HttpHeaderValue, Primitive, WorkerLocati export type { ClientOptions, Options } from './options'; export type { Package } from './package'; export type { PolymorphicEvent, PolymorphicRequest } from './polymorphics'; +export type { ReplayEvent, ReplayRecordingData } from './replay'; export type { QueryParams, Request } from './request'; export type { Runtime } from './runtime'; export type { CaptureContext, Scope, ScopeContext } from './scope'; diff --git a/packages/types/src/replay.ts b/packages/types/src/replay.ts new file mode 100644 index 000000000000..1bec7202ecdc --- /dev/null +++ b/packages/types/src/replay.ts @@ -0,0 +1,19 @@ +import { Event } from './event'; + +/** + * NOTE: These types are still considered Beta and subject to change. + * @hidden + */ +export interface ReplayEvent extends Event { + urls: string[]; + error_ids: string[]; + trace_ids: string[]; + replay_id: string; + segment_id: number; +} + +/** + * NOTE: These types are still considered Beta and subject to change. + * @hidden + */ +export type ReplayRecordingData = string | Uint8Array; diff --git a/packages/utils/src/envelope.ts b/packages/utils/src/envelope.ts index 3b9e0e374b53..5cb1a675645e 100644 --- a/packages/utils/src/envelope.ts +++ b/packages/utils/src/envelope.ts @@ -187,6 +187,8 @@ const ITEM_TYPE_TO_DATA_CATEGORY_MAP: Record = { client_report: 'internal', user_report: 'default', profile: 'profile', + replay_event: 'replay_event', + replay_recording: 'replay_recording', }; /**