UNPKG

@minecraft/creator-tools

Version:

Minecraft Creator Tools command line and libraries.

432 lines (431 loc) 16 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const ws_1 = __importStar(require("ws")); const uuid_1 = require("uuid"); const http = __importStar(require("http")); const ste_events_1 = require("ste-events"); const Log_1 = __importDefault(require("../core/Log")); const ICreatorToolsData_1 = require("../app/ICreatorToolsData"); const NodeStorage_1 = __importDefault(require("./NodeStorage")); const fs = __importStar(require("fs")); const Utilities_1 = __importDefault(require("../core/Utilities")); const SecurityUtilities_1 = __importDefault(require("../core/SecurityUtilities")); class MinecraftWebSocketServer { _port = 19136; server; _wss; _ws; _env; _pendingCommands = []; _pendingCommandIds = []; _pendingRequestIds = []; _pendingData = []; _curEventIndex = 0; _eventSeed; _onClientConnected = new ste_events_1.EventDispatcher(); _onClientDisconnected = new ste_events_1.EventDispatcher(); _onCommandCompleted = new ste_events_1.EventDispatcher(); _onEventReceived = new ste_events_1.EventDispatcher(); _currentCommandId = 0; constructor(env) { this._env = env; this._eventSeed = Utilities_1.default.createRandomId(4); this._handleMessage = this._handleMessage.bind(this); this._handleConnection = this._handleConnection.bind(this); this.openServer = this.openServer.bind(this); this.executeNextCommand = this.executeNextCommand.bind(this); } get onCommandCompleted() { return this._onCommandCompleted.asEvent(); } get onEventReceived() { return this._onEventReceived.asEvent(); } get onClientConnected() { return this._onClientConnected.asEvent(); } get onClientDisconnected() { return this._onClientDisconnected.asEvent(); } runCommand(command, requestId, data) { // Security: Sanitize command const sanitizedCommand = SecurityUtilities_1.default.sanitizeCommand(command); if (!SecurityUtilities_1.default.isCommandSafe(sanitizedCommand)) { Log_1.default.message("WebSocket command rejected as unsafe: " + command); return; } const newCommand = this._pendingCommands.length; this._pendingCommands[newCommand] = sanitizedCommand; this._pendingCommandIds[newCommand] = (0, uuid_1.v4)(); this._pendingRequestIds[newCommand] = requestId; this._pendingData[newCommand] = data; if (newCommand === this._currentCommandId) { this.executeNextCommand(); } } executeNextCommand() { if (this._currentCommandId < this._pendingCommands.length) { this._currentCommandId++; const nextCommand = this._currentCommandId - 1; const commandLine = this._pendingCommands[nextCommand]; const commandId = this._pendingCommandIds[nextCommand]; const nextCommandStr = JSON.stringify({ header: { version: 1, requestId: commandId, messageType: "commandRequest", messagePurpose: "commandRequest", }, body: { // version: 22, NOTE: if version is not included in request msg, // MC *should* use latest command version commandLine: commandLine, origin: { type: "server", }, }, }); Log_1.default.message("Command " + this._currentCommandId + " sent:" + nextCommandStr); if (this._ws) { this._ws.send(nextCommandStr); } } } getWebSocketWorldPath(state) { if (state === ICreatorToolsData_1.MinecraftGameConnectionMode.remoteMinecraft) { return ""; } let sourcePath = ""; if (state === ICreatorToolsData_1.MinecraftGameConnectionMode.localMinecraftPreview) { sourcePath = this._env.utilities.minecraftPreviewUwpPath; } else { sourcePath = this._env.utilities.minecraftUwpPath; } const worldPath = NodeStorage_1.default.ensureEndsWithDelimiter(sourcePath) + "minecraftWorlds" + NodeStorage_1.default.platformFolderDelimiter; const subfolders = fs.readdirSync(worldPath); let maxTime = 0; let maxPath = ""; if (subfolders) { for (let i = 0; i < subfolders.length; i++) { const subfolder = subfolders[i]; const stat = fs.statSync(worldPath + subfolder); if (stat.isDirectory()) { if (stat.mtimeMs > maxTime) { maxTime = stat.mtimeMs; maxPath = worldPath + subfolder + NodeStorage_1.default.platformFolderDelimiter; } } } } console.log("Reading path " + worldPath + "|" + maxPath); return maxPath; } closeServer() { if (!this.server) { return; } } openServer() { if (this.server) { return; } const listenHost = "127.0.0.1"; console.log("Starting Minecraft websocket server on " + listenHost + ":" + this._port); this.server = http.createServer(); this._wss = new ws_1.WebSocketServer({ server: this.server, verifyClient: (info) => { const remoteAddress = info.req.socket.remoteAddress; return remoteAddress === "127.0.0.1" || remoteAddress === "::1" || remoteAddress === "::ffff:127.0.0.1"; }, }); this.server.on("error", (e) => { Log_1.default.message("Error on server." + e); }); this._wss.on("connection", this._handleConnection); this._wss.on("close", this._handleDisconnection); this._wss.on("error", (e) => { Log_1.default.message("Error on web socket server." + e); }); try { this.server.listen(this._port, listenHost, () => { Log_1.default.message("Minecraft websocket server started on " + listenHost + ":" + this._port + "."); }); } catch (e) { Log_1.default.message("Error opening port " + this._port + "."); } } _handleMessage(message) { if (!message) { return; } try { if (message.indexOf("{") >= 0) { const struct = JSON.parse(message); if (struct.body !== undefined && struct.header !== undefined && struct.header.messagePurpose !== undefined) { const purpose = struct.header.messagePurpose; if (purpose === "commandResponse") { Log_1.default.message("Received command response: " + struct.header.requestId + "(" + message + ")"); const commandId = struct.header.requestId.toLowerCase(); let found = false; for (let i = this._pendingCommandIds.length - 1; i >= 0; i--) { const _reqId = this._pendingCommandIds[i]; if (_reqId.toLowerCase() === commandId) { console.log("Found request id " + _reqId); this._onCommandCompleted.dispatch(this, { requestId: this._pendingRequestIds[i], result: struct.body, data: this._pendingData[i], }); found = true; break; } } if (!found) { Log_1.default.debug("Could not find a matching request for id " + commandId); } this.executeNextCommand(); } else if (purpose === "event") { Log_1.default.message("Received event: " + struct.header.eventName + "(" + message + ")"); struct.eventId = this._eventSeed + ++this._curEventIndex; this._onEventReceived.dispatch(this, struct); } } } } catch (e) { console.log("Error processing command: " + e); } } _handleConnection(ws) { // Close existing connection if present if (this._ws && this._ws.readyState === ws_1.default.OPEN) { Log_1.default.message("Closing existing WebSocket connection to accept new client"); this._ws.close(1000, "New client connected"); } this._ws = ws; ws.send(JSON.stringify({ header: { version: 1, requestId: (0, uuid_1.v4)(), messageType: "commandRequest", messagePurpose: "subscribe", }, body: { eventName: "PlayerMessage", }, })); ws.send(JSON.stringify({ header: { version: 1, requestId: (0, uuid_1.v4)(), messageType: "commandRequest", messagePurpose: "subscribe", }, body: { eventName: "PlayerTravelled", }, })); ws.send(JSON.stringify({ header: { version: 1, requestId: (0, uuid_1.v4)(), messageType: "commandRequest", messagePurpose: "subscribe", }, body: { eventName: "PlayerTeleported", }, })); ws.send(JSON.stringify({ header: { version: 1, requestId: (0, uuid_1.v4)(), messageType: "commandRequest", messagePurpose: "subscribe", }, body: { eventName: "WorldGenerated", }, })); ws.send(JSON.stringify({ header: { version: 1, requestId: (0, uuid_1.v4)(), messageType: "commandRequest", messagePurpose: "subscribe", }, body: { eventName: "WorldLoaded", }, })); ws.send(JSON.stringify({ header: { version: 1, requestId: (0, uuid_1.v4)(), messageType: "commandRequest", messagePurpose: "subscribe", }, body: { eventName: "PlayerJoin", }, })); ws.send(JSON.stringify({ header: { version: 1, requestId: (0, uuid_1.v4)(), messageType: "commandRequest", messagePurpose: "subscribe", }, body: { eventName: "PlayerLeave", }, })); ws.send(JSON.stringify({ header: { version: 1, requestId: (0, uuid_1.v4)(), messageType: "commandRequest", messagePurpose: "subscribe", }, body: { eventName: "SlashCommandExecuted", }, })); ws.send(JSON.stringify({ header: { version: 1, requestId: (0, uuid_1.v4)(), messageType: "commandRequest", messagePurpose: "subscribe", }, body: { eventName: "ItemAcquired", }, })); ws.send(JSON.stringify({ header: { version: 1, requestId: (0, uuid_1.v4)(), messageType: "commandRequest", messagePurpose: "subscribe", }, body: { eventName: "ItemCrafted", }, })); ws.send(JSON.stringify({ header: { version: 1, requestId: (0, uuid_1.v4)(), messageType: "commandRequest", messagePurpose: "subscribe", }, body: { eventName: "ItemEquipped", }, })); ws.send(JSON.stringify({ header: { version: 1, requestId: (0, uuid_1.v4)(), messageType: "commandRequest", messagePurpose: "subscribe", }, body: { eventName: "PlayerTransform", }, })); ws.send(JSON.stringify({ header: { version: 1, requestId: (0, uuid_1.v4)(), messageType: "commandRequest", messagePurpose: "subscribe", }, body: { eventName: "ItemUsed", }, })); ws.send(JSON.stringify({ header: { version: 1, requestId: (0, uuid_1.v4)(), messageType: "commandRequest", messagePurpose: "subscribe", }, body: { eventName: "ItemInteracted", }, })); ws.send(JSON.stringify({ header: { version: 1, requestId: (0, uuid_1.v4)(), messageType: "commandRequest", messagePurpose: "subscribe", }, body: { eventName: "BlockPlaced", }, })); ws.send(JSON.stringify({ header: { version: 1, requestId: (0, uuid_1.v4)(), messageType: "commandRequest", messagePurpose: "subscribe", }, body: { eventName: "BlockBroken", }, })); ws.on("message", this._handleMessage); console.log("Inbound web socket connection."); this._onClientConnected.dispatch(this, ws.url); } _handleDisconnection(ws) { this._ws = undefined; this._onClientDisconnected.dispatch(this, ws.url); } } exports.default = MinecraftWebSocketServer;