104 lines
3.4 KiB
JavaScript
104 lines
3.4 KiB
JavaScript
const { EventEmitter } = require('events')
|
|
const { createDeserializer, createSerializer } = require('./transforms/serializer')
|
|
const { Player } = require('./serverPlayer')
|
|
const { RakServer } = require('./rak')
|
|
const { sleep } = require('./datatypes/util')
|
|
const { ServerAdvertisement } = require('./server/advertisement')
|
|
const Options = require('./options')
|
|
const debug = globalThis.isElectron ? console.debug : require('debug')('minecraft-protocol')
|
|
|
|
class Server extends EventEmitter {
|
|
constructor (options) {
|
|
super()
|
|
this.options = { ...Options.defaultOptions, ...options }
|
|
this.validateOptions()
|
|
this.serializer = createSerializer(this.options.version)
|
|
this.deserializer = createDeserializer(this.options.version)
|
|
this.advertisement = new ServerAdvertisement(this.options.motd)
|
|
this.advertisement.playersMax = options.maxPlayers ?? 3
|
|
/** @type {Object<string, Player>} */
|
|
this.clients = {}
|
|
this.clientCount = 0
|
|
this.conLog = debug
|
|
}
|
|
|
|
validateOptions () {
|
|
if (!Options.Versions[this.options.version]) {
|
|
console.warn('Supported versions', Options.Versions)
|
|
throw Error(`Unsupported version ${this.options.version}`)
|
|
}
|
|
this.options.protocolVersion = Options.Versions[this.options.version]
|
|
if (this.options.protocolVersion < Options.MIN_VERSION) {
|
|
throw new Error(`Protocol version < ${Options.MIN_VERSION} : ${this.options.protocolVersion}, too old`)
|
|
}
|
|
}
|
|
|
|
onOpenConnection = (conn) => {
|
|
this.conLog('new connection', conn?.address)
|
|
const player = new Player(this, conn)
|
|
this.clients[conn.address] = player
|
|
this.clientCount++
|
|
this.emit('connect', player)
|
|
}
|
|
|
|
onCloseConnection = (inetAddr, reason) => {
|
|
this.conLog('close connection', inetAddr?.address, reason)
|
|
delete this.clients[inetAddr]?.connection // Prevent close loop
|
|
this.clients[inetAddr?.address ?? inetAddr]?.close()
|
|
delete this.clients[inetAddr]
|
|
this.clientCount--
|
|
}
|
|
|
|
onEncapsulated = (buffer, address) => {
|
|
const client = this.clients[address]
|
|
if (!client) {
|
|
throw new Error(`packet from unknown inet addr: ${address}`)
|
|
}
|
|
client.handle(buffer)
|
|
}
|
|
|
|
getAdvertisement () {
|
|
if (this.options.advertisementFn) {
|
|
return this.options.advertisementFn()
|
|
}
|
|
this.advertisement.playersOnline = this.clientCount
|
|
return this.advertisement
|
|
}
|
|
|
|
async listen (host = this.options.host, port = this.options.port) {
|
|
this.raknet = new RakServer({ host, port }, this)
|
|
try {
|
|
await this.raknet.listen()
|
|
} catch (e) {
|
|
console.warn(`Failed to bind server on [${this.options.host}]/${this.options.port}, is the port free?`)
|
|
throw e
|
|
}
|
|
this.conLog('Listening on', host, port, this.options.version)
|
|
this.raknet.onOpenConnection = this.onOpenConnection
|
|
this.raknet.onCloseConnection = this.onCloseConnection
|
|
this.raknet.onEncapsulated = this.onEncapsulated
|
|
|
|
this.serverTimer = setInterval(() => {
|
|
this.raknet.updateAdvertisement()
|
|
}, 1000)
|
|
|
|
return { host, port }
|
|
}
|
|
|
|
async close (disconnectReason) {
|
|
for (const caddr in this.clients) {
|
|
const client = this.clients[caddr]
|
|
client.disconnect(disconnectReason)
|
|
}
|
|
|
|
clearInterval(this.serverTimer)
|
|
this.clients = {}
|
|
this.clientCount = 0
|
|
|
|
// Allow some time for client to get disconnect before closing connection.
|
|
await sleep(60)
|
|
this.raknet.close()
|
|
}
|
|
}
|
|
|
|
module.exports = { Server }
|