import { LikeSocket } from 'noice-json-rpc' ; import { logger } from 'vscode-debugadapter' ; function extractDomain ( method ) { const methodParts = method . split ( '.' ) ; if ( methodParts . length === 2 ) { return methodParts [ 0 ] ; } else { throwCriticalError ( `The method ${ method } didn't have exactly two parts` ) ; return 'Unknown' ; } } function encodifyChannel ( channelId , id ) { return id * 10 + channelId ; } function decodifyChannelId ( encodifiedId ) { return encodifiedId % 10 ; } function decodifyId ( encodifiedId ) { return Math . floor ( encodifiedId / 10 ) ; } function throwCriticalError ( message ) { logger . error ( 'CRDP Multiplexor - CRITICAL-ERROR: ' + message ) ; throw new Error ( message ) ; } export class CRDPMultiplexor { private _channels : CRDPChannel [ ] = [ ] ; private onMessage ( data ) { const message = JSON . parse ( data ) ; if ( message . id !== undefined ) { this . onResponseMessage ( message , data ) ; } else if ( message . method ) { this . onDomainNotification ( message , data ) ; } else { throwCriticalError ( `Message didn't have id nor method: ${ data }` ) ; } } private onResponseMessage ( message , data ) { const channel = this . _channels [ decodifyChannelId ( message . id ) ] ; if ( channel ) { message . id = decodifyId ( message . id ) ; data = JSON . stringify ( message ) ; channel . callMessageCallbacks ( data ) ; } else { throwCriticalError ( `Didn't find channel for message with id: ${ message . id } and data: <${ data }>` ) ; } } private onDomainNotification ( message , data ) { const domain = extractDomain ( message . method ) ; for ( const channel of this . _channels ) { channel . callDomainMessageCallbacks ( domain , data ) ; } } constructor ( private _wrappedLikeSocket ) { this . _wrappedLikeSocket . on ( 'message' , data => this . onMessage ( data ) ) ; } public addChannel ( channelName ) { if ( this . _channels . length >= 10 ) { throw new Error ( `Only 10 channels are supported` ) ; } const channel = new CRDPChannel ( channelName , this . _channels . length , this ) ; this . _channels . push ( channel ) ; return channel ; } public send ( channel , data ) { const message = JSON . parse ( data ) ; if ( message . id !== undefined ) { message . id = encodifyChannel ( channel . id , message . id ) ; data = JSON . stringify ( message ) ; } else { throwCriticalError ( `Channel [${ channel . name }] sent a message without an id: ${ data }` ) ; } this . _wrappedLikeSocket . send ( data ) ; } public addListenerOfNonMultiplexedEvent ( event , cb ) { this . _wrappedLikeSocket . on ( event , cb ) ; } public removeListenerOfNonMultiplexedEvent ( event , cb ) { this . _wrappedLikeSocket . removeListener ( event , cb ) ; } } export class CRDPChannel implements LikeSocket { private static timeToPreserveMessagesInMillis = 60 * 1000 ; private _messageCallbacks : Function [ ] = [ ] ; private _enabledDomains : { [ domain ] : boolean ; } = { } ; private _pendingMessagesForDomain : { [ domain ] : string [ ] ; } = { } ; public callMessageCallbacks ( messageData ) { this . _messageCallbacks . forEach ( callback => callback ( messageData ) ) ; } public callDomainMessageCallbacks ( domain , messageData ) { if ( this . _enabledDomains [ domain ] ) { this . callMessageCallbacks ( messageData ) ; } else if ( this . _pendingMessagesForDomain !== null ) { this . storeMessageForLater ( domain , messageData ) ; } } private storeMessageForLater ( domain , messageData ) { let messagesForDomain = this . _pendingMessagesForDomain [ domain ] ; if ( messagesForDomain === undefined ) { this . _pendingMessagesForDomain [ domain ] = [ ] ; messagesForDomain = this . _pendingMessagesForDomain [ domain ] ; } messagesForDomain . push ( messageData ) ; } constructor ( public name , public id , private _multiplexor ) { } public send ( messageData ) { const message = JSON . parse ( messageData ) ; const method = message . method ; const isEnableMethod = method && method . endsWith ( '.enable' ) ; let domain ; if ( isEnableMethod ) { domain = extractDomain ( method ) ; this . _enabledDomains [ domain ] = true ; } this . _multiplexor . send ( this , messageData ) ; if ( isEnableMethod ) { this . sendUnsentPendingMessages ( domain ) ; } } private sendUnsentPendingMessages ( domain ) { if ( this . _pendingMessagesForDomain !== null ) { const pendingMessagesData = this . _pendingMessagesForDomain [ domain ] ; if ( pendingMessagesData !== undefined && this . _messageCallbacks . length ) { logger . log ( `CRDP Multiplexor - Sending pending messages of domain ${ domain }(Count = ${ pendingMessagesData . length })` ) ; delete this . _pendingMessagesForDomain [ domain ] ; pendingMessagesData . forEach ( pendingMessageData => { this . callDomainMessageCallbacks ( domain , pendingMessageData ) ; } ) ; } } } private discardUnsentPendingMessages ( ) { logger . log ( `CRDP Multiplexor - Discarding unsent pending messages for domains: ${ Object . keys ( this . _pendingMessagesForDomain ) . join ( ', ' ) }` ) ; this . _pendingMessagesForDomain = null ; } public on ( event , cb ) ; public on ( event , cb ) ; public on ( event , cb ) ; public on ( event , cb ) { if ( event === 'message' ) { if ( this . _messageCallbacks . length === 0 ) { setTimeout ( ( ) => this . discardUnsentPendingMessages ( ) , CRDPChannel . timeToPreserveMessagesInMillis ) ; } this . _messageCallbacks . push ( cb ) ; } else { this . _multiplexor . addListenerOfNonMultiplexedEvent ( event , cb ) ; } } public removeListener ( event , cb ) { if ( event === 'message' ) { const index = this . _messageCallbacks . indexOf ( cb ) ; this . _messageCallbacks . splice ( index , 1 ) ; } else { this . _multiplexor . removeListenerOfNonMultiplexedEvent ( event , cb ) ; } } }