218 lines
5.8 KiB
JavaScript
218 lines
5.8 KiB
JavaScript
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
|