UNPKG

@rlqd/minecraft-server-util

Version:

A Node.js library for Minecraft servers that can retrieve status, perform queries, and RCON into servers.

513 lines (348 loc) 10.6 kB
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: net.NetConnectOpts): Promise<void> { 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: Error) => { this.socket?.destroy(); reject(error); }; const timeoutHandler = async () => { this.socket?.destroy(); reject(new Error('Server is offline or unreachable')); }; const closeHandler = (hasError: boolean) => { 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(): Promise<number> { return this.readUInt8(); } writeByte(value: number): void { this.writeUInt8(value); } async readBytes(length: number): Promise<Buffer> { await this.ensureBufferedData(length); const value = this.data.slice(0, length); this.data = this.data.slice(length); return value; } writeBytes(data: Uint8Array): void { this.data = Buffer.concat([this.data, data]); } async readUInt8(): Promise<number> { await this.ensureBufferedData(1); const value = this.data.readUInt8(0); this.data = this.data.slice(1); return value; } writeUInt8(value: number): void { const data = Buffer.alloc(1); data.writeUInt8(value); this.data = Buffer.concat([this.data, data]); } async readInt8(): Promise<number> { await this.ensureBufferedData(1); const value = this.data.readInt8(0); this.data = this.data.slice(1); return value; } writeInt8(value: number): void { const data = Buffer.alloc(1); data.writeInt8(value); this.data = Buffer.concat([this.data, data]); } async readUInt16BE(): Promise<number> { await this.ensureBufferedData(2); const value = this.data.readUInt16BE(0); this.data = this.data.slice(2); return value; } writeUInt16BE(value: number): void { const data = Buffer.alloc(2); data.writeUInt16BE(value); this.data = Buffer.concat([this.data, data]); } async readInt16BE(): Promise<number> { await this.ensureBufferedData(2); const value = this.data.readInt16BE(0); this.data = this.data.slice(2); return value; } writeInt16BE(value: number): void { const data = Buffer.alloc(2); data.writeInt16BE(value); this.data = Buffer.concat([this.data, data]); } async readUInt16LE(): Promise<number> { await this.ensureBufferedData(2); const value = this.data.readUInt16LE(0); this.data = this.data.slice(2); return value; } writeUInt16LE(value: number): void { const data = Buffer.alloc(2); data.writeUInt16LE(value); this.data = Buffer.concat([this.data, data]); } async readInt16LE(): Promise<number> { await this.ensureBufferedData(2); const value = this.data.readInt16LE(0); this.data = this.data.slice(2); return value; } writeInt16LE(value: number): void { const data = Buffer.alloc(2); data.writeInt16LE(value); this.data = Buffer.concat([this.data, data]); } async readUInt32BE(): Promise<number> { await this.ensureBufferedData(4); const value = this.data.readUInt32BE(0); this.data = this.data.slice(4); return value; } writeUInt32BE(value: number): void { const data = Buffer.alloc(4); data.writeUInt32BE(value); this.data = Buffer.concat([this.data, data]); } async readInt32BE(): Promise<number> { await this.ensureBufferedData(4); const value = this.data.readInt32BE(0); this.data = this.data.slice(4); return value; } writeInt32BE(value: number): void { const data = Buffer.alloc(4); data.writeInt32BE(value); this.data = Buffer.concat([this.data, data]); } async readUInt32LE(): Promise<number> { await this.ensureBufferedData(4); const value = this.data.readUInt32LE(0); this.data = this.data.slice(4); return value; } writeUInt32LE(value: number): void { const data = Buffer.alloc(4); data.writeUInt32LE(value); this.data = Buffer.concat([this.data, data]); } async readInt32LE(): Promise<number> { await this.ensureBufferedData(4); const value = this.data.readInt32LE(0); this.data = this.data.slice(4); return value; } writeInt32LE(value: number): void { const data = Buffer.alloc(4); data.writeInt32LE(value); this.data = Buffer.concat([this.data, data]); } async readUInt64BE(): Promise<bigint> { await this.ensureBufferedData(8); const value = this.data.readBigUInt64BE(0); this.data = this.data.slice(8); return value; } writeUInt64BE(value: bigint): void { const data = Buffer.alloc(8); data.writeBigUInt64BE(value); this.data = Buffer.concat([this.data, data]); } async readInt64BE(): Promise<bigint> { await this.ensureBufferedData(8); const value = this.data.readBigInt64BE(0); this.data = this.data.slice(8); return value; } writeInt64BE(value: bigint): void { const data = Buffer.alloc(8); data.writeBigInt64BE(value); this.data = Buffer.concat([this.data, data]); } async readUInt64LE(): Promise<bigint> { await this.ensureBufferedData(8); const value = this.data.readBigUInt64LE(0); this.data = this.data.slice(8); return value; } writeUInt64LE(value: bigint): void { const data = Buffer.alloc(8); data.writeBigUInt64LE(value); this.data = Buffer.concat([this.data, data]); } async readInt64LE(): Promise<bigint> { await this.ensureBufferedData(8); const value = this.data.readBigInt64LE(0); this.data = this.data.slice(8); return value; } writeInt64LE(value: bigint): void { const data = Buffer.alloc(8); data.writeBigInt64LE(value); this.data = Buffer.concat([this.data, data]); } async readFloatBE(): Promise<number> { await this.ensureBufferedData(4); const value = this.data.readFloatBE(0); this.data = this.data.slice(4); return value; } writeFloatBE(value: number): void { const data = Buffer.alloc(4); data.writeFloatBE(value); this.data = Buffer.concat([this.data, data]); } async readFloatLE(): Promise<number> { await this.ensureBufferedData(4); const value = this.data.readFloatLE(0); this.data = this.data.slice(4); return value; } writeFloatLE(value: number): void { const data = Buffer.alloc(4); data.writeFloatLE(value); this.data = Buffer.concat([this.data, data]); } async readDoubleBE(): Promise<number> { await this.ensureBufferedData(8); const value = this.data.readDoubleBE(0); this.data = this.data.slice(8); return value; } writeDoubleBE(value: number): void { const data = Buffer.alloc(8); data.writeDoubleBE(value); this.data = Buffer.concat([this.data, data]); } async readDoubleLE(): Promise<number> { await this.ensureBufferedData(8); const value = this.data.readDoubleLE(0); this.data = this.data.slice(8); return value; } writeDoubleLE(value: number): void { const data = Buffer.alloc(8); data.writeDoubleLE(value); this.data = Buffer.concat([this.data, data]); } async readVarInt(): Promise<number> { return await readVarInt(() => this.readByte()); } writeVarInt(value: number): void { this.writeBytes(writeVarInt(value)); } async readString(length: number): Promise<string> { const data = await this.readBytes(length); return decoder.decode(data); } writeString(value: string): void { this.writeBytes(encoder.encode(value)); } async readStringVarInt(): Promise<string> { const length = await this.readVarInt(); const data = await this.readBytes(length); return decoder.decode(data); } writeStringVarInt(value: string): void { const data = encoder.encode(value); this.writeVarInt(data.byteLength); this.writeBytes(data); } async readStringNT(): Promise<string> { 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: string): void { const data = encoder.encode(value); this.writeBytes(data); this.writeByte(0x00); } writeStringBytes(value: string): void { this.writeBytes(encoder.encode(value)); } async readStringUntil(byte: number): Promise<string> { 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): Promise<void> { 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(): void { this.socket?.removeAllListeners(); this.socket?.end(); this.socket?.destroy(); } async ensureBufferedData(byteLength: number): Promise<void> { if (this.data.byteLength >= byteLength) return Promise.resolve(); return this._waitForData(byteLength); } _waitForData(byteLength = 1): Promise<void> { 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;