CyberChef/src/web/waiters/BackgroundWorkerWaiter.mjs

178 lines
4.7 KiB
JavaScript

/**
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import ChefWorker from "worker-loader?inline=no-fallback!../../core/ChefWorker.js";
/**
* Waiter to handle conversations with a ChefWorker in the background.
*/
class BackgroundWorkerWaiter {
/**
* BackgroundWorkerWaiter constructor.
*
* @param {App} app - The main view object for CyberChef.
* @param {Manager} manager - The CyberChef event manager.
*/
constructor(app, manager) {
this.app = app;
this.manager = manager;
this.callbacks = {};
this.callbackID = 0;
this.completedCallback = -1;
this.timeout = null;
}
/**
* Sets up the ChefWorker and associated listeners.
*/
registerChefWorker() {
log.debug("Registering new background ChefWorker");
this.chefWorker = new ChefWorker();
this.chefWorker.addEventListener("message", this.handleChefMessage.bind(this));
this.chefWorker.postMessage({
action: "setLogPrefix",
data: "BGChefWorker"
});
this.chefWorker.postMessage({
action: "setLogLevel",
data: log.getLevel()
});
let docURL = document.location.href.split(/[#?]/)[0];
const index = docURL.lastIndexOf("/");
if (index > 0) {
docURL = docURL.substring(0, index);
}
this.chefWorker.postMessage({"action": "docURL", "data": docURL});
}
/**
* Handler for messages sent back by the ChefWorker.
*
* @param {MessageEvent} e
*/
handleChefMessage(e) {
const r = e.data;
log.debug(`Receiving '${r.action}' from BGChefWorker`);
switch (r.action) {
case "bakeComplete":
case "bakeError":
if (typeof r.data.id !== "undefined") {
clearTimeout(this.timeout);
this.callbacks[r.data.id].bind(this)(r.data);
this.completedCallback = r.data.id;
}
break;
case "workerLoaded":
log.debug("Background ChefWorker loaded");
break;
case "optionUpdate":
case "statusMessage":
case "progressMessage":
// Ignore these messages
break;
default:
log.error("Unrecognised message from background ChefWorker", e);
break;
}
}
/**
* Cancels the current bake by terminating the ChefWorker and creating a new one.
*/
cancelBake() {
if (this.chefWorker)
this.chefWorker.terminate();
this.registerChefWorker();
}
/**
* Asks the ChefWorker to bake the input using the specified recipe.
*
* @param {string} input
* @param {Object[]} recipeConfig
* @param {Object} options
* @param {number} progress
* @param {boolean} step
* @param {Function} callback
*/
bake(input, recipeConfig, options, progress, step, callback) {
const id = this.callbackID++;
this.callbacks[id] = callback;
this.chefWorker.postMessage({
action: "bake",
data: {
input: input,
recipeConfig: recipeConfig,
options: options,
progress: progress,
step: step,
id: id
}
});
}
/**
* Asks the Magic operation what it can do with the input data.
*
* @param {string|ArrayBuffer} input
*/
magic(input) {
// If we're still working on the previous bake, cancel it before starting a new one.
if (this.completedCallback + 1 < this.callbackID) {
clearTimeout(this.timeout);
this.cancelBake();
}
this.bake(input, [
{
"op": "Magic",
"args": [3, false, false]
}
], {}, 0, false, this.magicComplete);
// Cancel this bake if it takes too long.
this.timeout = setTimeout(this.cancelBake.bind(this), 3000);
}
/**
* Handler for completed Magic bakes.
*
* @param {Object} response
*/
magicComplete(response) {
log.debug("--- Background Magic Bake complete ---");
if (!response || response.error) return;
this.manager.output.backgroundMagicResult(response.dish.value);
}
/**
* Sets the console log level in the workers.
*/
setLogLevel() {
if (!this.chefWorker) return;
this.chefWorker.postMessage({
action: "setLogLevel",
data: log.getLevel()
});
}
}
export default BackgroundWorkerWaiter;