import { Global } from "../core/global" ; import { SocketHandler , SocketMessage } from "../core/socket-handler" ; import { WebhookRequest } from "../core/webhook-request" ; import { TCPClient } from "./tcp-client" ; import { NetworkErrorType } from "../core/global" ; import { LoggingHelper } from "../core/logging-helper" ; import { KeepAlive } from "./keep-alive" ; import { StringUtil } from "../core/string-util" ; import { HTTPBuffer } from "../core/http-buffer" ; const chalk = require ( "chalk" ) ; const Logger = "BST-CLIENT" ; /**
 * Handles between the bespoken server and the service running on the local machine
 * Initiates a TCP connection with the server
 */ export class BespokeClient { public onConnect : ( error ? : any ) => void = null ; public onReconnect : ( error ? : any ) => void = null ; public onError : ( errorType : NetworkErrorType , message : string ) => void ; public static RECONNECT_MAX_RETRIES : number = 3 ; private keepAlive : KeepAlive ; private socketHandler : SocketHandler ; private shuttingDown : boolean = false ; private reconnectRetries : number = 0 ; constructor ( public nodeID , private host , private port , private targetDomain , private targetPort , private secretKey ? ) { } public attemptConnection ( ) { const self = this ; if ( this . socketHandler ) { this . socketHandler . disconnect ( ) ; } if ( this . keepAlive ) { this . keepAlive . stop ( ) ; } this . socketHandler = SocketHandler . connect ( this . host , this . port , function ( error ) { self . connected ( error ) ; } , function ( socketMessage ) { self . messageReceived ( socketMessage ) ; } ) ; this . socketHandler . onCloseCallback = function ( ) { if ( ! self . shuttingDown ) { LoggingHelper . error ( Logger , "Socket closed by bst server: " + self . host + ":" + self . port ) ; } if ( self . reconnectRetries < BespokeClient . RECONNECT_MAX_RETRIES ) { self . reconnectRetries ++ ; LoggingHelper . error ( Logger , "Attempting to reconnect in " + self . reconnectRetries + " seconds" ) ; setTimeout ( function ( ) { self . attemptConnection ( ) ; } , self . reconnectRetries * 1000 ) ; } else { LoggingHelper . error ( Logger , "Check your network settings - and try connecting again." ) ; LoggingHelper . error ( Logger , "If the issue persists, contact us at Bespoken:" ) ; LoggingHelper . error ( Logger , "\thttps://gitter.im/bespoken/bst" ) ; self . shutdown ( ) ; } } ; } public connect ( onConnect ? ) { if ( onConnect !== undefined && onConnect !== null ) { this . onConnect = onConnect ; } this . attemptConnection ( ) ; } protected newKeepAlive ( handler ) { return new KeepAlive ( handler ) ; } private onWebhookReceived ( request ) { const self = this ; if ( this . secretKey ) { let secretKeyValidated = false ; if ( request . headers && request . headers [ "bespoken-key" ] === this . secretKey ) { secretKeyValidated = true ; } if ( request . queryParameters && request . queryParameters [ "bespoken-key" ] === this . secretKey ) { secretKeyValidated = true ; } if ( ! secretKeyValidated ) { const errorMessage = "Unauthorized request" ; this . socketHandler . send ( new SocketMessage ( HTTPBuffer . errorResponse ( errorMessage ) . raw ( ) , request . id ( ) ) ) ; return ; } } LoggingHelper . info ( Logger , "RequestReceived: " + request . toString ( ) + " ID: " + request . id ( ) ) ; const bodyToPrint = request . isJSON ( ) ? StringUtil . prettyPrintJSON ( request . body ) : "< Binary data >" ; LoggingHelper . verbose ( Logger , "Payload:\n" + chalk . hex ( LoggingHelper . REQUEST_COLOR ) ( bodyToPrint ) ) ; const tcpClient = new TCPClient ( request . id ( ) + "" ) ; const httpBuffer = new HTTPBuffer ( ) ; tcpClient . transmit ( self . targetDomain , self . targetPort , request . requestWithoutBespokenData ( ) , function ( data , error , message ) { if ( data != null ) { httpBuffer . append ( data ) ; if ( httpBuffer . complete ( ) ) { LoggingHelper . info ( Logger , "ResponseReceived ID: " + request . id ( ) ) ; let payload = null ; const bodyToString = httpBuffer . body ( ) . toString ( ) ; if ( httpBuffer . isJSON ( ) ) { payload = StringUtil . prettyPrintJSON ( bodyToString ) ; } else { payload = /[\x00-\x1F]/ . test ( bodyToString ) ? "< Binary data >" : bodyToString ; } if ( payload . indexOf ( "Unhandle exception" ) !== - 1 || payload . indexOf ( "Error: " ) !== - 1 ) { LoggingHelper . verbose ( Logger , "Payload:\n" + chalk . red ( payload ) ) ; } else { LoggingHelper . verbose ( Logger , "Payload:\n" + chalk . cyan ( payload ) ) ; } self . socketHandler . send ( new SocketMessage ( httpBuffer . raw ( ) , request . id ( ) ) ) ; } } else if ( error !== null && error !== undefined ) { if ( error === NetworkErrorType . CONNECTION_REFUSED ) { LoggingHelper . error ( Logger , chalk . red ( "CLIENT Connection Refused, Port " + self . targetPort + ". Is your server running?" ) ) ; } const errorMessage = "BST Proxy - Local Forwarding Error\n" + message ; self . socketHandler . send ( new SocketMessage ( HTTPBuffer . errorResponse ( errorMessage ) . raw ( ) , request . id ( ) ) ) ; if ( self . onError != null ) { self . onError ( error , message ) ; } } } ) ; } private connected ( error ? ) { const self = this ; if ( error ) { LoggingHelper . error ( Logger , "Unable to connect to: " + this . host + ":" + this . port ) ; if ( this . reconnectRetries < BespokeClient . RECONNECT_MAX_RETRIES ) { this . reconnectRetries ++ ; LoggingHelper . error ( Logger , "Attempting to reconnect in " + this . reconnectRetries + " seconds" ) ; setTimeout ( function ( ) { self . attemptConnection ( ) ; } , this . reconnectRetries * 1000 ) ; if ( this . onReconnect ) { this . onReconnect ( error ) ; } } else { this . shutdown ( ) ; if ( this . onConnect ) { this . onConnect ( error ) ; } } } else { this . reconnectRetries = 0 ; LoggingHelper . info ( Logger , "Connected - " + this . host + ":" + this . port ) ; const messageJSON = { "id" : this . nodeID } ; const message = JSON . stringify ( messageJSON ) ; this . socketHandler . send ( new SocketMessage ( message ) ) ; if ( this . onConnect !== undefined && this . onConnect !== null ) { this . onConnect ( ) ; } this . keepAlive = this . newKeepAlive ( this . socketHandler ) ; this . keepAlive . start ( function ( ) { LoggingHelper . error ( Logger , "Socket not communicating with bst server: " + self . socketHandler . remoteEndPoint ( ) ) ; LoggingHelper . error ( Logger , "Check your network settings - and maybe try connecting again." ) ; LoggingHelper . error ( Logger , "If the issue persists, contact us at Bespoken:" ) ; LoggingHelper . error ( Logger , "\thttps://gitter.im/bespoken/bst" ) ; } ) ; } } private messageReceived ( socketMessage ) { if ( socketMessage . startsWith ( "ACK" ) ) { } else if ( socketMessage . startsWith ( Global . KeepAliveMessage ) ) { this . keepAlive . received ( ) ; } else { this . onWebhookReceived ( WebhookRequest . fromBuffer ( this . socketHandler . socket , socketMessage . getMessage ( ) , socketMessage . getMessageID ( ) ) ) ; } } public shutdown ( callback ? ) { LoggingHelper . info ( Logger , "Shutting down proxy" ) ; this . shuttingDown = true ; if ( this . keepAlive ) { this . keepAlive . stop ( ) ; } this . reconnectRetries = BespokeClient . RECONNECT_MAX_RETRIES ; this . socketHandler . disconnect ( ) ; if ( callback !== undefined && callback !== null ) { callback ( ) ; } } }