@@ -141,22 +141,26 @@ async function findFreePort(startPort: number, endPort: number): Promise<number>
141141 for ( let port = startPort ; port <= endPort ; port ++ ) {
142142 try {
143143 for ( const host of hosts ) {
144- await new Promise < void > ( ( resolve , reject ) => {
145- const server = net . createServer ( ) ;
146- server . unref ( ) ;
147- server . once ( 'error' , reject ) ;
148- server . once ( 'listening' , ( ) => server . close ( ( ) => resolve ( ) ) ) ;
149- if ( host !== undefined ) {
150- server . listen ( port , host ) ;
151- } else {
152- server . listen ( port ) ;
153- }
154- } ) ;
144+ try {
145+ await new Promise < void > ( ( resolve , reject ) => {
146+ const server = net . createServer ( ) ;
147+ server . unref ( ) ;
148+ server . once ( 'error' , reject ) ;
149+ server . once ( 'listening' , ( ) => server . close ( ( ) => resolve ( ) ) ) ;
150+ if ( host !== undefined ) {
151+ server . listen ( port , host ) ;
152+ } else {
153+ server . listen ( port ) ;
154+ }
155+ } ) ;
156+ } catch ( e : any ) {
157+ if ( e . code === 'EADDRNOTAVAIL' || e . code === 'EINVAL' ) continue ;
158+ throw e ; // EADDRINUSE/EACCES or other → port unavailable
159+ }
155160 }
156161 return port ;
157162 } catch ( e : any ) {
158- if ( e . code !== 'EADDRINUSE' && e . code !== 'EACCES' &&
159- e . code !== 'EADDRNOTAVAIL' && e . code !== 'EINVAL' ) throw e ;
163+ if ( e . code !== 'EADDRINUSE' && e . code !== 'EACCES' ) throw e ;
160164 }
161165 }
162166 throw new Error ( `No open ports between ${ startPort } and ${ endPort } ` ) ;
@@ -750,14 +754,18 @@ export class MockttpServer extends AbstractMockttp implements Mockttp {
750754 const request = this . preprocessRequest ( rawRequest , 'request' ) ;
751755 if ( request === null ) return ; // Preprocessing failed - don't handle this
752756
757+ // Capture the emitter at the start of request handling, so that all events for this
758+ // request are emitted to the same emitter, even if the server is reset mid-request.
759+ const emitter = this . eventEmitter ;
760+
753761 if ( this . debug ) console . log ( `Handling request for ${ rawRequest . url } ` ) ;
754762
755763 let result : 'responded' | 'aborted' | null = null ;
756764 const abort = ( error ?: Error ) => {
757765 if ( result === null ) {
758766 result = 'aborted' ;
759767 request . timingEvents . abortedTimestamp = now ( ) ;
760- this . announceAbortAsync ( request , error ) ;
768+ this . announceAbortAsync ( request ) ;
761769 }
762770 }
763771 request . once ( 'aborted' , abort ) ;
@@ -774,13 +782,29 @@ export class MockttpServer extends AbstractMockttp implements Mockttp {
774782 request . tags ,
775783 {
776784 maxSize : this . maxBodySize ,
777- onWriteHead : ( ) => this . announceInitialResponseAsync ( response ) ,
778- onBodyData : this . eventEmitter . listenerCount ( 'response-body-data' ) > 0
779- ? this . announceBodyDataAsync . bind ( this , 'response' )
785+ onWriteHead : ( ) => {
786+ if ( emitter . listenerCount ( 'response-initiated' ) === 0 ) return ;
787+ setImmediate ( ( ) => {
788+ const initiatedRes = buildInitiatedResponse ( response ) ;
789+ emitter . emit ( 'response-initiated' , Object . assign (
790+ initiatedRes ,
791+ {
792+ timingEvents : _ . clone ( initiatedRes . timingEvents ) ,
793+ tags : _ . clone ( initiatedRes . tags )
794+ }
795+ ) ) ;
796+ } ) ;
797+ } ,
798+ onBodyData : emitter . listenerCount ( 'response-body-data' ) > 0
799+ ? ( id : string , eventTimestamp : number , content : Uint8Array , isEnded : boolean ) => {
800+ setImmediate ( ( ) => {
801+ emitter . emit ( 'response-body-data' , { id, content, isEnded, eventTimestamp } ) ;
802+ } ) ;
803+ }
780804 : undefined
781805 }
782806 ) ;
783- const hasResponseListener = this . eventEmitter . listenerCount ( 'response' ) > 0 ;
807+ const hasResponseListener = emitter . listenerCount ( 'response' ) > 0 ;
784808 if ( hasResponseListener ) {
785809 // Start buffering response body if there's somebody who
786810 // might want to hear about it later
@@ -812,7 +836,7 @@ export class MockttpServer extends AbstractMockttp implements Mockttp {
812836 record : this . recordTraffic ,
813837 debug : this . debug ,
814838 keyLogStream : this . keyLogStream ,
815- emitEventCallback : ( this . eventEmitter . listenerCount ( 'rule-event' ) !== 0 )
839+ emitEventCallback : ( emitter . listenerCount ( 'rule-event' ) !== 0 )
816840 ? ( type , event ) => this . announceRuleEventAsync ( request . id , nextRule ! . id , type , event )
817841 : undefined
818842 } ) ;
@@ -858,7 +882,17 @@ export class MockttpServer extends AbstractMockttp implements Mockttp {
858882 }
859883
860884 if ( result === 'responded' && hasResponseListener ) {
861- this . announceResponseAsync ( response ) ;
885+ // Use captured emitter for response announcement too
886+ waitForCompletedResponse ( response )
887+ . then ( ( res : CompletedResponse ) => {
888+ setImmediate ( ( ) => {
889+ emitter . emit ( 'response' , Object . assign ( res , {
890+ timingEvents : _ . clone ( res . timingEvents ) ,
891+ tags : _ . clone ( res . tags )
892+ } ) ) ;
893+ } ) ;
894+ } )
895+ . catch ( console . error ) ;
862896 }
863897 }
864898
0 commit comments