11/* eslint-disable max-lines */ // TODO: We might want to split this file up
2- import { addGlobalEventProcessor , captureException , getCurrentHub , setContext } from '@sentry/core' ;
3- import type { Breadcrumb , ReplayEvent , ReplayRecordingMode , TransportMakeRequestResponse } from '@sentry/types' ;
2+ import { addGlobalEventProcessor , captureException , getCurrentHub } from '@sentry/core' ;
3+ import type { Breadcrumb , ReplayRecordingMode } from '@sentry/types' ;
44import type { RateLimits } from '@sentry/utils' ;
5- import { addInstrumentationHandler , disabledUntil , isRateLimited , logger , updateRateLimits } from '@sentry/utils' ;
5+ import { addInstrumentationHandler , disabledUntil , logger } from '@sentry/utils' ;
66import { EventType , record } from 'rrweb' ;
77
8- import {
9- MAX_SESSION_LIFE ,
10- REPLAY_EVENT_NAME ,
11- SESSION_IDLE_DURATION ,
12- UNABLE_TO_SEND_REPLAY ,
13- VISIBILITY_CHANGE_TIMEOUT ,
14- WINDOW ,
15- } from './constants' ;
8+ import { MAX_SESSION_LIFE , SESSION_IDLE_DURATION , VISIBILITY_CHANGE_TIMEOUT , WINDOW } from './constants' ;
169import { breadcrumbHandler } from './coreHandlers/breadcrumbHandler' ;
1710import { handleFetchSpanListener } from './coreHandlers/handleFetch' ;
1811import { handleGlobalEventListener } from './coreHandlers/handleGlobalEvent' ;
@@ -34,28 +27,19 @@ import type {
3427 RecordingOptions ,
3528 ReplayContainer as ReplayContainerInterface ,
3629 ReplayPluginOptions ,
37- SendReplay ,
3830 Session ,
3931} from './types' ;
4032import { addEvent } from './util/addEvent' ;
4133import { addMemoryEntry } from './util/addMemoryEntry' ;
4234import { createBreadcrumb } from './util/createBreadcrumb' ;
4335import { createPerformanceEntries } from './util/createPerformanceEntries' ;
4436import { createPerformanceSpans } from './util/createPerformanceSpans' ;
45- import { createRecordingData } from './util/createRecordingData' ;
46- import { createReplayEnvelope } from './util/createReplayEnvelope' ;
4737import { debounce } from './util/debounce' ;
4838import { isExpired } from './util/isExpired' ;
4939import { isSessionExpired } from './util/isSessionExpired' ;
5040import { overwriteRecordDroppedEvent , restoreRecordDroppedEvent } from './util/monkeyPatchRecordDroppedEvent' ;
51- import { prepareReplayEvent } from './util/prepareReplayEvent' ;
52-
53- /**
54- * Returns true to return control to calling function, otherwise continue with normal batching
55- */
56-
57- const BASE_RETRY_INTERVAL = 5000 ;
58- const MAX_RETRY_COUNT = 3 ;
41+ import { sendReplay } from './util/sendReplay' ;
42+ import { RateLimitError } from './util/sendReplayRequest' ;
5943
6044/**
6145 * The main replay container class, which holds all the state and methods for recording and sending replays.
@@ -86,9 +70,6 @@ export class ReplayContainer implements ReplayContainerInterface {
8670
8771 private _performanceObserver : PerformanceObserver | null = null ;
8872
89- private _retryCount : number = 0 ;
90- private _retryInterval : number = BASE_RETRY_INTERVAL ;
91-
9273 private _debouncedFlush : ReturnType < typeof debounce > ;
9374 private _flushLock : Promise < unknown > | null = null ;
9475
@@ -129,11 +110,6 @@ export class ReplayContainer implements ReplayContainerInterface {
129110 initialUrl : '' ,
130111 } ;
131112
132- /**
133- * A RateLimits object holding the rate-limit durations in case a sent replay event was rate-limited.
134- */
135- private _rateLimits : RateLimits = { } ;
136-
137113 public constructor ( {
138114 options,
139115 recordingOptions,
@@ -837,14 +813,20 @@ export class ReplayContainer implements ReplayContainerInterface {
837813 const segmentId = this . session . segmentId ++ ;
838814 this . _maybeSaveSession ( ) ;
839815
840- await this . _sendReplay ( {
816+ await sendReplay ( {
841817 replayId,
842- events : recordingData ,
818+ recordingData,
843819 segmentId,
844820 includeReplayStartTimestamp : segmentId === 0 ,
845821 eventContext,
822+ session : this . session ,
823+ options : this . getOptions ( ) ,
824+ timestamp : new Date ( ) . getTime ( ) ,
846825 } ) ;
847826 } catch ( err ) {
827+ if ( err instanceof RateLimitError ) {
828+ this . _handleRateLimit ( err . rateLimits ) ;
829+ }
848830 this . _handleException ( err ) ;
849831 }
850832 }
@@ -897,185 +879,6 @@ export class ReplayContainer implements ReplayContainerInterface {
897879 }
898880 } ;
899881
900- /**
901- * Send replay attachment using `fetch()`
902- */
903- private async _sendReplayRequest ( {
904- events,
905- replayId,
906- segmentId : segment_id ,
907- includeReplayStartTimestamp,
908- eventContext,
909- timestamp = new Date ( ) . getTime ( ) ,
910- } : SendReplay ) : Promise < void | TransportMakeRequestResponse > {
911- const recordingData = createRecordingData ( {
912- events,
913- headers : {
914- segment_id,
915- } ,
916- } ) ;
917-
918- const { urls, errorIds, traceIds, initialTimestamp } = eventContext ;
919-
920- const hub = getCurrentHub ( ) ;
921- const client = hub . getClient ( ) ;
922- const scope = hub . getScope ( ) ;
923- const transport = client && client . getTransport ( ) ;
924- const dsn = client ?. getDsn ( ) ;
925-
926- if ( ! client || ! scope || ! transport || ! dsn || ! this . session || ! this . session . sampled ) {
927- return ;
928- }
929-
930- const baseEvent : ReplayEvent = {
931- // @ts -ignore private api
932- type : REPLAY_EVENT_NAME ,
933- ...( includeReplayStartTimestamp ? { replay_start_timestamp : initialTimestamp / 1000 } : { } ) ,
934- timestamp : timestamp / 1000 ,
935- error_ids : errorIds ,
936- trace_ids : traceIds ,
937- urls,
938- replay_id : replayId ,
939- segment_id,
940- replay_type : this . session . sampled ,
941- } ;
942-
943- const replayEvent = await prepareReplayEvent ( { scope, client, replayId, event : baseEvent } ) ;
944-
945- if ( ! replayEvent ) {
946- // Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions
947- client . recordDroppedEvent ( 'event_processor' , 'replay_event' , baseEvent ) ;
948- __DEBUG_BUILD__ && logger . log ( 'An event processor returned `null`, will not send event.' ) ;
949- return ;
950- }
951-
952- replayEvent . tags = {
953- ...replayEvent . tags ,
954- sessionSampleRate : this . _options . sessionSampleRate ,
955- errorSampleRate : this . _options . errorSampleRate ,
956- } ;
957-
958- /*
959- For reference, the fully built event looks something like this:
960- {
961- "type": "replay_event",
962- "timestamp": 1670837008.634,
963- "error_ids": [
964- "errorId"
965- ],
966- "trace_ids": [
967- "traceId"
968- ],
969- "urls": [
970- "https://example.com"
971- ],
972- "replay_id": "eventId",
973- "segment_id": 3,
974- "replay_type": "error",
975- "platform": "javascript",
976- "event_id": "eventId",
977- "environment": "production",
978- "sdk": {
979- "integrations": [
980- "BrowserTracing",
981- "Replay"
982- ],
983- "name": "sentry.javascript.browser",
984- "version": "7.25.0"
985- },
986- "sdkProcessingMetadata": {},
987- "tags": {
988- "sessionSampleRate": 1,
989- "errorSampleRate": 0,
990- }
991- }
992- */
993-
994- const envelope = createReplayEnvelope ( replayEvent , recordingData , dsn , client . getOptions ( ) . tunnel ) ;
995-
996- try {
997- const response = await transport . send ( envelope ) ;
998- // TODO (v8): we can remove this guard once transport.send's type signature doesn't include void anymore
999- if ( response ) {
1000- this . _rateLimits = updateRateLimits ( this . _rateLimits , response ) ;
1001- if ( isRateLimited ( this . _rateLimits , 'replay' ) ) {
1002- this . _handleRateLimit ( ) ;
1003- }
1004- }
1005- return response ;
1006- } catch {
1007- throw new Error ( UNABLE_TO_SEND_REPLAY ) ;
1008- }
1009- }
1010-
1011- /**
1012- * Reset the counter of retries for sending replays.
1013- */
1014- private _resetRetries ( ) : void {
1015- this . _retryCount = 0 ;
1016- this . _retryInterval = BASE_RETRY_INTERVAL ;
1017- }
1018-
1019- /**
1020- * Finalize and send the current replay event to Sentry
1021- */
1022- private async _sendReplay ( {
1023- replayId,
1024- events,
1025- segmentId,
1026- includeReplayStartTimestamp,
1027- eventContext,
1028- } : SendReplay ) : Promise < unknown > {
1029- // short circuit if there's no events to upload (this shouldn't happen as _runFlush makes this check)
1030- if ( ! events . length ) {
1031- return ;
1032- }
1033-
1034- try {
1035- await this . _sendReplayRequest ( {
1036- events,
1037- replayId,
1038- segmentId,
1039- includeReplayStartTimestamp,
1040- eventContext,
1041- } ) ;
1042- this . _resetRetries ( ) ;
1043- return true ;
1044- } catch ( err ) {
1045- // Capture error for every failed replay
1046- setContext ( 'Replays' , {
1047- _retryCount : this . _retryCount ,
1048- } ) ;
1049- this . _handleException ( err ) ;
1050-
1051- // If an error happened here, it's likely that uploading the attachment
1052- // failed, we'll can retry with the same events payload
1053- if ( this . _retryCount >= MAX_RETRY_COUNT ) {
1054- throw new Error ( `${ UNABLE_TO_SEND_REPLAY } - max retries exceeded` ) ;
1055- }
1056-
1057- // will retry in intervals of 5, 10, 30
1058- this . _retryInterval = ++ this . _retryCount * this . _retryInterval ;
1059-
1060- return await new Promise ( ( resolve , reject ) => {
1061- setTimeout ( async ( ) => {
1062- try {
1063- await this . _sendReplay ( {
1064- replayId,
1065- events,
1066- segmentId,
1067- includeReplayStartTimestamp,
1068- eventContext,
1069- } ) ;
1070- resolve ( true ) ;
1071- } catch ( err ) {
1072- reject ( err ) ;
1073- }
1074- } , this . _retryInterval ) ;
1075- } ) ;
1076- }
1077- }
1078-
1079882 /** Save the session, if it is sticky */
1080883 private _maybeSaveSession ( ) : void {
1081884 if ( this . session && this . _options . stickySession ) {
@@ -1086,14 +889,14 @@ export class ReplayContainer implements ReplayContainerInterface {
1086889 /**
1087890 * Pauses the replay and resumes it after the rate-limit duration is over.
1088891 */
1089- private _handleRateLimit ( ) : void {
892+ private _handleRateLimit ( rateLimits : RateLimits ) : void {
1090893 // in case recording is already paused, we don't need to do anything, as we might have already paused because of a
1091894 // rate limit
1092895 if ( this . isPaused ( ) ) {
1093896 return ;
1094897 }
1095898
1096- const rateLimitEnd = disabledUntil ( this . _rateLimits , 'replay' ) ;
899+ const rateLimitEnd = disabledUntil ( rateLimits , 'replay' ) ;
1097900 const rateLimitDuration = rateLimitEnd - Date . now ( ) ;
1098901
1099902 if ( rateLimitDuration > 0 ) {
0 commit comments