bedrocklive
Version:
Turn your Minecraft streams into interactive TikTok experiences. Connect live stream events directly to your game world.
128 lines (127 loc) • 5.27 kB
JavaScript
import { WebSocket } from "ws";
import chalk from "chalk";
import { EventEmitter } from "events";
import { v4 as uuidv4 } from "uuid";
import MinecraftCommandHandler from "../minecraft/minecraft-command-handler.js";
export default class MinecraftConnection extends EventEmitter {
static _instance;
_minecraftWss;
_commandHandler;
_activeClient;
constructor(minecraftWss) {
super();
this._minecraftWss = minecraftWss;
this._commandHandler = new MinecraftCommandHandler();
this.listen();
}
static instance(minecraftWss) {
if (!this._instance) {
this._instance = new MinecraftConnection(minecraftWss);
}
return this._instance;
}
listen() {
this._minecraftWss.on("connection", (ws) => {
if (this._activeClient && this._activeClient.readyState === WebSocket.OPEN) {
console.warn(chalk.yellow("New Minecraft client connected, but an active one already exists. Closing new connection."));
ws.close();
return;
}
console.log(chalk.greenBright("Minecraft Connection Established!"));
this._activeClient = ws;
this._commandHandler.setActiveConnection(ws);
ws.on("message", (message) => {
try {
const msg = JSON.parse(message.toString());
if (!msg.header || !msg.header.messagePurpose) {
console.warn(chalk.yellow(`Received message without a valid header: ${message}`));
return;
}
if (msg.header.messagePurpose === "event") {
if (typeof msg !== "object" && !msg.header && !msg.body) {
console.warn(chalk.yellow("Received invalid event message: ", msg));
return;
}
this.emit(msg.header.eventName, msg);
}
else if (msg.header.messagePurpose === "commandResponse") {
this._commandHandler.handleCommandResponse(msg);
}
else if (msg.header.messagePurpose === "error") {
console.error(chalk.red(`Minecraft Error Message: ${msg.body?.statusCode} - ${msg.body?.statusMessage}`));
if (msg.header.requestId && msg.body) {
this._commandHandler.handleCommandResponse(msg);
}
}
else {
console.warn(chalk.yellow(`Unknown message purpose: ${msg.header.messagePurpose}`), msg);
}
}
catch (error) {
console.error(chalk.yellowBright(`Failed to parse message: ${error.message}`), `Message: ${message}`);
}
});
ws.on("error", (error) => {
console.error(chalk.red(`Minecraft connection error: ${error}`));
if (this._activeClient === ws) {
this._activeClient = undefined;
this._commandHandler.setActiveConnection(undefined);
}
});
ws.on("close", () => {
console.log(chalk.yellowBright("Minecraft connection closed."));
if (this._activeClient === ws) {
this._activeClient = undefined;
this._commandHandler.setActiveConnection(undefined);
}
this.emit("disconnected");
});
this.emit("connected", ws);
});
}
flushQueue() {
this._commandHandler.flushQueue();
}
subscribeToEvents(events) {
const sendSubscription = (client) => {
events.forEach((event) => {
const subscribeRequest = {
header: {
version: 1,
requestId: uuidv4(),
messagePurpose: "subscribe",
messageType: "commandRequest",
},
body: {
eventName: event,
},
};
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(subscribeRequest));
}
});
};
if (this._activeClient && this._activeClient.readyState === WebSocket.OPEN) {
sendSubscription(this._activeClient);
}
else {
this.once('connected', sendSubscription);
}
}
/**
* runs a minecraft command
* @param {string} command The command to send.
*/
requestCommand(command) {
const commandRequest = this._commandHandler.createCommandRequest(command);
this._commandHandler.enqueueCommand(commandRequest);
}
/**
* Sends a script event to the Minecraft server.
* @param {string} eventId The ID of the event.
* @param {string} message The message to send with the event.
*/
sendScriptEvent(eventId, message) {
this.requestCommand(`scriptevent ${eventId} ${message}`);
}
}