CyberChef/src/core/ChefWorker.js

290 lines
6.8 KiB
JavaScript

/**
* Web Worker to handle communications between the front-end and the core.
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
import Chef from "./Chef.mjs";
import OperationConfig from "./config/OperationConfig.json" assert {type: "json"};
import OpModules from "./config/modules/OpModules.mjs";
// Add ">" to the start of all log messages in the Chef Worker
import loglevelMessagePrefix from "loglevel-message-prefix";
loglevelMessagePrefix(log, {
prefixes: [],
staticPrefixes: [">"],
prefixFormat: "%p"
});
// Set up Chef instance
self.chef = new Chef();
self.OpModules = OpModules;
self.OperationConfig = OperationConfig;
self.inputNum = -1;
// Tell the app that the worker has loaded and is ready to operate
self.postMessage({
action: "workerLoaded",
data: {}
});
/**
* Respond to message from parent thread.
*
* inputNum is optional and only used for baking multiple inputs.
* Defaults to -1 when one isn't sent with the bake message.
*
* Messages should have the following format:
* {
* action: "bake" | "silentBake",
* data: {
* input: {string},
* recipeConfig: {[Object]},
* options: {Object},
* progress: {number},
* step: {boolean},
* [inputNum=-1]: {number}
* }
* }
*/
self.addEventListener("message", function(e) {
// Handle message
const r = e.data;
log.debug("ChefWorker receiving command '" + r.action + "'");
switch (r.action) {
case "bake":
bake(r.data);
break;
case "silentBake":
silentBake(r.data);
break;
case "getDishAs":
getDishAs(r.data);
break;
case "getDishTitle":
getDishTitle(r.data);
break;
case "docURL":
// Used to set the URL of the current document so that scripts can be
// imported into an inline worker.
self.docURL = r.data;
break;
case "highlight":
calculateHighlights(
r.data.recipeConfig,
r.data.direction,
r.data.pos
);
break;
case "setLogLevel":
log.setLevel(r.data, false);
break;
default:
break;
}
});
/**
* Baking handler
*
* @param {Object} data
*/
async function bake(data) {
// Ensure the relevant modules are loaded
self.loadRequiredModules(data.recipeConfig);
try {
self.inputNum = (data.inputNum !== undefined) ? data.inputNum : -1;
const response = await self.chef.bake(
data.input, // The user's input
data.recipeConfig, // The configuration of the recipe
data.options // Options set by the user
);
const transferable = (data.input instanceof ArrayBuffer) ? [data.input] : undefined;
self.postMessage({
action: "bakeComplete",
data: Object.assign(response, {
id: data.id,
inputNum: data.inputNum,
bakeId: data.bakeId
})
}, transferable);
} catch (err) {
self.postMessage({
action: "bakeError",
data: {
error: err.message || err,
id: data.id,
inputNum: data.inputNum
}
});
}
self.inputNum = -1;
}
/**
* Silent baking handler
*/
function silentBake(data) {
const duration = self.chef.silentBake(data.recipeConfig);
self.postMessage({
action: "silentBakeComplete",
data: duration
});
}
/**
* Translates the dish to a given type.
*/
async function getDishAs(data) {
const value = await self.chef.getDishAs(data.dish, data.type);
const transferable = (data.type === "ArrayBuffer") ? [value] : undefined;
self.postMessage({
action: "dishReturned",
data: {
value: value,
id: data.id
}
}, transferable);
}
/**
* Gets the dish title
*
* @param {object} data
* @param {Dish} data.dish
* @param {number} data.maxLength
* @param {number} data.id
*/
async function getDishTitle(data) {
const title = await self.chef.getDishTitle(data.dish, data.maxLength);
self.postMessage({
action: "dishReturned",
data: {
value: title,
id: data.id
}
});
}
/**
* Calculates highlight offsets if possible.
*
* @param {Object[]} recipeConfig
* @param {string} direction
* @param {Object} pos - The position object for the highlight.
* @param {number} pos.start - The start offset.
* @param {number} pos.end - The end offset.
*/
async function calculateHighlights(recipeConfig, direction, pos) {
pos = await self.chef.calculateHighlights(recipeConfig, direction, pos);
self.postMessage({
action: "highlightsCalculated",
data: pos
});
}
/**
* Checks that all required modules are loaded and loads them if not.
*
* @param {Object} recipeConfig
*/
self.loadRequiredModules = function(recipeConfig) {
recipeConfig.forEach(op => {
const module = self.OperationConfig[op.op].module;
if (!(module in OpModules)) {
log.info(`Loading ${module} module`);
self.sendStatusMessage(`Loading ${module} module`);
self.importScripts(`${self.docURL}/modules/${module}.js`); // lgtm [js/client-side-unvalidated-url-redirection]
self.sendStatusMessage("");
}
});
};
/**
* Send status update to the app.
*
* @param {string} msg
*/
self.sendStatusMessage = function(msg) {
self.postMessage({
action: "statusMessage",
data: {
message: msg,
inputNum: self.inputNum
}
});
};
/**
* Send progress update to the app.
*
* @param {number} progress
* @param {number} total
*/
self.sendProgressMessage = function(progress, total) {
self.postMessage({
action: "progressMessage",
data: {
progress: progress,
total: total,
inputNum: self.inputNum
}
});
};
/**
* Send an option value update to the app.
*
* @param {string} option
* @param {*} value
*/
self.setOption = function(option, value) {
self.postMessage({
action: "optionUpdate",
data: {
option: option,
value: value
}
});
};
/**
* Send register values back to the app.
*
* @param {number} opIndex
* @param {number} numPrevRegisters
* @param {string[]} registers
*/
self.setRegisters = function(opIndex, numPrevRegisters, registers) {
self.postMessage({
action: "setRegisters",
data: {
opIndex: opIndex,
numPrevRegisters: numPrevRegisters,
registers: registers
}
});
};