node-minecraft-protocol/test/serverTest.js
Grooble 5bebac3662
1.21 Support (#1342)
* Update default version

* Update README

* 1.21.1

* Update version.js

* Update ci.yml

* Update version.js

* add values for vec2f and ChatTypes

* fix lint

* fix server tests

* fix lint

* update mcdata

* remove debug install

---------

Co-authored-by: Romain Beaumont <romain.rom1@gmail.com>
2024-10-26 23:52:46 +02:00

534 lines
17 KiB
JavaScript

/* eslint-env mocha */
const mc = require('../')
const assert = require('power-assert')
const { once } = require('events')
const nbt = require('prismarine-nbt')
const applyClientHelpers = require('./common/clientHelpers')
const { getPort } = require('./common/util')
const w = nbt.comp({
piglin_safe: nbt.byte(0),
natural: nbt.byte(1),
ambient_light: nbt.float(0),
infiniburn: nbt.string('minecraft:infiniburn_overworld'),
respawn_anchor_works: nbt.byte(0),
has_skylight: nbt.byte(1),
bed_works: nbt.byte(1),
has_raids: nbt.byte(1),
name: nbt.string('minecraft:overworld'),
logical_height: nbt.int(256),
shrunk: nbt.byte(0),
ultrawarm: nbt.byte(0),
has_ceiling: nbt.byte(0)
})
for (const supportedVersion of mc.supportedVersions) {
let PORT
const mcData = require('minecraft-data')(supportedVersion)
const version = mcData.version
const loginPacket = (client, server) => {
if (mcData.loginPacket) {
return mcData.loginPacket
}
return {
// 1.7
entityId: client.id,
gameMode: 1,
dimension: (version.version >= 735 ? mcData.loginPacket.dimension : 0),
difficulty: 2,
maxPlayers: server.maxPlayers,
levelType: 'default',
// 1.8
reducedDebugInfo: (version.version >= 735 ? false : 0),
// 1.14
// removes `difficulty`
viewDistance: 10,
// 1.15
hashedSeed: [0, 0],
enableRespawnScreen: true,
// 1.16
// removed levelType
previousGameMode: version.version >= 755 ? 0 : 255,
worldNames: ['minecraft:overworld'],
dimensionCodec: version.version >= 755 ? mcData.loginPacket.dimensionCodec : (version.version >= 735 ? mcData.loginPacket.dimension : { name: '', type: 'compound', value: { dimension: { type: 'list', value: { type: 'compound', value: [w] } } } }),
worldName: 'minecraft:overworld',
isDebug: false,
isFlat: false,
// 1.16.2
isHardcore: false,
// 1.18
simulationDistance: 10,
// 1.19
// removed `dimension`
// removed `dimensionCodec`
registryCodec: {
type: 'compound',
name: '',
value: {}
},
worldType: 'minecraft:overworld',
death: undefined,
// 1.20.5
enforceSecureChat: false
// more to be added
}
}
function sendBroadcastMessage (server, clients, message, sender) {
if (mcData.supportFeature('signedChat')) {
server.writeToClients(clients, 'player_chat', {
plainMessage: message,
signedChatContent: '',
unsignedChatContent: JSON.stringify({ text: message }),
type: mcData.supportFeature('incrementedChatType') ? { registryIndex: 1 } : 0,
senderUuid: 'd3527a0b-bc03-45d5-a878-2aafdd8c8a43', // random
senderName: JSON.stringify({ text: sender }),
senderTeam: undefined,
timestamp: Date.now(),
salt: 0n,
signature: mcData.supportFeature('useChatSessions') ? undefined : Buffer.alloc(0),
previousMessages: [],
filterType: 0,
networkName: JSON.stringify({ text: sender })
})
} else {
server.writeToClients(clients, 'chat', { message: JSON.stringify({ text: message }), position: 0, sender: sender || '0' })
}
}
describe('mc-server ' + supportedVersion + 'v', function () {
this.timeout(5000)
this.beforeEach(async function () {
PORT = await getPort()
console.log(`Using port for tests: ${PORT}`)
})
it('starts listening and shuts down cleanly', function (done) {
const server = mc.createServer({
'online-mode': false,
version: version.minecraftVersion,
port: PORT
})
let listening = false
server.on('listening', function () {
listening = true
server.close()
})
server.on('close', function () {
assert.ok(listening)
done()
})
})
it('kicks clients that do not log in', function (done) {
const server = mc.createServer({
'online-mode': false,
kickTimeout: 100,
checkTimeoutInterval: 10,
version: version.minecraftVersion,
port: PORT
})
let serverClosed, clientClosed
server.on('connection', function (client) {
client.on('end', function (reason) {
assert.strictEqual(reason, 'LoginTimeout')
server.close()
})
})
server.on('close', () => {
serverClosed = true
console.log('Server closed')
checkFinish()
})
server.on('listening', function () {
const client = new mc.Client(false, version.minecraftVersion)
client.on('end', () => {
clientClosed = true
console.log('Client closed')
checkFinish()
})
client.connect(PORT, '127.0.0.1')
})
function checkFinish () {
if (serverClosed && clientClosed) done()
}
})
it('kicks clients that do not send keepalive packets', function (done) {
const server = mc.createServer({
'online-mode': false,
kickTimeout: 100,
checkTimeoutInterval: 10,
version: version.minecraftVersion,
port: PORT
})
let serverClosed, clientClosed
server.on('connection', function (client) {
client.on('end', function (reason) {
assert.strictEqual(reason, 'KeepAliveTimeout')
console.log('Server client disconnected')
server.close()
})
})
server.on('close', () => {
serverClosed = true
console.log('Server closed')
checkFinish()
})
server.on('listening', function () {
const client = mc.createClient({
username: 'superpants',
host: '127.0.0.1',
port: PORT,
keepAlive: false,
version: version.minecraftVersion
})
client.on('end', () => {
clientClosed = true
console.log('Client closed')
checkFinish()
})
})
function checkFinish () {
if (serverClosed && clientClosed) done()
}
})
it('responds to ping requests', function (done) {
const chatMotd = { // Generated with prismarine-chat MessageBuilder on version 1.16 may change in the future
extra: [{ color: 'red', text: 'Red text' }],
bold: true,
text: 'Example chat mesasge'
}
const server = mc.createServer({
'online-mode': false,
motd: 'test1234',
motdMsg: chatMotd,
'max-players': 120,
version: version.minecraftVersion,
port: PORT
})
server.on('listening', function () {
mc.ping({
host: '127.0.0.1',
version: version.minecraftVersion,
port: PORT
}, function (err, results) {
if (err) return done(err)
assert.ok(results.latency >= 0)
assert.ok(results.latency <= 1000)
delete results.latency
assert.deepEqual(results, {
version: {
name: version.minecraftVersion,
protocol: version.version
},
players: {
max: 120,
online: 0,
sample: []
},
description: {
extra: [{ color: 'red', text: 'Red text' }],
bold: true,
text: 'Example chat mesasge'
}
})
server.close()
})
})
server.on('close', done)
})
it('responds with chatMessage motd\'s', function (done) {
const server = mc.createServer({
'online-mode': false,
motd: 'test1234',
'max-players': 120,
version: version.minecraftVersion,
port: PORT
})
server.on('listening', function () {
mc.ping({
host: '127.0.0.1',
version: version.minecraftVersion,
port: PORT
}, function (err, results) {
if (err) return done(err)
assert.ok(results.latency >= 0)
assert.ok(results.latency <= 1000)
delete results.latency
assert.deepEqual(results, {
version: {
name: version.minecraftVersion,
protocol: version.version
},
players: {
max: 120,
online: 0,
sample: []
},
description: { text: 'test1234' }
})
server.close()
})
})
server.on('close', done)
})
it('clients can be changed by beforeLogin', function (done) {
const notchUUID = '069a79f4-44e9-4726-a5be-fca90e38aaf5'
const server = mc.createServer({
'online-mode': false,
version: version.minecraftVersion,
port: PORT,
beforeLogin: (client) => {
client.uuid = notchUUID
}
})
server.on('listening', function () {
const client = mc.createClient({
username: 'notNotch',
host: '127.0.0.1',
version: version.minecraftVersion,
port: PORT
})
client.on('packet', (data, { name }) => {
if (name === 'success') {
assert.strictEqual(data.uuid, notchUUID, 'UUID')
server.close()
}
})
})
server.on('close', done)
})
it('clients can log in and chat', function (done) {
const server = mc.createServer({
'online-mode': false,
version: version.minecraftVersion,
port: PORT
})
const broadcast = (message, exclude) => sendBroadcastMessage(server,
Object.values(server.clients).filter(client => client !== exclude), message)
const username = ['player1', 'player2']
let index = 0
server.on('playerJoin', function (client) {
console.log('ChatTest: Player has joined')
assert.notEqual(client.id, null)
assert.strictEqual(client.username, username[index++])
broadcast(client.username + ' joined the game.')
client.on('end', function () {
broadcast(client.username + ' left the game.', client)
if (client.username === 'player2') server.close()
})
client.write('login', loginPacket(client, server))
const handleChat = (packet) => broadcast(`<${client.username}> ${packet.message}`)
client.on('chat', handleChat)
client.on('chat_message', handleChat)
})
server.on('close', done)
server.on('listening', function () {
const player1 = applyClientHelpers(mc.createClient({
username: 'player1',
host: '127.0.0.1',
version: version.minecraftVersion,
port: PORT
}))
console.log('ChatTest: Player1 is joining...')
player1.on('login', async function (packet) {
console.log('ChatTest: Player 1 has joined')
const player2 = applyClientHelpers(mc.createClient({
username: 'player2',
host: '127.0.0.1',
version: version.minecraftVersion,
port: PORT
}))
console.log('ChatTest: waiting for next message from P2')
const p1Join = await player1.nextMessage('player2')
assert.strictEqual(p1Join, '{"text":"player2 joined the game."}')
console.log('ChatTest: Got message from P2')
player2.chat('hi')
const p2hi = await player1.nextMessage('player2')
assert.strictEqual(p2hi, '{"text":"<player2> hi"}')
console.log('ChatTest: Waiting again for next message from P2')
player1.chat('hello')
const p1hello = await player2.nextMessage('player1')
assert.strictEqual(p1hello, '{"text":"<player1> hello"}')
player2.end()
const p2leaving = await player1.nextMessage('player2')
assert.strictEqual(p2leaving, '{"text":"player2 left the game."}')
player1.end()
})
})
})
it('kicks clients when invalid credentials', function (done) {
this.timeout(10000)
const server = mc.createServer({
version: version.minecraftVersion,
port: PORT
})
let serverPlayerDisconnected, serverClosed, clientClosed
server.on('connection', function (client) {
client.on('end', function (reason) {
serverPlayerDisconnected = true
console.log('Server player disconnected')
checkFinish()
server.close()
})
})
server.on('close', () => {
serverClosed = true
console.log('Server closed')
checkFinish()
})
server.on('listening', function () {
console.log('Server is listening')
const client = mc.createClient({
username: 'lalalal',
host: '127.0.0.1',
version: version.minecraftVersion,
port: PORT
})
client.on('end', () => {
clientClosed = true
console.log('Client closed')
checkFinish()
})
})
function checkFinish () {
if (serverPlayerDisconnected && clientClosed && serverClosed) done()
}
})
it('gives correct reason for kicking clients when shutting down', function (done) {
const server = mc.createServer({
'online-mode': false,
version: version.minecraftVersion,
port: PORT
})
let serverPlayerDisconnected, serverClosed
server.on('playerJoin', function (client) {
console.log('Server got player join')
client.on('end', function (reason) {
assert.strictEqual(reason, 'ServerShutdown')
serverPlayerDisconnected = true
console.log('Server player disconnected')
checkFinish()
})
client.write('login', loginPacket(client, server))
})
server.on('close', () => {
serverClosed = true
console.log('Server closed')
checkFinish()
})
server.on('listening', function () {
const client = mc.createClient({
username: 'lalalal',
host: '127.0.0.1',
version: version.minecraftVersion,
port: PORT
})
client.on('playerJoin', function () {
console.log('Client joined')
server.close()
})
})
function checkFinish () {
if (serverPlayerDisconnected && serverClosed) done()
}
})
it('encodes chat packet once and send it to two clients', function (done) {
const server = mc.createServer({
'online-mode': false,
version: version.minecraftVersion,
port: PORT
})
server.on('playerJoin', function (client) {
client.write('login', loginPacket(client, server))
})
server.on('close', done)
server.on('listening', async function () {
const player1 = applyClientHelpers(mc.createClient({
username: 'player1',
host: '127.0.0.1',
version: version.minecraftVersion,
port: PORT
}))
const player2 = applyClientHelpers(mc.createClient({
username: 'player2',
host: '127.0.0.1',
version: version.minecraftVersion,
port: PORT
}))
await Promise.all([once(player1, 'login'), once(player2, 'login')])
sendBroadcastMessage(server, Object.values(server.clients), 'A message from the server.')
const results = await Promise.all([player1.nextMessage(), player2.nextMessage()])
for (const msg of results) {
assert.strictEqual(msg, '{"text":"A message from the server."}')
}
player1.end()
player2.end()
await Promise.all([once(player1, 'end'), once(player2, 'end')])
server.close()
})
})
it('supports bundle packet', function (done) {
const server = mc.createServer({
'online-mode': false,
version: version.minecraftVersion,
port: PORT
})
server.on('playerJoin', function (client) {
client.on('end', function (reason) {
assert.strictEqual(reason, 'ServerShutdown')
})
client.write('login', loginPacket(client, server))
client.writeBundle([
['update_time', { age: 1, time: 2 }],
['close_window', { windowId: 0 }]
])
})
server.on('close', done)
server.on('listening', function () {
const client = mc.createClient({
username: 'lalalal',
host: '127.0.0.1',
version: version.minecraftVersion,
port: PORT
})
client.on('update_time', function () {
// Below handler synchronously defined should be guaranteed to be called after the above one
const d1 = Date.now()
client.on('close_window', function () {
server.close()
const d2 = Date.now()
if (mcData.supportFeature('hasBundlePacket') && (d2 - d1) > 1) {
throw new Error(`bundle packet constituents did not arrive at once : ${d1}, ${d2}`)
}
})
})
})
})
})
}