@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
269 lines (268 loc) • 10.5 kB
JavaScript
"use strict";
/**
* Electron Preload Script
*
* This script runs in a privileged context before the renderer process loads.
* It exposes a safe API to the renderer via contextBridge.
*
* SECURITY: This script acts as a bridge between the main process (Node.js)
* and the renderer process (web). It carefully validates all operations
* before allowing them.
*
* NOTE: This file is compiled separately with contextIsolation support.
* It uses CommonJS (require) because Electron's preload scripts don't
* support ESM in the same way as the main process.
*/
Object.defineProperty(exports, "__esModule", { value: true });
const electron_1 = require("electron");
const _allowedExtensions = [
"js",
"ts",
"mjs",
"json",
"md",
"png",
"jpg",
"jpeg",
"lang",
"fsb",
"map",
"ogg",
"flac",
"hdr",
"psd",
"env",
"gif",
"wav",
"tga",
"env",
"mjs",
"wlist",
"brarchive",
"nbt",
"webm",
"svg",
"otf",
"ttf",
"bin",
"obj",
"pdn",
"zip",
"py",
"h",
"fontdata",
"properties",
"cartobackup",
"mctbackup",
"",
"mcstructure",
"mcworld",
"mcproject",
"map",
"js.map",
"mctemplate",
"material",
"vertex",
"md",
"geometry",
"fragment",
"mcfunction",
"mcaddon",
"mcpack",
"html",
"dat",
"dat_old",
"txt",
"ldb",
"log",
"in",
"cmake",
];
const _allowedExecutableExtensions = ["mcstructure", "mcworld", "mctemplate", "mcaddon"];
function _getTypeFromName(name) {
const nameW = name.trim().toLowerCase();
const lastBackslash = nameW.lastIndexOf("\\");
const lastSlash = nameW.lastIndexOf("/");
const lastPeriod = nameW.lastIndexOf(".");
if (lastPeriod < 0 || lastPeriod < lastSlash || lastPeriod < lastBackslash) {
return "";
}
return nameW.substring(lastPeriod + 1, nameW.length);
}
function _canonicalizePathForValidation(path) {
if ((path[0] !== "<" || path[5] !== ">") && !path.startsWith("<pt_")) {
throw new Error("PLD: Unsupported canon path: " + path);
}
if (path.startsWith("<EDUP>") ||
path.startsWith("<EDUR>") ||
path.startsWith("<BDRK>") ||
path.startsWith("<BDPV>") ||
path.startsWith("<MCPE>") ||
path.startsWith("<DOCP>")) {
path = path.substring(6);
}
else if (path.startsWith("<pt_")) {
const endGreater = path.indexOf(">", 4);
if (endGreater > 4) {
path = path.substring(endGreater + 1);
}
}
else {
throw new Error("PLD: Unsupported canon path A: " + path);
}
return path;
}
function _validateFolderPath(path) {
path = _canonicalizePathForValidation(path);
// banned character combos
if (path.indexOf("..") >= 0 || path.indexOf("\\\\") >= 0 || path.indexOf("//") >= 0) {
throw new Error("Unsupported path combinations: " + path);
}
if (path.lastIndexOf(":") >= 2) {
throw new Error("Unsupported drive location: " + path);
}
}
function _validateDoubleFolderPath(path) {
const pathArr = path.split("|");
if (pathArr.length !== 2) {
throw new Error("Unsupported double folder path: " + path);
}
_validateFolderPath(pathArr[0]);
_validateFolderPath(pathArr[1]);
}
function _validateFilePath(path) {
_validateFolderPath(path);
const extension = _getTypeFromName(path);
if (!_allowedExtensions.includes(extension)) {
throw new Error("PLD: Unsupported file type: " + path);
}
}
function _validateExecutableFilePath(path) {
_validateFolderPath(path);
const extension = _getTypeFromName(path);
if (!_allowedExecutableExtensions.includes(extension)) {
throw new Error("PLD: Unsupported executable file type: " + path);
}
}
// Valid channels for Agent IPC (uses invoke pattern directly)
const _agentChannels = [
"agent:start",
"agent:stop",
"agent:createSession",
"agent:send",
"agent:abort",
"agent:destroySession",
"agent:getAuthStatus",
"agent:listModels",
"agent:updateContext",
];
electron_1.contextBridge.exposeInMainWorld("api", {
// Direct invoke for Agent channels (cleaner API)
invoke: (channel, data) => {
if (_agentChannels.includes(channel)) {
return electron_1.ipcRenderer.invoke(channel, data);
}
throw new Error("PLD: Invalid invoke channel: " + channel);
},
send: (channel, commandName, data) => {
const validChannels = ["appweb"];
if (validChannels.includes(channel)) {
const pipe = commandName.indexOf("|");
let position = -1;
if (pipe >= 0) {
position = parseInt(commandName.substring(pipe + 1, commandName.length));
commandName = commandName.substring(0, pipe);
}
switch (commandName) {
case "asyncopenFolder":
electron_1.ipcRenderer.invoke("asyncselectDirectory", position + "|" + data);
return;
case "asyncconvertFile":
return electron_1.ipcRenderer.invoke("asyncconvertFile", position + "|" + data);
case "asyncstartWebSocketServer":
return electron_1.ipcRenderer.invoke("asyncstartWebSocketServer", position + "|" + data);
case "asyncstopWebSocketServer":
return electron_1.ipcRenderer.invoke("asyncstopWebSocketServer", position + "|" + data);
case "asyncstartDedicatedServer":
return electron_1.ipcRenderer.invoke("asyncstartDedicatedServer", position + "|" + data);
case "asyncstopDedicatedServer":
return electron_1.ipcRenderer.invoke("asyncstopDedicatedServer", position + "|" + data);
case "asyncdedicatedServerCommand":
return electron_1.ipcRenderer.invoke("asyncdedicatedServerCommand", position + "|" + data);
case "asyncwebSocketCommand":
return electron_1.ipcRenderer.invoke("asyncwebSocketCommand", position + "|" + data);
case "asyncshellOpenPath":
_validateExecutableFilePath(data);
return electron_1.ipcRenderer.invoke("asyncshellOpenPath", position + "|" + data);
case "asyncshellOpenFolderInExplorer":
return electron_1.ipcRenderer.invoke("asyncshellOpenFolderInExplorer", position + "|" + data);
case "asyncminecraftShell":
return electron_1.ipcRenderer.invoke("asyncminecraftShell", position + "|" + data);
case "asyncshellRecycleItem":
_validateFilePath(data);
return electron_1.ipcRenderer.invoke("asyncshellRecycleItem", position + "|" + data);
case "asyncreloadMct":
return electron_1.ipcRenderer.invoke("asyncreloadMct", position + "|" + data);
case "asyncgetDedicatedServerProjectDir":
return electron_1.ipcRenderer.invoke("asyncgetDedicatedServerProjectDir", position + "|" + data);
case "asyncgetDedicatedServerStatus":
return electron_1.ipcRenderer.invoke("asyncgetDedicatedServerStatus", position + "|" + data);
case "asyncgetDedicatedServerWorldDir":
return electron_1.ipcRenderer.invoke("asyncgetDedicatedServerWorldDir", position + "|" + data);
case "asyncgetMinecraftGameProjectDeployDir":
return electron_1.ipcRenderer.invoke("asyncgetMinecraftGameProjectDeployDir", position + "|" + data);
case "asyncgetMinecraftGameWorldDeployDir":
return electron_1.ipcRenderer.invoke("asyncgetMinecraftGameWorldDeployDir", position + "|" + data);
case "asyncwindowClose":
case "asynclogToConsole":
case "asyncwindowRestore":
case "asyncwindowMinimize":
case "asyncwindowMove":
case "asyncwindowMaximize":
case "asyncwindowUpdate":
case "asyncwindowLeftSide":
case "asyncwindowRightSide":
case "asyncupdateIAgree":
case "asyncgetWindowState":
case "asyncgetPlatform":
case "asyncgetDirname":
case "asyncgetContentSources":
case "asynccontentSourceLogin":
return electron_1.ipcRenderer.invoke(commandName, position + "|" + data);
case "getIsDev":
return electron_1.ipcRenderer.invoke("getIsDev", data);
case "asyncfsRenameFolder":
_validateDoubleFolderPath(data);
return electron_1.ipcRenderer.invoke(commandName, position + "|" + data);
case "asyncfsDeleteFolder":
_validateFolderPath(data);
return electron_1.ipcRenderer.invoke(commandName, position + "|" + data);
case "asyncfsExists":
case "bsyncfsReadFile":
case "asyncfsReadUtf8File":
_validateFilePath(data);
return electron_1.ipcRenderer.invoke(commandName, position + "|" + data);
case "asyncfsFolderExists":
case "asyncfsRootStorageExists":
case "asyncfsMkdir":
case "asyncfsReaddir":
case "asyncfsStat":
_validateFolderPath(data);
return electron_1.ipcRenderer.invoke(commandName, position + "|" + data);
case "asyncfsWriteFile":
case "asyncfsWriteUtf8File":
const writeFilePath = data.path;
_validateFilePath(writeFilePath);
return electron_1.ipcRenderer.invoke(commandName, position + "|" + data.path + "|" + data.content);
default:
throw new Error("PLD: Unknown command: " + commandName);
}
}
},
receive: (channel, func) => {
const validChannels = ["appsvc", "agent:event", "error:fatal"];
if (validChannels.includes(channel)) {
electron_1.ipcRenderer.on(channel, (_event, ...args) => func(...args));
}
},
});