1+ /* eslint-disable max-lines */
2+
13import * as fs from 'fs' ;
24import * as http from 'http' ;
35import type { AddressInfo } from 'net' ;
@@ -30,12 +32,22 @@ interface SentryRequestCallbackData {
3032 sentryResponseStatusCode ?: number ;
3133}
3234
35+ interface EventCallbackListener {
36+ ( data : string ) : void ;
37+ }
38+
3339type OnRequest = (
34- eventCallbackListeners : Set < ( data : string ) => void > ,
40+ eventCallbackListeners : Set < EventCallbackListener > ,
3541 proxyRequest : http . IncomingMessage ,
3642 proxyRequestBody : string ,
43+ eventBuffer : BufferedEvent [ ] ,
3744) => Promise < [ number , string , Record < string , string > | undefined ] > ;
3845
46+ interface BufferedEvent {
47+ timestamp : number ;
48+ data : string ;
49+ }
50+
3951/**
4052 * Start a generic proxy server.
4153 * The `onRequest` callback receives the incoming request and the request body,
@@ -51,7 +63,8 @@ export async function startProxyServer(
5163 } ,
5264 onRequest ?: OnRequest ,
5365) : Promise < void > {
54- const eventCallbackListeners : Set < ( data : string ) => void > = new Set ( ) ;
66+ const eventBuffer : BufferedEvent [ ] = [ ] ;
67+ const eventCallbackListeners : Set < EventCallbackListener > = new Set ( ) ;
5568
5669 const proxyServer = http . createServer ( ( proxyRequest , proxyResponse ) => {
5770 const proxyRequestChunks : Uint8Array [ ] = [ ] ;
@@ -76,15 +89,17 @@ export async function startProxyServer(
7689
7790 const callback : OnRequest =
7891 onRequest ||
79- ( async ( eventCallbackListeners , proxyRequest , proxyRequestBody ) => {
92+ ( async ( eventCallbackListeners , proxyRequest , proxyRequestBody , eventBuffer ) => {
93+ eventBuffer . push ( { data : proxyRequestBody , timestamp : Date . now ( ) } ) ;
94+
8095 eventCallbackListeners . forEach ( listener => {
8196 listener ( proxyRequestBody ) ;
8297 } ) ;
8398
8499 return [ 200 , '{}' , { } ] ;
85100 } ) ;
86101
87- callback ( eventCallbackListeners , proxyRequest , proxyRequestBody )
102+ callback ( eventCallbackListeners , proxyRequest , proxyRequestBody , eventBuffer )
88103 . then ( ( [ statusCode , responseBody , responseHeaders ] ) => {
89104 proxyResponse . writeHead ( statusCode , responseHeaders ) ;
90105 proxyResponse . write ( responseBody , 'utf-8' ) ;
@@ -110,12 +125,24 @@ export async function startProxyServer(
110125 eventCallbackResponse . statusCode = 200 ;
111126 eventCallbackResponse . setHeader ( 'connection' , 'keep-alive' ) ;
112127
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+
113134 const callbackListener = ( data : string ) : void => {
114135 eventCallbackResponse . write ( data . concat ( '\n' ) , 'utf8' ) ;
115136 } ;
116137
117138 eventCallbackListeners . add ( callbackListener ) ;
118139
140+ eventBuffer . forEach ( bufferedEvent => {
141+ if ( bufferedEvent . timestamp >= listenerTimestamp ) {
142+ callbackListener ( bufferedEvent . data ) ;
143+ }
144+ } ) ;
145+
119146 eventCallbackRequest . on ( 'close' , ( ) => {
120147 eventCallbackListeners . delete ( callbackListener ) ;
121148 } ) ;
@@ -142,7 +169,7 @@ export async function startProxyServer(
142169 * option to this server (like this `tunnel: http://localhost:${port option}/`).
143170 */
144171export async function startEventProxyServer ( options : EventProxyServerOptions ) : Promise < void > {
145- await startProxyServer ( options , async ( eventCallbackListeners , proxyRequest , proxyRequestBody ) => {
172+ await startProxyServer ( options , async ( eventCallbackListeners , proxyRequest , proxyRequestBody , eventBuffer ) => {
146173 const envelopeHeader : EnvelopeItem [ 0 ] = JSON . parse ( proxyRequestBody . split ( '\n' ) [ 0 ] as string ) ;
147174
148175 const shouldForwardEventToSentry = options . forwardToSentry != null ? options . forwardToSentry : true ;
@@ -199,8 +226,12 @@ export async function startEventProxyServer(options: EventProxyServerOptions): P
199226 sentryResponseStatusCode : res . status ,
200227 } ;
201228
229+ const dataString = Buffer . from ( JSON . stringify ( data ) ) . toString ( 'base64' ) ;
230+
231+ eventBuffer . push ( { data : dataString , timestamp : Date . now ( ) } ) ;
232+
202233 eventCallbackListeners . forEach ( listener => {
203- listener ( Buffer . from ( JSON . stringify ( data ) ) . toString ( 'base64' ) ) ;
234+ listener ( dataString ) ;
204235 } ) ;
205236
206237 const resHeaders : Record < string , string > = { } ;
@@ -221,24 +252,28 @@ export async function waitForPlainRequest(
221252 const eventCallbackServerPort = await retrieveCallbackServerPort ( proxyServerName ) ;
222253
223254 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+ } ) ;
230264
231- response . on ( 'data' , ( chunk : Buffer ) => {
232- const chunkString = chunk . toString ( 'utf8' ) ;
265+ response . on ( 'data' , ( chunk : Buffer ) => {
266+ const chunkString = chunk . toString ( 'utf8' ) ;
233267
234- eventContents = eventContents . concat ( chunkString ) ;
268+ eventContents = eventContents . concat ( chunkString ) ;
235269
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+ ) ;
242277
243278 request . end ( ) ;
244279 } ) ;
@@ -248,48 +283,53 @@ export async function waitForPlainRequest(
248283export async function waitForRequest (
249284 proxyServerName : string ,
250285 callback : ( eventData : SentryRequestCallbackData ) => Promise < boolean > | boolean ,
286+ timestamp : number = Date . now ( ) ,
251287) : Promise < SentryRequestCallbackData > {
252288 const eventCallbackServerPort = await retrieveCallbackServerPort ( proxyServerName ) ;
253289
254290 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+ } ) ;
261300
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' ) ,
281307 ) ;
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 ) ;
285328 }
286- eventContents = '' ;
287- } else {
288- eventContents = eventContents . concat ( char ) ;
289- }
329+ } ) ;
290330 } ) ;
291- } ) ;
292- } ) ;
331+ } ,
332+ ) ;
293333
294334 request . end ( ) ;
295335 } ) ;
@@ -299,18 +339,23 @@ export async function waitForRequest(
299339export function waitForEnvelopeItem (
300340 proxyServerName : string ,
301341 callback : ( envelopeItem : EnvelopeItem ) => Promise < boolean > | boolean ,
342+ timestamp : number = Date . now ( ) ,
302343) : Promise < EnvelopeItem > {
303344 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+ }
310354 }
311- }
312- return false ;
313- } ) . catch ( reject ) ;
355+ return false ;
356+ } ,
357+ timestamp ,
358+ ) . catch ( reject ) ;
314359 } ) ;
315360}
316361
@@ -319,15 +364,20 @@ export function waitForError(
319364 proxyServerName : string ,
320365 callback : ( transactionEvent : Event ) => Promise < boolean > | boolean ,
321366) : Promise < Event > {
367+ const timestamp = Date . now ( ) ;
322368 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 ) ;
331381 } ) ;
332382}
333383
@@ -336,15 +386,20 @@ export function waitForTransaction(
336386 proxyServerName : string ,
337387 callback : ( transactionEvent : Event ) => Promise < boolean > | boolean ,
338388) : Promise < Event > {
389+ const timestamp = Date . now ( ) ;
339390 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 ) ;
348403 } ) ;
349404}
350405
0 commit comments