import * as Net from "net" ; import * as EE from "events" ; enum State { Offline = 0 , Connecting , Verification , Online } export interface DukVersion { id : string ; proto : number ; dukVersion : number ; gitDescribe : string ; target : string ; major : number ; minor : number ; patch : number ; } export class DukConnection extends EE . EventEmitter { _state : State = State . Offline ; _socket : Net . Socket ; _protoVersion : DukVersion ; private dbgLog : ( msg : string ) => void ; private constructor ( ) { super ( ) ; } public static connect ( ip , port , timeoutMS = 5000 , dbgLog ? ) { const con = new DukConnection ( ) ; con . dbgLog = dbgLog || < any > ( ( ) => { } ) ; con . _connect ( ip , port , timeoutMS ) ; return con ; } private _connect ( ip , port , timeoutMS ) { const sock = new Net . Socket ( ) ; this . log ( `Establishing connection with Duktape at ${ ip }:${ port }` ) ; sock . setTimeout ( timeoutMS , ( ) => { this . dbgLog ( `Connection timed out.` ) ; this . onError ( new Error ( "Connection timed out." ) ) ; } ) ; sock . once ( "error" , error => { this . dbgLog ( `Connection error: ${ error }` ) ; this . onError ( error ) ; } ) ; sock . on ( "close" , ( ) => { this . closeSocket ( "Socked closed unexpectedly." ) ; } ) ; sock . once ( "connect" , ( event ) => { this . log ( "Connected. Verifying protocol..." ) ; this . _state = State . Verification ; sock . setTimeout ( 0 ) ; const inBuf = new Buffer ( 2048 ) ; let inSize = 0 ; const onData = ( data ) => { const rem = inBuf . length - inSize ; if ( rem < 1 ) { this . onError ( new Error ( "Parse error (version identification too long), dropping connection" ) ) ; return ; } const bytesToCopy = rem < data . length ? rem : data . length ; data . copy ( inBuf , inSize , 0 , bytesToCopy ) ; inSize += bytesToCopy ; for ( let i = 0 ; i < inSize ; i ++ ) { if ( inBuf [ i ] !== 0x0a ) { continue ; } let verBuffer = new Buffer ( i ) ; inBuf . copy ( verBuffer , 0 , 0 , i ) ; const idString = verBuffer . toString ( "utf8" ) ; this . dbgLog ( "Protocol ID: " + idString ) ; let version ; try { let split = idString . split ( " " ) ; const dukVersion = Number ( split [ 1 ] ) ; version = { id : idString , proto : Number ( split [ 0 ] ) , dukVersion : dukVersion , gitDescribe : split [ 2 ] , target : split [ 3 ] , major : Math . floor ( dukVersion / 10000 ) , minor : ( dukVersion / 100 ) % 100 , patch : dukVersion % 100 } ; this . _protoVersion = version ; } catch ( err ) { this . onError ( new Error ( `Error validating protocol version: ${ err }` ) ) ; return ; } sock . removeListener ( "data" , onData ) ; const inRem = inSize - i - 1 ; const dataRem = data . length - bytesToCopy ; const remBuf = new Buffer ( inRem + dataRem ) ; if ( inRem > 0 ) { inBuf . copy ( remBuf , 0 , i + 1 , inSize ) ; } if ( dataRem > 0 ) { data . copy ( remBuf , inRem , bytesToCopy , dataRem ) ; } this . onConnected ( remBuf , version ) ; return ; } if ( bytesToCopy < data . length ) { this . onError ( new Error ( "Input buffer overflow" ) ) ; } } ; sock . on ( "data" , onData ) ; } ) ; sock . connect ( port , ip ) ; this . _socket = sock ; this . _state = State . Connecting ; } public closeSocket ( reason ? ) { try { this . _socket . removeAllListeners ( ) ; this . _socket . destroy ( ) ; this . _socket = null ; this . dbgLog ( `Socket closed: ${ reason }` ) ; this . onDisconnect ( reason ) ; } catch ( e ) { } } private onConnected ( buf , version ) { this . _state = State . Online ; this . emit ( "connected" , buf , version ) ; } private onError ( err ) { this . closeSocket ( `Connection error: ${ err }` ) ; this . emit ( "error" , err ) ; } private onDisconnect ( reason ? ) { this . emit ( "disconnect" , reason || "" ) ; } private log ( msg ) { console . log ( msg ) ; } }