1
1
/* 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' ;
4
4
import type { RateLimits } from '@sentry/utils' ;
5
- import { addInstrumentationHandler , disabledUntil , isRateLimited , logger , updateRateLimits } from '@sentry/utils' ;
5
+ import { addInstrumentationHandler , disabledUntil , logger } from '@sentry/utils' ;
6
6
import { EventType , record } from 'rrweb' ;
7
7
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' ;
16
9
import { breadcrumbHandler } from './coreHandlers/breadcrumbHandler' ;
17
10
import { handleFetchSpanListener } from './coreHandlers/handleFetch' ;
18
11
import { handleGlobalEventListener } from './coreHandlers/handleGlobalEvent' ;
@@ -34,28 +27,19 @@ import type {
34
27
RecordingOptions ,
35
28
ReplayContainer as ReplayContainerInterface ,
36
29
ReplayPluginOptions ,
37
- SendReplay ,
38
30
Session ,
39
31
} from './types' ;
40
32
import { addEvent } from './util/addEvent' ;
41
33
import { addMemoryEntry } from './util/addMemoryEntry' ;
42
34
import { createBreadcrumb } from './util/createBreadcrumb' ;
43
35
import { createPerformanceEntries } from './util/createPerformanceEntries' ;
44
36
import { createPerformanceSpans } from './util/createPerformanceSpans' ;
45
- import { createRecordingData } from './util/createRecordingData' ;
46
- import { createReplayEnvelope } from './util/createReplayEnvelope' ;
47
37
import { debounce } from './util/debounce' ;
48
38
import { isExpired } from './util/isExpired' ;
49
39
import { isSessionExpired } from './util/isSessionExpired' ;
50
40
import { 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' ;
59
43
60
44
/**
61
45
* 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 {
86
70
87
71
private _performanceObserver : PerformanceObserver | null = null ;
88
72
89
- private _retryCount : number = 0 ;
90
- private _retryInterval : number = BASE_RETRY_INTERVAL ;
91
-
92
73
private _debouncedFlush : ReturnType < typeof debounce > ;
93
74
private _flushLock : Promise < unknown > | null = null ;
94
75
@@ -129,11 +110,6 @@ export class ReplayContainer implements ReplayContainerInterface {
129
110
initialUrl : '' ,
130
111
} ;
131
112
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
-
137
113
public constructor ( {
138
114
options,
139
115
recordingOptions,
@@ -837,14 +813,20 @@ export class ReplayContainer implements ReplayContainerInterface {
837
813
const segmentId = this . session . segmentId ++ ;
838
814
this . _maybeSaveSession ( ) ;
839
815
840
- await this . _sendReplay ( {
816
+ await sendReplay ( {
841
817
replayId,
842
- events : recordingData ,
818
+ recordingData,
843
819
segmentId,
844
820
includeReplayStartTimestamp : segmentId === 0 ,
845
821
eventContext,
822
+ session : this . session ,
823
+ options : this . getOptions ( ) ,
824
+ timestamp : new Date ( ) . getTime ( ) ,
846
825
} ) ;
847
826
} catch ( err ) {
827
+ if ( err instanceof RateLimitError ) {
828
+ this . _handleRateLimit ( err . rateLimits ) ;
829
+ }
848
830
this . _handleException ( err ) ;
849
831
}
850
832
}
@@ -897,185 +879,6 @@ export class ReplayContainer implements ReplayContainerInterface {
897
879
}
898
880
} ;
899
881
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
-
1079
882
/** Save the session, if it is sticky */
1080
883
private _maybeSaveSession ( ) : void {
1081
884
if ( this . session && this . _options . stickySession ) {
@@ -1086,14 +889,14 @@ export class ReplayContainer implements ReplayContainerInterface {
1086
889
/**
1087
890
* Pauses the replay and resumes it after the rate-limit duration is over.
1088
891
*/
1089
- private _handleRateLimit ( ) : void {
892
+ private _handleRateLimit ( rateLimits : RateLimits ) : void {
1090
893
// in case recording is already paused, we don't need to do anything, as we might have already paused because of a
1091
894
// rate limit
1092
895
if ( this . isPaused ( ) ) {
1093
896
return ;
1094
897
}
1095
898
1096
- const rateLimitEnd = disabledUntil ( this . _rateLimits , 'replay' ) ;
899
+ const rateLimitEnd = disabledUntil ( rateLimits , 'replay' ) ;
1097
900
const rateLimitDuration = rateLimitEnd - Date . now ( ) ;
1098
901
1099
902
if ( rateLimitDuration > 0 ) {
0 commit comments