import { Connection , Server , WebSocketTransport } from "vscode-cdp-proxy" ; import * as semver from "semver" ; import { IncomingMessage } from "http" ; import { OutputChannelLogger } from "../../utils/log/outputChannelLogger" ; import { DebuggerEndpointHelper } from "./debuggerEndpointHelper" ; import { LogLevel } from "../../utils/log/logHelper" ; import { CancellationToken } from "vscode" ; import { SourcemapPathTransformer } from "./sourcemapPathTransformer" ; import { PlatformType } from "../cordovaDebugSession" ; import { IProjectType } from "../../utils/cordovaProjectHelper" ; import { SimulateHelper } from "../../utils/simulateHelper" ; import { CDPMessageHandlerBase , DispatchDirection } from "./CDPMessageHandlers/abstraction/CDPMessageHandlerBase" ; import { CDPMessageHandlerCreator } from "./CDPMessageHandlers/CDPMessageHandlerCreator" ; import { ICordovaAttachRequestArgs } from "../requestArgs" ; import { TargetType } from "../cordovaDebugSession" ; export class CordovaCDPProxy { private readonly PROXY_LOG_TAGS = { DEBUGGER_COMMAND : "Command Debugger To Target" , APPLICATION_COMMAND : "Command Target To Debugger" , DEBUGGER_REPLY : "Reply From Debugger To Target" , APPLICATION_REPLY : "Reply From Target To Debugger" , } ; private server : Server | null ; private hostAddress : string ; private port : number ; private debuggerTarget : Connection | null ; private applicationTarget : Connection | null ; private simPageTarget : Connection | null ; private logger : OutputChannelLogger ; private debuggerEndpointHelper : DebuggerEndpointHelper ; private applicationTargetPort : number ; private logLevel : LogLevel ; private cancellationToken : CancellationToken | undefined ; private CDPMessageHandler : CDPMessageHandlerBase ; private communicationPreparationsDone : boolean ; private browserInspectUri : string ; private isSimulate : boolean ; constructor ( hostAddress , port , sourcemapPathTransformer , projectType , args , logLevel = LogLevel . None ) { this . port = port ; this . hostAddress = hostAddress ; this . logLevel = logLevel ; this . logger = OutputChannelLogger . getChannel ( "Cordova Chrome Proxy" , true , false , true ) ; this . debuggerEndpointHelper = new DebuggerEndpointHelper ( ) ; this . browserInspectUri = args . webSocketDebuggerUrl || "" ; this . isSimulate = ! ! ( SimulateHelper . isSimulateTarget ( args . target ) && args . simulatePort ) ; if ( args . platform === PlatformType . IOS && ( args . target === TargetType . Emulator || args . target === TargetType . Device ) ) { this . CDPMessageHandler = CDPMessageHandlerCreator . create ( sourcemapPathTransformer , projectType , args , false ) ; this . communicationPreparationsDone = false ; } else { this . CDPMessageHandler = CDPMessageHandlerCreator . create ( sourcemapPathTransformer , projectType , args , true ) ; this . communicationPreparationsDone = true ; } } public createServer ( logLevel , cancellationToken ) { this . cancellationToken = cancellationToken ; this . logLevel = logLevel ; return Server . create ( { port : this . port , host : this . hostAddress } ) . then ( ( server ) => { this . server = server ; this . server . onConnection ( this . onConnectionHandler . bind ( this ) ) ; } ) ; } public async stopServer ( ) { if ( this . simPageTarget ) { await this . simPageTarget . close ( ) ; this . simPageTarget = null ; } if ( this . applicationTarget ) { await this . applicationTarget . close ( ) ; this . applicationTarget = null ; } if ( this . server ) { this . server . dispose ( ) ; this . server = null ; } this . browserInspectUri = "" ; this . cancellationToken = undefined ; } public setApplicationTargetPort ( applicationTargetPort ) { this . applicationTargetPort = applicationTargetPort ; } public setBrowserInspectUri ( browserInspectUri ) { this . browserInspectUri = browserInspectUri ; } public configureCDPMessageHandlerAccordingToProcessedAttachArgs ( args ) { if ( args . iOSVersion && ! this . communicationPreparationsDone && semver . lt ( args . iOSVersion , "12.2.0" ) ) { this . communicationPreparationsDone = true ; } this . CDPMessageHandler . configureHandlerAfterAttachmentPreparation ( CDPMessageHandlerCreator . generateHandlerOptions ( args ) ) ; } public getSimPageTargetAPI ( ) { return this . simPageTarget ?. api ; } private async onConnectionHandler ( [ debuggerTarget ] ) { this . debuggerTarget = debuggerTarget ; this . debuggerTarget . pause ( ) ; if ( ! this . browserInspectUri ) { if ( this . cancellationToken ) { this . browserInspectUri = await this . debuggerEndpointHelper . retryGetWSEndpoint ( `http://localhost:${ this . applicationTargetPort }` , 20 , this . cancellationToken ) ; } else { this . browserInspectUri = await this . debuggerEndpointHelper . getWSEndpoint ( `http://localhost:${ this . applicationTargetPort }` ) ; } } if ( this . isSimulate ) { const simPageInspectUri = await this . debuggerEndpointHelper . getWSEndpoint ( `http://localhost:${ this . applicationTargetPort }` , this . isSimulate ) ; this . simPageTarget = new Connection ( await WebSocketTransport . create ( simPageInspectUri ) ) ; } this . applicationTarget = new Connection ( await WebSocketTransport . create ( this . browserInspectUri ) ) ; this . applicationTarget . onError ( this . onApplicationTargetError . bind ( this ) ) ; this . debuggerTarget . onError ( this . onDebuggerTargetError . bind ( this ) ) ; this . applicationTarget . onCommand ( this . handleApplicationTargetCommand . bind ( this ) ) ; this . debuggerTarget . onCommand ( this . handleDebuggerTargetCommand . bind ( this ) ) ; this . applicationTarget . onReply ( this . handleApplicationTargetReply . bind ( this ) ) ; this . debuggerTarget . onReply ( this . handleDebuggerTargetReply . bind ( this ) ) ; this . applicationTarget . onEnd ( this . onApplicationTargetClosed . bind ( this ) ) ; this . debuggerTarget . onEnd ( this . onDebuggerTargetClosed . bind ( this ) ) ; this . CDPMessageHandler . setApplicationTarget ( this . applicationTarget ) ; this . CDPMessageHandler . setDebuggerTarget ( this . debuggerTarget ) ; this . unpauseDebuggerTarget ( ) ; } private handleDebuggerTargetCommand ( event ) { this . logger . logWithCustomTag ( this . PROXY_LOG_TAGS . DEBUGGER_COMMAND , JSON . stringify ( event , null , 2 ) , this . logLevel ) ; const processedMessage = this . CDPMessageHandler . processDebuggerCDPMessage ( event ) ; if ( processedMessage . dispatchDirection === DispatchDirection . BACK ) { this . debuggerTarget ?. send ( processedMessage . event ) ; } else if ( processedMessage . dispatchDirection === DispatchDirection . FORWARD ) { this . applicationTarget ?. send ( processedMessage . event ) ; } } private handleApplicationTargetCommand ( event ) { this . logger . logWithCustomTag ( this . PROXY_LOG_TAGS . APPLICATION_COMMAND , JSON . stringify ( event , null , 2 ) , this . logLevel ) ; const processedMessage = this . CDPMessageHandler . processApplicationCDPMessage ( event ) ; if ( processedMessage . communicationPreparationsDone ) { this . communicationPreparationsDone = true ; this . unpauseDebuggerTarget ( ) ; } if ( processedMessage . dispatchDirection === DispatchDirection . BACK ) { this . applicationTarget ?. send ( processedMessage . event ) ; } else if ( processedMessage . dispatchDirection === DispatchDirection . FORWARD ) { this . debuggerTarget ?. send ( processedMessage . event ) ; } } private handleDebuggerTargetReply ( event ) { this . logger . logWithCustomTag ( this . PROXY_LOG_TAGS . DEBUGGER_REPLY , JSON . stringify ( event , null , 2 ) , this . logLevel ) ; const processedMessage = this . CDPMessageHandler . processDebuggerCDPMessage ( event ) ; if ( processedMessage . dispatchDirection === DispatchDirection . BACK ) { this . debuggerTarget ?. send ( processedMessage . event ) ; } else if ( processedMessage . dispatchDirection === DispatchDirection . FORWARD ) { this . applicationTarget ?. send ( processedMessage . event ) ; } } private handleApplicationTargetReply ( event ) { this . logger . logWithCustomTag ( this . PROXY_LOG_TAGS . APPLICATION_REPLY , JSON . stringify ( event , null , 2 ) , this . logLevel ) ; const processedMessage = this . CDPMessageHandler . processApplicationCDPMessage ( event ) ; if ( processedMessage . dispatchDirection === DispatchDirection . BACK ) { this . applicationTarget ?. send ( processedMessage . event ) ; } else if ( processedMessage . dispatchDirection === DispatchDirection . FORWARD ) { this . debuggerTarget ?. send ( processedMessage . event ) ; } } private onDebuggerTargetError ( err ) { this . logger . log ( `Error on debugger transport: ${ err }` ) ; } private onApplicationTargetError ( err ) { this . logger . log ( `Error on application transport: ${ err }` ) ; } private async onApplicationTargetClosed ( ) { this . applicationTarget = null ; } private async onDebuggerTargetClosed ( ) { this . CDPMessageHandler . processDebuggerCDPMessage ( { method : "close" } ) ; this . debuggerTarget = null ; this . communicationPreparationsDone = false ; this . browserInspectUri = "" ; } private unpauseDebuggerTarget ( ) { if ( this . debuggerTarget && this . communicationPreparationsDone ) { this . debuggerTarget . unpause ( ) ; } } }