import { Global } from "./global" ; import { Socket } from "net" ; import { StringUtil } from "./string-util" ; import { LoggingHelper } from "./logging-helper" ; import * as net from "net" ; let Logger = "SOCKET" ; /**
 * Manages the low-level socket communications
 */ export class SocketHandler { public buffer : Buffer = Buffer . from ( "" ) ; public onDataCallback : ( data : Buffer ) => void ; public onCloseCallback : ( ) => void = null ; private connected : boolean = true ; public static connect ( host , port , onConnect , onMessage ) { let socket = new net . Socket ( ) ; let handler = new SocketHandler ( socket , onMessage ) ; handler . connected = false ; socket . connect ( port , host , function ( ) { handler . connected = true ; onConnect ( ) ; } ) ; socket . on ( "error" , function ( error ) { if ( ! handler . connected ) { onConnect ( error ) ; } } ) ; return handler ; } public constructor ( public socket , private onMessage ) { let self = this ; this . onDataCallback = function ( data ) { if ( ! self . handleData ( data ) ) { this . socket . destroy ( "Error handling data - closing socket" ) ; } } ; this . socket . on ( "data" , this . onDataCallback ) ; this . socket . on ( "error" , function ( e ) { if ( self . connected ) { LoggingHelper . error ( Logger , "SocketError From: " + self . remoteEndPoint ( ) + " Error: " + e . code + " Message: " + e . message ) ; } } ) ; this . socket . on ( "close" , function ( ) { if ( self . connected ) { if ( self . onCloseCallback != null ) { self . onCloseCallback ( ) ; } else { LoggingHelper . debug ( Logger , "Socket closed" ) ; } } } ) ; } param data private handleData ( data ) { if ( data !== null ) { try { this . buffer = Buffer . concat ( [ this . buffer , data ] ) ; } catch ( e ) { console . error ( `Buffer allocation error existing ${ this . buffer . length } new: ${ data . length } error: ${ e . message }` ) ; return false ; } } const delimiterIndex = this . buffer . indexOf ( Global . MessageDelimiter ) ; if ( delimiterIndex > - 1 ) { const messageIDIndex = delimiterIndex - Global . MessageIDLength ; let badMessage = messageIDIndex < 0 ; const message = this . buffer . slice ( 0 , messageIDIndex ) ; const messageIDString = this . buffer . slice ( delimiterIndex - Global . MessageIDLength , delimiterIndex ) . toString ( ) ; const messageID = parseInt ( messageIDString ) ; if ( isNaN ( messageID ) || ( messageID + "" ) . length < 13 ) { badMessage = true ; } if ( badMessage ) { LoggingHelper . error ( Logger , "Bad message received: " + this . buffer . toString ( ) ) ; } else { const socketMessage = new SocketMessage ( message , messageID ) ; LoggingHelper . debug ( Logger , "DATA READ " + this . remoteEndPoint ( ) + " ID: " + messageID + " MSG: " + StringUtil . prettyPrint ( socketMessage . messageForLogging ( ) ) ) ; this . onMessage ( socketMessage ) ; } this . buffer = this . buffer . slice ( delimiterIndex + Global . MessageDelimiter . length ) ; if ( this . buffer . toString ( ) . indexOf ( Global . MessageDelimiter ) !== - 1 ) { return this . handleData ( null ) ; } } return true ; } public send ( socketMessage ) { if ( this . socket === null ) { LoggingHelper . warn ( Logger , "Writing message to closed socket: " + socketMessage . getMessageID ( ) ) ; return ; } const dataSent = socketMessage . isString ( ) ? socketMessage . asString ( ) : "< Binary Data >" ; LoggingHelper . debug ( Logger , "DATA SENT " + this . remoteEndPoint ( ) + " SEQUENCE: " + socketMessage . getMessageID ( ) + " " + StringUtil . prettyPrint ( dataSent ) ) ; this . socket . write ( socketMessage . getFullMessage ( ) , null ) ; } public remoteAddress ( ) { return this . socket . remoteAddress ; } public remoteEndPoint ( ) { if ( this . socket === null ) { return "" ; } return this . socket . remoteAddress + ":" + this . socket . remotePort ; } public disconnect ( ) { if ( this . isOpen ( ) ) { this . socket . end ( ) ; this . socket = null ; } } public isOpen ( ) { return this . socket != null ; } } export class SocketMessage { private message : Buffer = Buffer . from ( "" ) ; public constructor ( message , private sequenceNumber ? ) { if ( typeof message === "string" ) { this . message = Buffer . concat ( [ this . message , Buffer . from ( message ) ] ) ; } else { this . message = Buffer . concat ( [ this . message , message ] ) ; } } public getMessageID ( ) { return this . sequenceNumber ? this . sequenceNumber : new Date ( ) . getTime ( ) ; } public asString ( ) { return this . message . toString ( ) ; } public isString ( ) { return ! /[\x00-\x1F]/ . test ( this . asString ( ) ) ; } public isJSON ( ) { try { JSON . parse ( this . asString ( ) ) ; return true ; } catch ( error ) { return false ; } } public asJSON ( ) { return JSON . parse ( this . asString ( ) ) ; } public messageForLogging ( ) { return this . isString ( ) ? this . asString ( ) : "< Binary Data>" ; } public getMessage ( ) { return this . message ; } public getFullMessage ( ) { const messageID = this . getMessageID ( ) ; return Buffer . concat ( [ this . message , Buffer . from ( messageID . toString ( ) ) , Buffer . from ( Global . MessageDelimiter ) ] ) ; } public contains ( stringToFind ) { return this . asString ( ) . indexOf ( stringToFind ) > - 1 ; } public startsWith ( stringToFind ) { return this . asString ( ) . startsWith ( stringToFind ) ; } }