mcraft-fun-mineflayer
Version:
Mineflayer viewer (connector) for mcraft.fun project and vanilla Minecraft client! Both TCP and WebSockets servers are supported.
191 lines (190 loc) • 6.87 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.setNextWebsocketOptions = setNextWebsocketOptions;
const net_1 = require("net");
const ws_1 = require("ws");
const server_1 = __importDefault(require("minecraft-protocol/src/server"));
const minecraft_protocol_1 = require("minecraft-protocol");
const clientIgnoredPackets = [
'position'
];
let nextWebsocketOptions;
function setNextWebsocketOptions(options) {
nextWebsocketOptions = options;
}
class WebsocketConnectionSocket extends net_1.Socket {
ws;
id = '';
constructor(ws, versionData, passwordValidation) {
super();
this.ws = ws;
let isFirstMessage = true;
let dropMessages = false;
this.ws.on('message', (data) => {
if (dropMessages)
return;
// if data is string "version" then output info
if (isFirstMessage && Buffer.isBuffer(data) && Buffer.from(data).toString() === 'version') {
this.ws.send(JSON.stringify(versionData));
this.end();
return;
}
if (isFirstMessage && passwordValidation) {
if (!Buffer.isBuffer(data) || Buffer.from(data).toString() !== passwordValidation) {
dropMessages = true;
setTimeout(() => {
this.ws.send(JSON.stringify({
error: 'Invalid password'
}));
this.end();
}, 500);
return;
}
isFirstMessage = false;
return;
}
isFirstMessage = false;
// console.log('message', data)
this.emit('data', data);
});
this.ws.on('close', () => {
this.emit('end');
});
this.on('end', () => {
this.ws.close();
});
this.ws.on('error', err => {
this.emit('error', err);
});
}
write(data, callback) {
// console.debug('write', data)
this.ws.send(data, callback);
return true;
}
//@ts-expect-error
end() {
this.ws.close();
}
}
class WebsocketServer extends server_1.default {
i = 0;
clientsPerIp = {};
listen(port, host) {
// implement it with websocket instead
// eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
const self = this;
if (port === undefined) {
this.socketServer = {
close() {
// self.emit('close')
},
};
}
else {
let server;
if (nextWebsocketOptions?.server) {
server = nextWebsocketOptions.server;
}
const ws = new ws_1.WebSocketServer({
port: server ? undefined : port,
server: server,
host
});
this.socketServer = ws;
ws.on('connection', (webSocket, req) => {
self.newConnection(webSocket, req);
});
self.socketServer.on('error', err => {
self.emit('error', err);
});
self.socketServer.on('close', () => {
self.emit('close');
});
self.socketServer.on('listening', () => {
self.emit('listening');
});
}
}
newConnection(webSocket, req) {
// eslint-disable-next-line unicorn/no-this-assignment, @typescript-eslint/no-this-alias
const self = this;
const _socket = webSocket;
const options = this.options;
const versionData = {
time: Date.now(),
version: this.version,
replEnabled: options.allowEval === true,
consoleEnabled: options.sendConsole === true,
requiresPass: Boolean(options.password),
forwardChat: options.forwardChat === true,
takeoverMode: options.takeoverMode === true,
apiVersion: -1
// todo
};
const socket = new WebsocketConnectionSocket(_socket, versionData, this.options.password);
//@ts-expect-error
const client = new minecraft_protocol_1.Client(true, this.version, this.customPackets, this.hideErrors);
//@ts-expect-error
client._end = client.end;
client.end = function (endReason, fullReason = JSON.stringify({ text: endReason })) {
if (client.state === minecraft_protocol_1.states.PLAY) {
client.write('kick_disconnect', { reason: fullReason });
}
else if (client.state === minecraft_protocol_1.states.LOGIN) {
client.write('disconnect', { reason: fullReason });
}
//@ts-expect-error
client._end(endReason);
};
const ip = req.headers['cf-connecting-ip'] || req.headers['x-forwarded-for']?.split?.(',')?.[0] || req.connection?.remoteAddress || req.socket.remoteAddress;
// Check IP whitelist if configured
if (Array.isArray(this.options.ipFilter)) {
if (!this.options.ipFilter.includes(ip)) {
client.end('Your IP is not whitelisted');
return;
}
}
_socket.remoteAddress = ip;
client.id = ip + this.i++;
socket.id = client.id;
this.clients[client.id] = client;
this.clientsPerIp[ip] ??= 0;
this.clientsPerIp[ip]++;
if (this.clientsPerIp[ip] > 3) {
client.end('Too many connections from your IP');
return;
}
client.on('end', () => {
delete self.clients[client.id];
self.clientsPerIp[ip]--;
});
//@ts-expect-error
client.setSocket(socket);
this.emit('connection', client);
}
close() {
for (const clientId of Object.keys(this.clients)) {
const client = this.clients[clientId];
client.end('ServerShutdown');
}
this.socketServer.close();
}
writeToClients(clients, name, params) {
if (clients.length === 0)
return;
try {
const buffer = this.serializer.createPacketBuffer({ name, params });
for (const client of clients)
client.writeRaw(buffer);
}
catch (err) {
console.error(`Something went wrong with sending packet ${name} to ${clients.length} clients with params ${JSON.stringify(params)}`);
console.error(err);
}
}
}
exports.default = WebsocketServer;