const net = require("net") const MessageFactory = require("../Protocol/MessageFactory") const { Crypto } = require("../Utils/Crypto") class BrawlClient { constructor(config, logger) { this.config = config this.logger = logger this.socket = null this.connected = false this.crypto = new Crypto(config.crypto) this.messageFactory = new MessageFactory(config, this.crypto, logger) this.packetTypes = [10100, 10101, 20108, 0] this.currentPacketIndex = 0 this.sequenceNumber = 0 this.keepAliveCounter = 0 this.reconnectAttempts = 0 this.keepAliveInterval = null } connect() { this.logger.info(`🌐 Connecting to ${this.config.server.host}:${this.config.server.port}...`) this.socket = new net.Socket() this.socket.setTimeout(this.config.network.timeout) this.socket.connect(this.config.server.port, this.config.server.host, () => { this.logger.connection("connected", { host: this.config.server.host, port: this.config.server.port, }) this.connected = true this.reconnectAttempts = 0 this.startProtocolSequence() }) this.socket.on("data", (data) => { this.handleServerData(data) }) this.socket.on("close", () => { this.logger.connection("disconnected") this.connected = false this.stopKeepAlive() this.attemptReconnect() }) this.socket.on("error", (err) => { this.logger.error("Connection error:", err.message) this.connected = false this.stopKeepAlive() }) this.socket.on("timeout", () => { this.logger.warn("Connection timeout") this.socket.destroy() }) } handleServerData(data) { try { let decryptedData = data if (this.config.crypto.enabled) { decryptedData = this.crypto.decrypt(data) this.logger.debug("🔓 Packet decrypted") } const message = this.messageFactory.parseMessage(decryptedData) switch (message.type) { case 20100: this.logger.success("🔐 Server Hello received") break case 20103: this.logger.success("✅ Login OK received") this.startKeepAlive() break case 20104: this.logger.error("❌ Login Failed received") break case 20108: this.logger.debug("💓 Keep Alive response received") break default: this.logger.info(`📦 Unknown packet type: ${message.type}`) } } catch (error) { this.logger.error("Error parsing server data:", error.message) } } sendMessage(type, data = null) { if (!this.connected) { this.logger.warn("❌ Not connected to server") return false } try { const message = this.messageFactory.createMessage(type, data) const payload = message.encode() const packet = this.messageFactory.createPacketHeader(type, payload) let finalPacket = packet if (this.config.crypto.enabled) { finalPacket = this.crypto.encrypt(packet) this.logger.debug("🔐 Packet encrypted") } else { this.logger.debug("📤 Packet sent without encryption") } this.socket.write(finalPacket) const typeName = this.getMessageTypeName(type) this.logger.success(`✅ ${typeName} sent successfully`) return true } catch (error) { this.logger.error(`💥 Error sending message ${type}:`, error.message) return false } } getMessageTypeName(type) { const names = { 10100: "ClientHelloMessage", 10101: "LoginMessage", 20108: "KeepAliveMessage", 0: "HeartbeatMessage", } return names[type] || `Unknown(${type})` } startProtocolSequence() { this.logger.info("🎯 Starting protocol sequence...") setTimeout(() => this.sendMessage(10100), 100) setTimeout(() => this.sendMessage(10101), 1000) setTimeout(() => this.sendMessage(0, { sequence: this.sequenceNumber++ }), 2000) setTimeout(() => { this.startPeriodicSending() }, 5000) } startPeriodicSending() { setInterval(() => { if (this.connected) { const packetType = this.packetTypes[this.currentPacketIndex] let data = null if (packetType === 0) { data = { sequence: this.sequenceNumber++ } } else if (packetType === 20108) { data = { counter: this.keepAliveCounter++ } } this.sendMessage(packetType, data) this.currentPacketIndex = (this.currentPacketIndex + 1) % this.packetTypes.length } }, this.config.network.keepAliveInterval) } startKeepAlive() { this.keepAliveInterval = setInterval(() => { if (this.connected) { this.sendMessage(20108, { counter: this.keepAliveCounter++ }) } }, this.config.network.keepAliveInterval) } stopKeepAlive() { if (this.keepAliveInterval) { clearInterval(this.keepAliveInterval) this.keepAliveInterval = null } } attemptReconnect() { if (this.reconnectAttempts < this.config.network.reconnectAttempts) { this.reconnectAttempts++ this.logger.info( `🔄 Attempting reconnection ${this.reconnectAttempts}/${this.config.network.reconnectAttempts}...`, ) setTimeout(() => { this.connect() }, this.config.network.reconnectDelay) } else { this.logger.error("💀 Max reconnection attempts reached") } } disconnect() { this.logger.info("🔌 Disconnecting from server...") this.connected = false this.stopKeepAlive() if (this.socket) { this.socket.destroy() this.socket = null } } getStatus() { return { connected: this.connected, sequenceNumber: this.sequenceNumber, keepAliveCounter: this.keepAliveCounter, reconnectAttempts: this.reconnectAttempts, } } } module.exports = BrawlClient