import net , { Socket } from 'net' ; import { EventEmitter } from 'events' ; import { TextDecoder , TextEncoder } from 'util' ; import { readVarInt , writeVarInt } from '../util/varint' ; const encoder = new TextEncoder ( ) ; const decoder = new TextDecoder ( 'utf8' ) ; class TCPClient extends EventEmitter { public isConnected = false ; private socket : Socket | null = null ; private data = Buffer . alloc ( 0 ) ; connect ( options ) { return new Promise ( ( resolve , reject ) => { this . socket = net . createConnection ( options ) ; const connectHandler = ( ) => { this . isConnected = true ; this . socket ?. removeListener ( 'connect' , connectHandler ) ; this . socket ?. removeListener ( 'error' , errorHandler ) ; this . socket ?. removeListener ( 'timeout' , timeoutHandler ) ; this . socket ?. removeListener ( 'close' , closeHandler ) ; resolve ( ) ; } ; const errorHandler = ( error ) => { this . socket ?. destroy ( ) ; reject ( error ) ; } ; const timeoutHandler = async ( ) => { this . socket ?. destroy ( ) ; reject ( new Error ( 'Socket timed out while connecting' ) ) ; } ; const closeHandler = ( hasError ) => { this . isConnected = false ; this . socket ?. destroy ( ) ; if ( ! hasError ) reject ( ) ; this . emit ( 'close' ) ; } ; this . socket . on ( 'data' , ( data ) => { this . data = Buffer . concat ( [ this . data , data ] ) ; this . emit ( 'data' ) ; } ) ; this . socket . on ( 'connect' , ( ) => connectHandler ( ) ) ; this . socket . on ( 'error' , ( error ) => errorHandler ( error ) ) ; this . socket . on ( 'timeout' , ( ) => timeoutHandler ( ) ) ; this . socket . on ( 'close' , ( hasError ) => closeHandler ( hasError ) ) ; } ) ; } readByte ( ) { return this . readUInt8 ( ) ; } writeByte ( value ) { this . writeUInt8 ( value ) ; } async readBytes ( length ) { await this . ensureBufferedData ( length ) ; const value = this . data . slice ( 0 , length ) ; this . data = this . data . slice ( length ) ; return value ; } writeBytes ( data ) { this . data = Buffer . concat ( [ this . data , data ] ) ; } async readUInt8 ( ) { await this . ensureBufferedData ( 1 ) ; const value = this . data . readUInt8 ( 0 ) ; this . data = this . data . slice ( 1 ) ; return value ; } writeUInt8 ( value ) { const data = Buffer . alloc ( 1 ) ; data . writeUInt8 ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readInt8 ( ) { await this . ensureBufferedData ( 1 ) ; const value = this . data . readInt8 ( 0 ) ; this . data = this . data . slice ( 1 ) ; return value ; } writeInt8 ( value ) { const data = Buffer . alloc ( 1 ) ; data . writeInt8 ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readUInt16BE ( ) { await this . ensureBufferedData ( 2 ) ; const value = this . data . readUInt16BE ( 0 ) ; this . data = this . data . slice ( 2 ) ; return value ; } writeUInt16BE ( value ) { const data = Buffer . alloc ( 2 ) ; data . writeUInt16BE ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readInt16BE ( ) { await this . ensureBufferedData ( 2 ) ; const value = this . data . readInt16BE ( 0 ) ; this . data = this . data . slice ( 2 ) ; return value ; } writeInt16BE ( value ) { const data = Buffer . alloc ( 2 ) ; data . writeInt16BE ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readUInt16LE ( ) { await this . ensureBufferedData ( 2 ) ; const value = this . data . readUInt16LE ( 0 ) ; this . data = this . data . slice ( 2 ) ; return value ; } writeUInt16LE ( value ) { const data = Buffer . alloc ( 2 ) ; data . writeUInt16LE ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readInt16LE ( ) { await this . ensureBufferedData ( 2 ) ; const value = this . data . readInt16LE ( 0 ) ; this . data = this . data . slice ( 2 ) ; return value ; } writeInt16LE ( value ) { const data = Buffer . alloc ( 2 ) ; data . writeInt16LE ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readUInt32BE ( ) { await this . ensureBufferedData ( 4 ) ; const value = this . data . readUInt32BE ( 0 ) ; this . data = this . data . slice ( 4 ) ; return value ; } writeUInt32BE ( value ) { const data = Buffer . alloc ( 4 ) ; data . writeUInt32BE ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readInt32BE ( ) { await this . ensureBufferedData ( 4 ) ; const value = this . data . readInt32BE ( 0 ) ; this . data = this . data . slice ( 4 ) ; return value ; } writeInt32BE ( value ) { const data = Buffer . alloc ( 4 ) ; data . writeInt32BE ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readUInt32LE ( ) { await this . ensureBufferedData ( 4 ) ; const value = this . data . readUInt32LE ( 0 ) ; this . data = this . data . slice ( 4 ) ; return value ; } writeUInt32LE ( value ) { const data = Buffer . alloc ( 4 ) ; data . writeUInt32LE ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readInt32LE ( ) { await this . ensureBufferedData ( 4 ) ; const value = this . data . readInt32LE ( 0 ) ; this . data = this . data . slice ( 4 ) ; return value ; } writeInt32LE ( value ) { const data = Buffer . alloc ( 4 ) ; data . writeInt32LE ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readUInt64BE ( ) { await this . ensureBufferedData ( 8 ) ; const value = this . data . readBigUInt64BE ( 0 ) ; this . data = this . data . slice ( 8 ) ; return value ; } writeUInt64BE ( value ) { const data = Buffer . alloc ( 8 ) ; data . writeBigUInt64BE ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readInt64BE ( ) { await this . ensureBufferedData ( 8 ) ; const value = this . data . readBigInt64BE ( 0 ) ; this . data = this . data . slice ( 8 ) ; return value ; } writeInt64BE ( value ) { const data = Buffer . alloc ( 8 ) ; data . writeBigInt64BE ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readUInt64LE ( ) { await this . ensureBufferedData ( 8 ) ; const value = this . data . readBigUInt64LE ( 0 ) ; this . data = this . data . slice ( 8 ) ; return value ; } writeUInt64LE ( value ) { const data = Buffer . alloc ( 8 ) ; data . writeBigUInt64LE ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readInt64LE ( ) { await this . ensureBufferedData ( 8 ) ; const value = this . data . readBigInt64LE ( 0 ) ; this . data = this . data . slice ( 8 ) ; return value ; } writeInt64LE ( value ) { const data = Buffer . alloc ( 8 ) ; data . writeBigInt64LE ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readFloatBE ( ) { await this . ensureBufferedData ( 4 ) ; const value = this . data . readFloatBE ( 0 ) ; this . data = this . data . slice ( 4 ) ; return value ; } writeFloatBE ( value ) { const data = Buffer . alloc ( 4 ) ; data . writeFloatBE ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readFloatLE ( ) { await this . ensureBufferedData ( 4 ) ; const value = this . data . readFloatLE ( 0 ) ; this . data = this . data . slice ( 4 ) ; return value ; } writeFloatLE ( value ) { const data = Buffer . alloc ( 4 ) ; data . writeFloatLE ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readDoubleBE ( ) { await this . ensureBufferedData ( 8 ) ; const value = this . data . readDoubleBE ( 0 ) ; this . data = this . data . slice ( 8 ) ; return value ; } writeDoubleBE ( value ) { const data = Buffer . alloc ( 8 ) ; data . writeDoubleBE ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readDoubleLE ( ) { await this . ensureBufferedData ( 8 ) ; const value = this . data . readDoubleLE ( 0 ) ; this . data = this . data . slice ( 8 ) ; return value ; } writeDoubleLE ( value ) { const data = Buffer . alloc ( 8 ) ; data . writeDoubleLE ( value ) ; this . data = Buffer . concat ( [ this . data , data ] ) ; } async readVarInt ( ) { return await readVarInt ( ( ) => this . readByte ( ) ) ; } writeVarInt ( value ) { this . writeBytes ( writeVarInt ( value ) ) ; } async readString ( length ) { const data = await this . readBytes ( length ) ; return decoder . decode ( data ) ; } writeString ( value ) { this . writeBytes ( encoder . encode ( value ) ) ; } async readStringVarInt ( ) { const length = await this . readVarInt ( ) ; const data = await this . readBytes ( length ) ; return decoder . decode ( data ) ; } writeStringVarInt ( value ) { const data = encoder . encode ( value ) ; this . writeVarInt ( data . byteLength ) ; this . writeBytes ( data ) ; } async readStringNT ( ) { let buf = Buffer . alloc ( 0 ) ; let value ; while ( ( value = await this . readByte ( ) ) !== 0x00 ) { buf = Buffer . concat ( [ buf , Buffer . from ( [ value ] ) ] ) ; } return decoder . decode ( buf ) ; } writeStringNT ( value ) { const data = encoder . encode ( value ) ; this . writeBytes ( data ) ; this . writeByte ( 0x00 ) ; } writeStringBytes ( value ) { this . writeBytes ( encoder . encode ( value ) ) ; } async readStringUntil ( byte ) { let buf = Buffer . alloc ( 0 ) ; let value ; while ( ( value = await this . readByte ( ) ) !== byte ) { buf = Buffer . concat ( [ buf , Buffer . from ( [ value ] ) ] ) ; } return decoder . decode ( buf ) ; } flush ( prefixLength = true ) { if ( ! this . socket ) return Promise . resolve ( ) ; return new Promise ( ( resolve , reject ) => { let buf = this . data ; if ( prefixLength ) { buf = Buffer . concat ( [ writeVarInt ( buf . byteLength ) , buf ] ) ; } this . socket ?. write ( buf , ( error ) => { if ( error ) return reject ( error ) ; resolve ( ) ; } ) ; this . data = Buffer . alloc ( 0 ) ; } ) ; } close ( ) { this . socket ?. removeAllListeners ( ) ; this . socket ?. end ( ) ; this . socket ?. destroy ( ) ; } async ensureBufferedData ( byteLength ) { if ( this . data . byteLength >= byteLength ) return Promise . resolve ( ) ; return this . _waitForData ( byteLength ) ; } _waitForData ( byteLength = 1 ) { return new Promise < void > ( ( resolve , reject ) => { const dataHandler = ( ) => { if ( this . data . byteLength >= byteLength ) { this . removeListener ( 'data' , dataHandler ) ; this . removeListener ( 'close' , closeHandler ) ; resolve ( ) ; } } ; const closeHandler = ( ) => { this . removeListener ( 'data' , dataHandler ) ; this . removeListener ( 'close' , closeHandler ) ; reject ( new Error ( 'Socket closed unexpectedly while waiting for data' ) ) ; } ; this . on ( 'data' , ( ) => dataHandler ( ) ) ; this . on ( 'close' , ( ) => closeHandler ( ) ) ; } ) ; } } export default TCPClient ;