@@ -6,6 +6,7 @@ import IResolvablePromise from '@secret-agent/core-interfaces/IResolvablePromise
66import Log from '@secret-agent/commons/Logger' ;
77import ICreateSessionOptions from '@secret-agent/core-interfaces/ICreateSessionOptions' ;
88import ISessionMeta from '@secret-agent/core-interfaces/ISessionMeta' ;
9+ import { CanceledPromiseError } from '@secret-agent/commons/interfaces/IPendingWaitEvent' ;
910import IConnectionToCoreOptions from '../interfaces/IConnectionToCoreOptions' ;
1011import CoreCommandQueue from '../lib/CoreCommandQueue' ;
1112import CoreSession from '../lib/CoreSession' ;
@@ -17,11 +18,11 @@ const { log } = Log(module);
1718
1819export default abstract class ConnectionToCore {
1920 public readonly commandQueue : CoreCommandQueue ;
21+ public readonly hostOrError : Promise < string | Error > ;
2022 public options : IConnectionToCoreOptions ;
2123
22- public hostOrError : Promise < string | Error > ;
23-
2424 private connectPromise : Promise < Error | null > ;
25+ private isClosing = false ;
2526
2627 private coreSessions : CoreSessions ;
2728 private readonly pendingRequestsById = new Map < string , IResolvablePromiseWithId > ( ) ;
@@ -34,31 +35,57 @@ export default abstract class ConnectionToCore {
3435 this . options . maxConcurrency ,
3536 this . options . agentTimeoutMillis ,
3637 ) ;
38+
39+ if ( this . options . host ) {
40+ this . hostOrError = Promise . resolve ( this . options . host )
41+ . then ( x => {
42+ if ( ! x . includes ( '://' ) ) {
43+ return `ws://${ x } ` ;
44+ }
45+ return x ;
46+ } )
47+ . catch ( err => err ) ;
48+ } else {
49+ this . hostOrError = Promise . resolve ( new Error ( 'No host provided' ) ) ;
50+ }
3751 }
3852
39- protected abstract internalSendRequest ( payload : ICoreRequestPayload ) : void | Promise < void > ;
53+ protected abstract internalSendRequest ( payload : ICoreRequestPayload ) : Promise < void > ;
54+ protected abstract createConnection ( ) : Promise < Error | null > ;
55+ protected abstract destroyConnection ( ) : Promise < any > ;
4056
4157 public connect ( ) : Promise < Error | null > {
42- if ( this . connectPromise ) return this . connectPromise ;
43-
44- const { promise, id } = this . createPendingResult ( ) ;
45- this . connectPromise = promise . then ( x => this . onConnected ( x . data ) ) . catch ( err => err ) ;
46- this . internalSendRequest ( {
47- command : 'connect' ,
48- args : [ this . options ] ,
49- messageId : id ,
50- } ) ;
58+ this . connectPromise ??= this . createConnection ( )
59+ . then ( err => {
60+ if ( err ) throw err ;
61+ return this . internalSendRequestAndWait ( {
62+ command : 'connect' ,
63+ args : [ this . options ] ,
64+ } ) ;
65+ } )
66+ . then ( result => this . onConnected ( result . data ) )
67+ . catch ( err => err ) ;
5168
5269 return this . connectPromise ;
5370 }
5471
5572 public async disconnect ( fatalError ?: Error ) : Promise < void > {
56- this . commandQueue . clearPending ( ) ;
57- this . coreSessions . close ( ) ;
58- if ( this . connectPromise || fatalError ) {
59- await this . commandQueue . run ( 'disconnect' , [ fatalError ] ) ;
60- this . connectPromise = null ;
73+ if ( this . isClosing ) return ;
74+ this . isClosing = true ;
75+ const logid = log . stats ( 'ConnectionToCore.Disconnecting' ) ;
76+
77+ this . cancelPendingRequests ( ) ;
78+ if ( this . connectPromise ) {
79+ await this . internalSendRequestAndWait ( {
80+ command : 'disconnect' ,
81+ args : [ fatalError ] ,
82+ } ) ;
6183 }
84+ await this . destroyConnection ( ) ;
85+ log . stats ( 'RemoteConnectionToCore.Disconnected' , {
86+ parentLogId : logid ,
87+ sessionId : null ,
88+ } ) ;
6289 }
6390
6491 /////// PIPE FUNCTIONS /////////////////////////////////////////////////////////////////////////////////////////////
@@ -69,12 +96,7 @@ export default abstract class ConnectionToCore {
6996 const result = await this . connect ( ) ;
7097 if ( result ) throw result ;
7198
72- const { promise, id } = this . createPendingResult ( ) ;
73- await this . internalSendRequest ( {
74- messageId : id ,
75- ...payload ,
76- } ) ;
77- return promise ;
99+ return this . internalSendRequestAndWait ( payload ) ;
78100 }
79101
80102 public onMessage ( payload : ICoreResponsePayload | ICoreEventPayload ) : void {
@@ -122,6 +144,25 @@ export default abstract class ConnectionToCore {
122144 await this . commandQueue . run ( 'logUnhandledError' , error ) ;
123145 }
124146
147+ protected async internalSendRequestAndWait (
148+ payload : Omit < ICoreRequestPayload , 'messageId' > ,
149+ ) : Promise < ICoreResponsePayload > {
150+ const { promise, id } = this . createPendingResult ( ) ;
151+ try {
152+ await this . internalSendRequest ( {
153+ messageId : id ,
154+ ...payload ,
155+ } ) ;
156+ } catch ( error ) {
157+ if ( error instanceof CanceledPromiseError ) {
158+ this . pendingRequestsById . delete ( id ) ;
159+ return ;
160+ }
161+ throw error ;
162+ }
163+ return promise ;
164+ }
165+
125166 protected onEvent ( payload : ICoreEventPayload ) : void {
126167 const { meta, listenerId, eventArgs } = payload as ICoreEventPayload ;
127168 const session = this . getSession ( meta . sessionId ) ;
@@ -136,13 +177,22 @@ export default abstract class ConnectionToCore {
136177 if ( message . isError ) {
137178 const error = new Error ( message . data ?. message ) ;
138179 Object . assign ( error , message . data ) ;
139- error . stack += `\n${ '------CONNECTION' . padEnd ( 50 , '-' ) } \n${ pending . stack } ` ;
140- pending . reject ( error ) ;
180+ this . rejectPendingRequest ( pending , error ) ;
141181 } else {
142182 pending . resolve ( { data : message . data , commandId : message . commandId } ) ;
143183 }
144184 }
145185
186+ protected cancelPendingRequests ( ) : void {
187+ this . commandQueue . clearPending ( ) ;
188+ this . coreSessions . close ( ) ;
189+ const pending = [ ...this . pendingRequestsById . values ( ) ] ;
190+ this . pendingRequestsById . clear ( ) ;
191+ for ( const entry of pending ) {
192+ this . rejectPendingRequest ( entry , new CanceledPromiseError ( 'Disconnecting from Core' ) ) ;
193+ }
194+ }
195+
146196 private createPendingResult ( ) : IResolvablePromiseWithId {
147197 const resolvablePromise = createPromise < ICoreResponsePayload > ( ) as IResolvablePromiseWithId ;
148198 this . lastId += 1 ;
@@ -152,9 +202,15 @@ export default abstract class ConnectionToCore {
152202 return this . pendingRequestsById . get ( id ) ;
153203 }
154204
205+ private rejectPendingRequest ( pending : IResolvablePromiseWithId , error : Error ) : void {
206+ error . stack += `\n${ '------CONNECTION' . padEnd ( 50 , '-' ) } \n${ pending . stack } ` ;
207+ pending . reject ( error ) ;
208+ }
209+
155210 private onConnected (
156211 connectionParams : { maxConcurrency ?: number ; browserEmulatorIds ?: string [ ] } = { } ,
157212 ) : void {
213+ this . isClosing = false ;
158214 const { maxConcurrency, browserEmulatorIds } = connectionParams ;
159215 if ( ! this . options . maxConcurrency || maxConcurrency < this . options . maxConcurrency ) {
160216 log . info ( 'Overriding max concurrency with Core value' , {
0 commit comments