1
1
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
2
2
// TODO: figure out member access types and remove the line above
3
3
4
- import { captureException } from '@sentry/core ' ;
4
+ import type { ReplayRecordingData } from '@sentry/types ' ;
5
5
import { logger } from '@sentry/utils' ;
6
6
7
7
import type { AddEventResult , EventBuffer , RecordingEvent , WorkerRequest } from './types' ;
@@ -20,24 +20,90 @@ export function createEventBuffer({ useCompression }: CreateEventBufferParams):
20
20
const workerBlob = new Blob ( [ workerString ] ) ;
21
21
const workerUrl = URL . createObjectURL ( workerBlob ) ;
22
22
23
- try {
24
- __DEBUG_BUILD__ && logger . log ( '[Replay] Using compression worker' ) ;
25
- const worker = new Worker ( workerUrl ) ;
26
- if ( worker ) {
27
- return new EventBufferCompressionWorker ( worker ) ;
28
- } else {
29
- captureException ( new Error ( 'Unable to create compression worker' ) ) ;
30
- }
31
- } catch {
32
- // catch and ignore, fallback to simple event buffer
33
- }
34
- __DEBUG_BUILD__ && logger . log ( '[Replay] Falling back to simple event buffer' ) ;
23
+ __DEBUG_BUILD__ && logger . log ( '[Replay] Using compression worker' ) ;
24
+ const worker = new Worker ( workerUrl ) ;
25
+ return new EventBufferProxy ( worker ) ;
35
26
}
36
27
37
28
__DEBUG_BUILD__ && logger . log ( '[Replay] Using simple buffer' ) ;
38
29
return new EventBufferArray ( ) ;
39
30
}
40
31
32
+ /**
33
+ * This proxy will try to use the compression worker, and fall back to use the simple buffer if an error occurs there.
34
+ * This can happen e.g. if the worker cannot be loaded.
35
+ * Exported only for testing.
36
+ */
37
+ export class EventBufferProxy implements EventBuffer {
38
+ private _fallback : EventBufferArray ;
39
+ private _compression : EventBufferCompressionWorker ;
40
+ private _used : EventBuffer ;
41
+
42
+ public constructor ( worker : Worker ) {
43
+ this . _fallback = new EventBufferArray ( ) ;
44
+ this . _compression = new EventBufferCompressionWorker ( worker ) ;
45
+ this . _used = this . _fallback ;
46
+
47
+ void this . _ensureWorkerIsLoaded ( ) ;
48
+ }
49
+
50
+ /** @inheritDoc */
51
+ public get pendingLength ( ) : number {
52
+ return this . _used . pendingLength ;
53
+ }
54
+
55
+ /** @inheritDoc */
56
+ public get pendingEvents ( ) : RecordingEvent [ ] {
57
+ return this . _used . pendingEvents ;
58
+ }
59
+
60
+ /** @inheritDoc */
61
+ public destroy ( ) : void {
62
+ this . _fallback . destroy ( ) ;
63
+ this . _compression . destroy ( ) ;
64
+ }
65
+
66
+ /**
67
+ * Add an event to the event buffer.
68
+ *
69
+ * Returns true if event was successfully added.
70
+ */
71
+ public addEvent ( event : RecordingEvent , isCheckout ?: boolean ) : Promise < AddEventResult > {
72
+ return this . _used . addEvent ( event , isCheckout ) ;
73
+ }
74
+
75
+ /** @inheritDoc */
76
+ public finish ( ) : Promise < ReplayRecordingData > {
77
+ return this . _used . finish ( ) ;
78
+ }
79
+
80
+ /** Ensure the worker has loaded. */
81
+ private async _ensureWorkerIsLoaded ( ) : Promise < void > {
82
+ try {
83
+ await this . _compression . ensureReady ( ) ;
84
+ } catch ( error ) {
85
+ // If the worker fails to load, we fall back to the simple buffer.
86
+ // Nothing more to do from our side here
87
+ __DEBUG_BUILD__ && logger . log ( '[Replay] Failed to load the compression worker, falling back to simple buffer' ) ;
88
+ return ;
89
+ }
90
+
91
+ // Compression worker is ready, we can use it
92
+ // Now we need to switch over the array buffer to the compression worker
93
+ const addEventPromises : Promise < void > [ ] = [ ] ;
94
+ for ( const event of this . _fallback . pendingEvents ) {
95
+ addEventPromises . push ( this . _compression . addEvent ( event ) ) ;
96
+ }
97
+
98
+ // We switch over to the compression buffer immediately - any further events will be added
99
+ // after the previously buffered ones
100
+ this . _used = this . _compression ;
101
+
102
+ // Wait for original events to be re-added before resolving
103
+ await Promise . all ( addEventPromises ) ;
104
+ }
105
+ }
106
+
41
107
class EventBufferArray implements EventBuffer {
42
108
private _events : RecordingEvent [ ] ;
43
109
@@ -119,6 +185,34 @@ export class EventBufferCompressionWorker implements EventBuffer {
119
185
return this . _pendingEvents ;
120
186
}
121
187
188
+ /**
189
+ * Ensure the worker is ready (or not).
190
+ * This will either resolve when the worker is ready, or reject if an error occured.
191
+ */
192
+ public ensureReady ( ) : Promise < void > {
193
+ return new Promise ( ( resolve , reject ) => {
194
+ this . _worker . addEventListener (
195
+ 'message' ,
196
+ ( { data } : MessageEvent ) => {
197
+ if ( data . success ) {
198
+ resolve ( ) ;
199
+ } else {
200
+ reject ( ) ;
201
+ }
202
+ } ,
203
+ { once : true } ,
204
+ ) ;
205
+
206
+ this . _worker . addEventListener (
207
+ 'error' ,
208
+ error => {
209
+ reject ( error ) ;
210
+ } ,
211
+ { once : true } ,
212
+ ) ;
213
+ } ) ;
214
+ }
215
+
122
216
/**
123
217
* Destroy the event buffer.
124
218
*/
0 commit comments