1
+ /* eslint-disable max-lines */
2
+
1
3
import * as fs from 'fs' ;
2
4
import * as http from 'http' ;
3
5
import type { AddressInfo } from 'net' ;
@@ -30,12 +32,22 @@ interface SentryRequestCallbackData {
30
32
sentryResponseStatusCode ?: number ;
31
33
}
32
34
35
+ interface EventCallbackListener {
36
+ ( data : string ) : void ;
37
+ }
38
+
33
39
type OnRequest = (
34
- eventCallbackListeners : Set < ( data : string ) => void > ,
40
+ eventCallbackListeners : Set < EventCallbackListener > ,
35
41
proxyRequest : http . IncomingMessage ,
36
42
proxyRequestBody : string ,
43
+ eventBuffer : BufferedEvent [ ] ,
37
44
) => Promise < [ number , string , Record < string , string > | undefined ] > ;
38
45
46
+ interface BufferedEvent {
47
+ timestamp : number ;
48
+ data : string ;
49
+ }
50
+
39
51
/**
40
52
* Start a generic proxy server.
41
53
* The `onRequest` callback receives the incoming request and the request body,
@@ -51,7 +63,8 @@ export async function startProxyServer(
51
63
} ,
52
64
onRequest ?: OnRequest ,
53
65
) : Promise < void > {
54
- const eventCallbackListeners : Set < ( data : string ) => void > = new Set ( ) ;
66
+ const eventBuffer : BufferedEvent [ ] = [ ] ;
67
+ const eventCallbackListeners : Set < EventCallbackListener > = new Set ( ) ;
55
68
56
69
const proxyServer = http . createServer ( ( proxyRequest , proxyResponse ) => {
57
70
const proxyRequestChunks : Uint8Array [ ] = [ ] ;
@@ -76,15 +89,17 @@ export async function startProxyServer(
76
89
77
90
const callback : OnRequest =
78
91
onRequest ||
79
- ( async ( eventCallbackListeners , proxyRequest , proxyRequestBody ) => {
92
+ ( async ( eventCallbackListeners , proxyRequest , proxyRequestBody , eventBuffer ) => {
93
+ eventBuffer . push ( { data : proxyRequestBody , timestamp : Date . now ( ) } ) ;
94
+
80
95
eventCallbackListeners . forEach ( listener => {
81
96
listener ( proxyRequestBody ) ;
82
97
} ) ;
83
98
84
99
return [ 200 , '{}' , { } ] ;
85
100
} ) ;
86
101
87
- callback ( eventCallbackListeners , proxyRequest , proxyRequestBody )
102
+ callback ( eventCallbackListeners , proxyRequest , proxyRequestBody , eventBuffer )
88
103
. then ( ( [ statusCode , responseBody , responseHeaders ] ) => {
89
104
proxyResponse . writeHead ( statusCode , responseHeaders ) ;
90
105
proxyResponse . write ( responseBody , 'utf-8' ) ;
@@ -110,12 +125,24 @@ export async function startProxyServer(
110
125
eventCallbackResponse . statusCode = 200 ;
111
126
eventCallbackResponse . setHeader ( 'connection' , 'keep-alive' ) ;
112
127
128
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
129
+ const searchParams = new URL ( eventCallbackRequest . url ! , 'http://justsomerandombasesothattheurlisparseable.com/' )
130
+ . searchParams ;
131
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
132
+ const listenerTimestamp = Number ( searchParams . get ( 'timestamp' ) ! ) ;
133
+
113
134
const callbackListener = ( data : string ) : void => {
114
135
eventCallbackResponse . write ( data . concat ( '\n' ) , 'utf8' ) ;
115
136
} ;
116
137
117
138
eventCallbackListeners . add ( callbackListener ) ;
118
139
140
+ eventBuffer . forEach ( bufferedEvent => {
141
+ if ( bufferedEvent . timestamp >= listenerTimestamp ) {
142
+ callbackListener ( bufferedEvent . data ) ;
143
+ }
144
+ } ) ;
145
+
119
146
eventCallbackRequest . on ( 'close' , ( ) => {
120
147
eventCallbackListeners . delete ( callbackListener ) ;
121
148
} ) ;
@@ -142,7 +169,7 @@ export async function startProxyServer(
142
169
* option to this server (like this `tunnel: http://localhost:${port option}/`).
143
170
*/
144
171
export async function startEventProxyServer ( options : EventProxyServerOptions ) : Promise < void > {
145
- await startProxyServer ( options , async ( eventCallbackListeners , proxyRequest , proxyRequestBody ) => {
172
+ await startProxyServer ( options , async ( eventCallbackListeners , proxyRequest , proxyRequestBody , eventBuffer ) => {
146
173
const envelopeHeader : EnvelopeItem [ 0 ] = JSON . parse ( proxyRequestBody . split ( '\n' ) [ 0 ] as string ) ;
147
174
148
175
const shouldForwardEventToSentry = options . forwardToSentry != null ? options . forwardToSentry : true ;
@@ -199,8 +226,12 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P
199
226
sentryResponseStatusCode : res . status ,
200
227
} ;
201
228
229
+ const dataString = Buffer . from ( JSON . stringify ( data ) ) . toString ( 'base64' ) ;
230
+
231
+ eventBuffer . push ( { data : dataString , timestamp : Date . now ( ) } ) ;
232
+
202
233
eventCallbackListeners . forEach ( listener => {
203
- listener ( Buffer . from ( JSON . stringify ( data ) ) . toString ( 'base64' ) ) ;
234
+ listener ( dataString ) ;
204
235
} ) ;
205
236
206
237
const resHeaders : Record < string , string > = { } ;
@@ -221,24 +252,28 @@ export async function waitForPlainRequest(
221
252
const eventCallbackServerPort = await retrieveCallbackServerPort ( proxyServerName ) ;
222
253
223
254
return new Promise ( ( resolve , reject ) => {
224
- const request = http . request ( `http://localhost:${ eventCallbackServerPort } /` , { } , response => {
225
- let eventContents = '' ;
226
-
227
- response . on ( 'error' , err => {
228
- reject ( err ) ;
229
- } ) ;
255
+ const request = http . request (
256
+ `http://localhost:${ eventCallbackServerPort } /?timestamp=${ Date . now ( ) } ` ,
257
+ { } ,
258
+ response => {
259
+ let eventContents = '' ;
260
+
261
+ response . on ( 'error' , err => {
262
+ reject ( err ) ;
263
+ } ) ;
230
264
231
- response . on ( 'data' , ( chunk : Buffer ) => {
232
- const chunkString = chunk . toString ( 'utf8' ) ;
265
+ response . on ( 'data' , ( chunk : Buffer ) => {
266
+ const chunkString = chunk . toString ( 'utf8' ) ;
233
267
234
- eventContents = eventContents . concat ( chunkString ) ;
268
+ eventContents = eventContents . concat ( chunkString ) ;
235
269
236
- if ( callback ( eventContents ) ) {
237
- response . destroy ( ) ;
238
- return resolve ( eventContents ) ;
239
- }
240
- } ) ;
241
- } ) ;
270
+ if ( callback ( eventContents ) ) {
271
+ response . destroy ( ) ;
272
+ return resolve ( eventContents ) ;
273
+ }
274
+ } ) ;
275
+ } ,
276
+ ) ;
242
277
243
278
request . end ( ) ;
244
279
} ) ;
@@ -248,48 +283,53 @@ export async function waitForPlainRequest(
248
283
export async function waitForRequest (
249
284
proxyServerName : string ,
250
285
callback : ( eventData : SentryRequestCallbackData ) => Promise < boolean > | boolean ,
286
+ timestamp : number = Date . now ( ) ,
251
287
) : Promise < SentryRequestCallbackData > {
252
288
const eventCallbackServerPort = await retrieveCallbackServerPort ( proxyServerName ) ;
253
289
254
290
return new Promise < SentryRequestCallbackData > ( ( resolve , reject ) => {
255
- const request = http . request ( `http://localhost:${ eventCallbackServerPort } /` , { } , response => {
256
- let eventContents = '' ;
257
-
258
- response . on ( 'error' , err => {
259
- reject ( err ) ;
260
- } ) ;
291
+ const request = http . request (
292
+ `http://localhost:${ eventCallbackServerPort } /?timestamp=${ timestamp } ` ,
293
+ { } ,
294
+ response => {
295
+ let eventContents = '' ;
296
+
297
+ response . on ( 'error' , err => {
298
+ reject ( err ) ;
299
+ } ) ;
261
300
262
- response . on ( 'data' , ( chunk : Buffer ) => {
263
- const chunkString = chunk . toString ( 'utf8' ) ;
264
- chunkString . split ( '' ) . forEach ( char => {
265
- if ( char === '\n' ) {
266
- const eventCallbackData : SentryRequestCallbackData = JSON . parse (
267
- Buffer . from ( eventContents , 'base64' ) . toString ( 'utf8' ) ,
268
- ) ;
269
- const callbackResult = callback ( eventCallbackData ) ;
270
- if ( typeof callbackResult !== 'boolean' ) {
271
- callbackResult . then (
272
- match => {
273
- if ( match ) {
274
- response . destroy ( ) ;
275
- resolve ( eventCallbackData ) ;
276
- }
277
- } ,
278
- err => {
279
- throw err ;
280
- } ,
301
+ response . on ( 'data' , ( chunk : Buffer ) => {
302
+ const chunkString = chunk . toString ( 'utf8' ) ;
303
+ chunkString . split ( '' ) . forEach ( char => {
304
+ if ( char === '\n' ) {
305
+ const eventCallbackData : SentryRequestCallbackData = JSON . parse (
306
+ Buffer . from ( eventContents , 'base64' ) . toString ( 'utf8' ) ,
281
307
) ;
282
- } else if ( callbackResult ) {
283
- response . destroy ( ) ;
284
- resolve ( eventCallbackData ) ;
308
+ const callbackResult = callback ( eventCallbackData ) ;
309
+ if ( typeof callbackResult !== 'boolean' ) {
310
+ callbackResult . then (
311
+ match => {
312
+ if ( match ) {
313
+ response . destroy ( ) ;
314
+ resolve ( eventCallbackData ) ;
315
+ }
316
+ } ,
317
+ err => {
318
+ throw err ;
319
+ } ,
320
+ ) ;
321
+ } else if ( callbackResult ) {
322
+ response . destroy ( ) ;
323
+ resolve ( eventCallbackData ) ;
324
+ }
325
+ eventContents = '' ;
326
+ } else {
327
+ eventContents = eventContents . concat ( char ) ;
285
328
}
286
- eventContents = '' ;
287
- } else {
288
- eventContents = eventContents . concat ( char ) ;
289
- }
329
+ } ) ;
290
330
} ) ;
291
- } ) ;
292
- } ) ;
331
+ } ,
332
+ ) ;
293
333
294
334
request . end ( ) ;
295
335
} ) ;
@@ -299,18 +339,23 @@ export async function waitForRequest(
299
339
export function waitForEnvelopeItem (
300
340
proxyServerName : string ,
301
341
callback : ( envelopeItem : EnvelopeItem ) => Promise < boolean > | boolean ,
342
+ timestamp : number = Date . now ( ) ,
302
343
) : Promise < EnvelopeItem > {
303
344
return new Promise ( ( resolve , reject ) => {
304
- waitForRequest ( proxyServerName , async eventData => {
305
- const envelopeItems = eventData . envelope [ 1 ] ;
306
- for ( const envelopeItem of envelopeItems ) {
307
- if ( await callback ( envelopeItem ) ) {
308
- resolve ( envelopeItem ) ;
309
- return true ;
345
+ waitForRequest (
346
+ proxyServerName ,
347
+ async eventData => {
348
+ const envelopeItems = eventData . envelope [ 1 ] ;
349
+ for ( const envelopeItem of envelopeItems ) {
350
+ if ( await callback ( envelopeItem ) ) {
351
+ resolve ( envelopeItem ) ;
352
+ return true ;
353
+ }
310
354
}
311
- }
312
- return false ;
313
- } ) . catch ( reject ) ;
355
+ return false ;
356
+ } ,
357
+ timestamp ,
358
+ ) . catch ( reject ) ;
314
359
} ) ;
315
360
}
316
361
@@ -319,15 +364,20 @@ export function waitForError(
319
364
proxyServerName : string ,
320
365
callback : ( transactionEvent : Event ) => Promise < boolean > | boolean ,
321
366
) : Promise < Event > {
367
+ const timestamp = Date . now ( ) ;
322
368
return new Promise ( ( resolve , reject ) => {
323
- waitForEnvelopeItem ( proxyServerName , async envelopeItem => {
324
- const [ envelopeItemHeader , envelopeItemBody ] = envelopeItem ;
325
- if ( envelopeItemHeader . type === 'event' && ( await callback ( envelopeItemBody as Event ) ) ) {
326
- resolve ( envelopeItemBody as Event ) ;
327
- return true ;
328
- }
329
- return false ;
330
- } ) . catch ( reject ) ;
369
+ waitForEnvelopeItem (
370
+ proxyServerName ,
371
+ async envelopeItem => {
372
+ const [ envelopeItemHeader , envelopeItemBody ] = envelopeItem ;
373
+ if ( envelopeItemHeader . type === 'event' && ( await callback ( envelopeItemBody as Event ) ) ) {
374
+ resolve ( envelopeItemBody as Event ) ;
375
+ return true ;
376
+ }
377
+ return false ;
378
+ } ,
379
+ timestamp ,
380
+ ) . catch ( reject ) ;
331
381
} ) ;
332
382
}
333
383
@@ -336,15 +386,20 @@ export function waitForTransaction(
336
386
proxyServerName : string ,
337
387
callback : ( transactionEvent : Event ) => Promise < boolean > | boolean ,
338
388
) : Promise < Event > {
389
+ const timestamp = Date . now ( ) ;
339
390
return new Promise ( ( resolve , reject ) => {
340
- waitForEnvelopeItem ( proxyServerName , async envelopeItem => {
341
- const [ envelopeItemHeader , envelopeItemBody ] = envelopeItem ;
342
- if ( envelopeItemHeader . type === 'transaction' && ( await callback ( envelopeItemBody as Event ) ) ) {
343
- resolve ( envelopeItemBody as Event ) ;
344
- return true ;
345
- }
346
- return false ;
347
- } ) . catch ( reject ) ;
391
+ waitForEnvelopeItem (
392
+ proxyServerName ,
393
+ async envelopeItem => {
394
+ const [ envelopeItemHeader , envelopeItemBody ] = envelopeItem ;
395
+ if ( envelopeItemHeader . type === 'transaction' && ( await callback ( envelopeItemBody as Event ) ) ) {
396
+ resolve ( envelopeItemBody as Event ) ;
397
+ return true ;
398
+ }
399
+ return false ;
400
+ } ,
401
+ timestamp ,
402
+ ) . catch ( reject ) ;
348
403
} ) ;
349
404
}
350
405
0 commit comments