App.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786
  1. import Utils from "../core/Utils.js";
  2. import ChefWorker from "worker-loader!../core/ChefWorker.js";
  3. import Manager from "./Manager.js";
  4. import HTMLCategory from "./HTMLCategory.js";
  5. import HTMLOperation from "./HTMLOperation.js";
  6. import Split from "split.js";
  7. /**
  8. * HTML view for CyberChef responsible for building the web page and dealing with all user
  9. * interactions.
  10. *
  11. * @author n1474335 [n1474335@gmail.com]
  12. * @copyright Crown Copyright 2016
  13. * @license Apache-2.0
  14. *
  15. * @constructor
  16. * @param {CatConf[]} categories - The list of categories and operations to be populated.
  17. * @param {Object.<string, OpConf>} operations - The list of operation configuration objects.
  18. * @param {String[]} defaultFavourites - A list of default favourite operations.
  19. * @param {Object} options - Default setting for app options.
  20. */
  21. const App = function(categories, operations, defaultFavourites, defaultOptions) {
  22. this.categories = categories;
  23. this.operations = operations;
  24. this.dfavourites = defaultFavourites;
  25. this.doptions = defaultOptions;
  26. this.options = Utils.extend({}, defaultOptions);
  27. this.manager = new Manager(this);
  28. this.baking = false;
  29. this.autoBake_ = false;
  30. this.autoBakePause = false;
  31. this.progress = 0;
  32. this.ingId = 0;
  33. };
  34. /**
  35. * This function sets up the stage and creates listeners for all events.
  36. *
  37. * @fires Manager#appstart
  38. */
  39. App.prototype.setup = function() {
  40. document.dispatchEvent(this.manager.appstart);
  41. this.registerChefWorker();
  42. this.initialiseSplitter();
  43. this.loadLocalStorage();
  44. this.populateOperationsList();
  45. this.manager.setup();
  46. this.resetLayout();
  47. this.setCompileMessage();
  48. this.loadURIParams();
  49. this.appLoaded = true;
  50. this.loaded();
  51. };
  52. /**
  53. * Sets up the ChefWorker and associated listeners.
  54. */
  55. App.prototype.registerChefWorker = function() {
  56. this.chefWorker = new ChefWorker();
  57. this.chefWorker.addEventListener("message", this.handleChefMessage.bind(this));
  58. };
  59. /**
  60. * Fires once all setup activities have completed.
  61. */
  62. App.prototype.loaded = function() {
  63. // Check that both the app and the worker have loaded successfully, and that
  64. // we haven't already loaded before attempting to remove the loading screen.
  65. if (!this.workerLoaded || !this.appLoaded ||
  66. !document.getElementById("loader-wrapper")) return;
  67. // Trigger CSS animations to remove preloader
  68. document.body.classList.add("loaded");
  69. // Wait for animations to complete then remove the preloader and loaded style
  70. // so that the animations for existing elements don't play again.
  71. setTimeout(function() {
  72. document.getElementById("loader-wrapper").remove();
  73. document.body.classList.remove("loaded");
  74. }, 1000);
  75. // Clear the loading message interval
  76. clearInterval(window.loadingMsgsInt);
  77. };
  78. /**
  79. * An error handler for displaying the error to the user.
  80. *
  81. * @param {Error} err
  82. */
  83. App.prototype.handleError = function(err) {
  84. console.error(err);
  85. const msg = err.displayStr || err.toString();
  86. this.alert(msg, "danger", this.options.errorTimeout, !this.options.showErrors);
  87. };
  88. /**
  89. * Updates the UI to show if baking is in process or not.
  90. *
  91. * @param {bakingStatus}
  92. */
  93. App.prototype.setBakingStatus = function(bakingStatus) {
  94. this.baking = bakingStatus;
  95. let outputLoader = document.getElementById("output-loader"),
  96. outputElement = document.getElementById("output-text");
  97. if (bakingStatus) {
  98. this.manager.controls.hideStaleIndicator();
  99. this.bakingStatusTimeout = setTimeout(function() {
  100. outputElement.disabled = true;
  101. outputLoader.style.visibility = "visible";
  102. outputLoader.style.opacity = 1;
  103. this.manager.controls.toggleBakeButtonFunction(true);
  104. }.bind(this), 200);
  105. } else {
  106. clearTimeout(this.bakingStatusTimeout);
  107. outputElement.disabled = false;
  108. outputLoader.style.opacity = 0;
  109. outputLoader.style.visibility = "hidden";
  110. this.manager.controls.toggleBakeButtonFunction(false);
  111. }
  112. };
  113. /**
  114. * Asks the ChefWorker to bake the current input using the current recipe.
  115. *
  116. * @param {boolean} [step] - Set to true if we should only execute one operation instead of the
  117. * whole recipe.
  118. */
  119. App.prototype.bake = function(step) {
  120. if (this.baking) return;
  121. this.setBakingStatus(true);
  122. this.chefWorker.postMessage({
  123. action: "bake",
  124. data: {
  125. input: this.getInput(), // The user's input
  126. recipeConfig: this.getRecipeConfig(), // The configuration of the recipe
  127. options: this.options, // Options set by the user
  128. progress: this.progress, // The current position in the recipe
  129. step: step // Whether or not to take one step or execute the whole recipe
  130. }
  131. });
  132. };
  133. /**
  134. * Cancels the current bake by terminating the ChefWorker and creating a new one.
  135. */
  136. App.prototype.cancelBake = function() {
  137. this.chefWorker.terminate();
  138. this.registerChefWorker();
  139. this.setBakingStatus(false);
  140. this.manager.controls.showStaleIndicator();
  141. };
  142. /**
  143. * Handler for messages sent back by the ChefWorker.
  144. *
  145. * @param {MessageEvent} e
  146. */
  147. App.prototype.handleChefMessage = function(e) {
  148. switch (e.data.action) {
  149. case "bakeSuccess":
  150. this.bakingComplete(e.data.data);
  151. break;
  152. case "bakeError":
  153. this.handleError(e.data.data);
  154. this.setBakingStatus(false);
  155. break;
  156. case "silentBakeComplete":
  157. break;
  158. case "workerLoaded":
  159. this.workerLoaded = true;
  160. this.loaded();
  161. break;
  162. default:
  163. console.error("Unrecognised message from ChefWorker", e);
  164. break;
  165. }
  166. };
  167. /**
  168. * Handler for completed bakes.
  169. *
  170. * @param {Object} response
  171. */
  172. App.prototype.bakingComplete = function(response) {
  173. this.setBakingStatus(false);
  174. if (!response) return;
  175. if (response.error) {
  176. this.handleError(response.error);
  177. }
  178. this.options = response.options;
  179. this.dishStr = response.type === "html" ? Utils.stripHtmlTags(response.result, true) : response.result;
  180. this.progress = response.progress;
  181. this.manager.recipe.updateBreakpointIndicator(response.progress);
  182. this.manager.output.set(response.result, response.type, response.duration);
  183. // If baking took too long, disable auto-bake
  184. if (response.duration > this.options.autoBakeThreshold && this.autoBake_) {
  185. this.manager.controls.setAutoBake(false);
  186. this.alert("Baking took longer than " + this.options.autoBakeThreshold +
  187. "ms, Auto Bake has been disabled.", "warning", 5000);
  188. }
  189. };
  190. /**
  191. * Runs Auto Bake if it is set.
  192. */
  193. App.prototype.autoBake = function() {
  194. if (this.autoBake_ && !this.autoBakePause && !this.baking) {
  195. this.bake();
  196. } else {
  197. this.manager.controls.showStaleIndicator();
  198. }
  199. };
  200. /**
  201. * Asks the ChefWorker to run a silent bake, forcing the browser to load and cache all the relevant
  202. * JavaScript code needed to do a real bake.
  203. *
  204. * The output will not be modified (hence "silent" bake). This will only actually execute the recipe
  205. * if auto-bake is enabled, otherwise it will just wake up the ChefWorker with an empty recipe.
  206. */
  207. App.prototype.silentBake = function() {
  208. let recipeConfig = [];
  209. if (this.autoBake_) {
  210. // If auto-bake is not enabled we don't want to actually run the recipe as it may be disabled
  211. // for a good reason.
  212. recipeConfig = this.getRecipeConfig();
  213. }
  214. this.chefWorker.postMessage({
  215. action: "silentBake",
  216. data: {
  217. recipeConfig: recipeConfig
  218. }
  219. });
  220. };
  221. /**
  222. * Gets the user's input data.
  223. *
  224. * @returns {string}
  225. */
  226. App.prototype.getInput = function() {
  227. const input = this.manager.input.get();
  228. // Save to session storage in case we need to restore it later
  229. sessionStorage.setItem("inputLength", input.length);
  230. sessionStorage.setItem("input", input);
  231. return input;
  232. };
  233. /**
  234. * Sets the user's input data.
  235. *
  236. * @param {string} input - The string to set the input to
  237. */
  238. App.prototype.setInput = function(input) {
  239. sessionStorage.setItem("inputLength", input.length);
  240. sessionStorage.setItem("input", input);
  241. this.manager.input.set(input);
  242. };
  243. /**
  244. * Populates the operations accordion list with the categories and operations specified in the
  245. * view constructor.
  246. *
  247. * @fires Manager#oplistcreate
  248. */
  249. App.prototype.populateOperationsList = function() {
  250. // Move edit button away before we overwrite it
  251. document.body.appendChild(document.getElementById("edit-favourites"));
  252. let html = "";
  253. let i;
  254. for (i = 0; i < this.categories.length; i++) {
  255. let catConf = this.categories[i],
  256. selected = i === 0,
  257. cat = new HTMLCategory(catConf.name, selected);
  258. for (let j = 0; j < catConf.ops.length; j++) {
  259. let opName = catConf.ops[j],
  260. op = new HTMLOperation(opName, this.operations[opName], this, this.manager);
  261. cat.addOperation(op);
  262. }
  263. html += cat.toHtml();
  264. }
  265. document.getElementById("categories").innerHTML = html;
  266. const opLists = document.querySelectorAll("#categories .op-list");
  267. for (i = 0; i < opLists.length; i++) {
  268. opLists[i].dispatchEvent(this.manager.oplistcreate);
  269. }
  270. // Add edit button to first category (Favourites)
  271. document.querySelector("#categories a").appendChild(document.getElementById("edit-favourites"));
  272. };
  273. /**
  274. * Sets up the adjustable splitter to allow the user to resize areas of the page.
  275. */
  276. App.prototype.initialiseSplitter = function() {
  277. this.columnSplitter = Split(["#operations", "#recipe", "#IO"], {
  278. sizes: [20, 30, 50],
  279. minSize: [240, 325, 450],
  280. gutterSize: 4,
  281. onDrag: function() {
  282. this.manager.controls.adjustWidth();
  283. this.manager.output.adjustWidth();
  284. }.bind(this)
  285. });
  286. this.ioSplitter = Split(["#input", "#output"], {
  287. direction: "vertical",
  288. gutterSize: 4,
  289. });
  290. this.resetLayout();
  291. };
  292. /**
  293. * Loads the information previously saved to the HTML5 local storage object so that user options
  294. * and favourites can be restored.
  295. */
  296. App.prototype.loadLocalStorage = function() {
  297. // Load options
  298. let lOptions;
  299. if (localStorage.options !== undefined) {
  300. lOptions = JSON.parse(localStorage.options);
  301. }
  302. this.manager.options.load(lOptions);
  303. // Load favourites
  304. this.loadFavourites();
  305. };
  306. /**
  307. * Loads the user's favourite operations from the HTML5 local storage object and populates the
  308. * Favourites category with them.
  309. * If the user currently has no saved favourites, the defaults from the view constructor are used.
  310. */
  311. App.prototype.loadFavourites = function() {
  312. let favourites = localStorage.favourites &&
  313. localStorage.favourites.length > 2 ?
  314. JSON.parse(localStorage.favourites) :
  315. this.dfavourites;
  316. favourites = this.validFavourites(favourites);
  317. this.saveFavourites(favourites);
  318. const favCat = this.categories.filter(function(c) {
  319. return c.name === "Favourites";
  320. })[0];
  321. if (favCat) {
  322. favCat.ops = favourites;
  323. } else {
  324. this.categories.unshift({
  325. name: "Favourites",
  326. ops: favourites
  327. });
  328. }
  329. };
  330. /**
  331. * Filters the list of favourite operations that the user had stored and removes any that are no
  332. * longer available. The user is notified if this is the case.
  333. * @param {string[]} favourites - A list of the user's favourite operations
  334. * @returns {string[]} A list of the valid favourites
  335. */
  336. App.prototype.validFavourites = function(favourites) {
  337. const validFavs = [];
  338. for (let i = 0; i < favourites.length; i++) {
  339. if (this.operations.hasOwnProperty(favourites[i])) {
  340. validFavs.push(favourites[i]);
  341. } else {
  342. this.alert("The operation \"" + Utils.escapeHtml(favourites[i]) +
  343. "\" is no longer available. It has been removed from your favourites.", "info");
  344. }
  345. }
  346. return validFavs;
  347. };
  348. /**
  349. * Saves a list of favourite operations to the HTML5 local storage object.
  350. *
  351. * @param {string[]} favourites - A list of the user's favourite operations
  352. */
  353. App.prototype.saveFavourites = function(favourites) {
  354. localStorage.setItem("favourites", JSON.stringify(this.validFavourites(favourites)));
  355. };
  356. /**
  357. * Resets favourite operations back to the default as specified in the view constructor and
  358. * refreshes the operation list.
  359. */
  360. App.prototype.resetFavourites = function() {
  361. this.saveFavourites(this.dfavourites);
  362. this.loadFavourites();
  363. this.populateOperationsList();
  364. this.manager.recipe.initialiseOperationDragNDrop();
  365. };
  366. /**
  367. * Adds an operation to the user's favourites.
  368. *
  369. * @param {string} name - The name of the operation
  370. */
  371. App.prototype.addFavourite = function(name) {
  372. const favourites = JSON.parse(localStorage.favourites);
  373. if (favourites.indexOf(name) >= 0) {
  374. this.alert("'" + name + "' is already in your favourites", "info", 2000);
  375. return;
  376. }
  377. favourites.push(name);
  378. this.saveFavourites(favourites);
  379. this.loadFavourites();
  380. this.populateOperationsList();
  381. this.manager.recipe.initialiseOperationDragNDrop();
  382. };
  383. /**
  384. * Checks for input and recipe in the URI parameters and loads them if present.
  385. */
  386. App.prototype.loadURIParams = function() {
  387. // Load query string or hash from URI (depending on which is populated)
  388. const params = window.location.search || window.location.hash;
  389. this.uriParams = Utils.parseURIParams(params);
  390. // Pause auto-bake while loading but don't modify `this.autoBake_`
  391. // otherwise `manualBake` cannot trigger.
  392. this.autoBakePause = true;
  393. // Read in recipe from URI params
  394. if (this.uriParams.recipe) {
  395. try {
  396. const recipeConfig = JSON.parse(this.uriParams.recipe);
  397. this.setRecipeConfig(recipeConfig);
  398. } catch (err) {}
  399. } else if (this.uriParams.op) {
  400. // If there's no recipe, look for single operations
  401. this.manager.recipe.clearRecipe();
  402. try {
  403. this.manager.recipe.addOperation(this.uriParams.op);
  404. } catch (err) {
  405. // If no exact match, search for nearest match and add that
  406. const matchedOps = this.manager.ops.filterOperations(this.uriParams.op, false);
  407. if (matchedOps.length) {
  408. this.manager.recipe.addOperation(matchedOps[0].name);
  409. }
  410. // Populate search with the string
  411. const search = document.getElementById("search");
  412. search.value = this.uriParams.op;
  413. search.dispatchEvent(new Event("search"));
  414. }
  415. }
  416. // Read in input data from URI params
  417. if (this.uriParams.input) {
  418. try {
  419. const inputData = Utils.fromBase64(this.uriParams.input);
  420. this.setInput(inputData);
  421. } catch (err) {}
  422. }
  423. // Unpause auto-bake
  424. this.autoBakePause = false;
  425. this.autoBake();
  426. };
  427. /**
  428. * Returns the next ingredient ID and increments it for next time.
  429. *
  430. * @returns {number}
  431. */
  432. App.prototype.nextIngId = function() {
  433. return this.ingId++;
  434. };
  435. /**
  436. * Gets the current recipe configuration.
  437. *
  438. * @returns {Object[]}
  439. */
  440. App.prototype.getRecipeConfig = function() {
  441. const recipeConfig = this.manager.recipe.getConfig();
  442. sessionStorage.setItem("recipeConfig", JSON.stringify(recipeConfig));
  443. return recipeConfig;
  444. };
  445. /**
  446. * Given a recipe configuration, sets the recipe to that configuration.
  447. *
  448. * @param {Object[]} recipeConfig - The recipe configuration
  449. */
  450. App.prototype.setRecipeConfig = function(recipeConfig) {
  451. sessionStorage.setItem("recipeConfig", JSON.stringify(recipeConfig));
  452. document.getElementById("rec-list").innerHTML = null;
  453. for (let i = 0; i < recipeConfig.length; i++) {
  454. const item = this.manager.recipe.addOperation(recipeConfig[i].op);
  455. // Populate arguments
  456. const args = item.querySelectorAll(".arg");
  457. for (let j = 0; j < args.length; j++) {
  458. if (args[j].getAttribute("type") === "checkbox") {
  459. // checkbox
  460. args[j].checked = recipeConfig[i].args[j];
  461. } else if (args[j].classList.contains("toggle-string")) {
  462. // toggleString
  463. args[j].value = recipeConfig[i].args[j].string;
  464. args[j].previousSibling.children[0].innerHTML =
  465. Utils.escapeHtml(recipeConfig[i].args[j].option) +
  466. " <span class='caret'></span>";
  467. } else {
  468. // all others
  469. args[j].value = recipeConfig[i].args[j];
  470. }
  471. }
  472. // Set disabled and breakpoint
  473. if (recipeConfig[i].disabled) {
  474. item.querySelector(".disable-icon").click();
  475. }
  476. if (recipeConfig[i].breakpoint) {
  477. item.querySelector(".breakpoint").click();
  478. }
  479. this.progress = 0;
  480. }
  481. };
  482. /**
  483. * Resets the splitter positions to default.
  484. */
  485. App.prototype.resetLayout = function() {
  486. this.columnSplitter.setSizes([20, 30, 50]);
  487. this.ioSplitter.setSizes([50, 50]);
  488. this.manager.controls.adjustWidth();
  489. this.manager.output.adjustWidth();
  490. };
  491. /**
  492. * Sets the compile message.
  493. */
  494. App.prototype.setCompileMessage = function() {
  495. // Display time since last build and compile message
  496. let now = new Date(),
  497. timeSinceCompile = Utils.fuzzyTime(now.getTime() - window.compileTime),
  498. compileInfo = "<span style=\"font-weight: normal\">Last build: " +
  499. timeSinceCompile.substr(0, 1).toUpperCase() + timeSinceCompile.substr(1) + " ago";
  500. if (window.compileMessage !== "") {
  501. compileInfo += " - " + window.compileMessage;
  502. }
  503. compileInfo += "</span>";
  504. document.getElementById("notice").innerHTML = compileInfo;
  505. };
  506. /**
  507. * Pops up a message to the user and writes it to the console log.
  508. *
  509. * @param {string} str - The message to display (HTML supported)
  510. * @param {string} style - The colour of the popup
  511. * "danger" = red
  512. * "warning" = amber
  513. * "info" = blue
  514. * "success" = green
  515. * @param {number} timeout - The number of milliseconds before the popup closes automatically
  516. * 0 for never (until the user closes it)
  517. * @param {boolean} [silent=false] - Don't show the message in the popup, only print it to the
  518. * console
  519. *
  520. * @example
  521. * // Pops up a red box with the message "[current time] Error: Something has gone wrong!"
  522. * // that will need to be dismissed by the user.
  523. * this.alert("Error: Something has gone wrong!", "danger", 0);
  524. *
  525. * // Pops up a blue information box with the message "[current time] Happy Christmas!"
  526. * // that will disappear after 5 seconds.
  527. * this.alert("Happy Christmas!", "info", 5000);
  528. */
  529. App.prototype.alert = function(str, style, timeout, silent) {
  530. const time = new Date();
  531. console.log("[" + time.toLocaleString() + "] " + str);
  532. if (silent) return;
  533. style = style || "danger";
  534. timeout = timeout || 0;
  535. let alertEl = document.getElementById("alert"),
  536. alertContent = document.getElementById("alert-content");
  537. alertEl.classList.remove("alert-danger");
  538. alertEl.classList.remove("alert-warning");
  539. alertEl.classList.remove("alert-info");
  540. alertEl.classList.remove("alert-success");
  541. alertEl.classList.add("alert-" + style);
  542. // If the box hasn't been closed, append to it rather than replacing
  543. if (alertEl.style.display === "block") {
  544. alertContent.innerHTML +=
  545. "<br><br>[" + time.toLocaleTimeString() + "] " + str;
  546. } else {
  547. alertContent.innerHTML =
  548. "[" + time.toLocaleTimeString() + "] " + str;
  549. }
  550. // Stop the animation if it is in progress
  551. $("#alert").stop();
  552. alertEl.style.display = "block";
  553. alertEl.style.opacity = 1;
  554. if (timeout > 0) {
  555. clearTimeout(this.alertTimeout);
  556. this.alertTimeout = setTimeout(function(){
  557. $("#alert").slideUp(100);
  558. }, timeout);
  559. }
  560. };
  561. /**
  562. * Pops up a box asking the user a question and sending the answer to a specified callback function.
  563. *
  564. * @param {string} title - The title of the box
  565. * @param {string} body - The question (HTML supported)
  566. * @param {function} callback - A function accepting one boolean argument which handles the
  567. * response e.g. function(answer) {...}
  568. * @param {Object} [scope=this] - The object to bind to the callback function
  569. *
  570. * @example
  571. * // Pops up a box asking if the user would like a cookie. Prints the answer to the console.
  572. * this.confirm("Question", "Would you like a cookie?", function(answer) {console.log(answer);});
  573. */
  574. App.prototype.confirm = function(title, body, callback, scope) {
  575. scope = scope || this;
  576. document.getElementById("confirm-title").innerHTML = title;
  577. document.getElementById("confirm-body").innerHTML = body;
  578. document.getElementById("confirm-modal").style.display = "block";
  579. this.confirmClosed = false;
  580. $("#confirm-modal").modal()
  581. .one("show.bs.modal", function(e) {
  582. this.confirmClosed = false;
  583. }.bind(this))
  584. .one("click", "#confirm-yes", function() {
  585. this.confirmClosed = true;
  586. callback.bind(scope)(true);
  587. $("#confirm-modal").modal("hide");
  588. }.bind(this))
  589. .one("hide.bs.modal", function(e) {
  590. if (!this.confirmClosed)
  591. callback.bind(scope)(false);
  592. this.confirmClosed = true;
  593. }.bind(this));
  594. };
  595. /**
  596. * Handler for the alert close button click event.
  597. * Closes the alert box.
  598. */
  599. App.prototype.alertCloseClick = function() {
  600. document.getElementById("alert").style.display = "none";
  601. };
  602. /**
  603. * Handler for CyerChef statechange events.
  604. * Fires whenever the input or recipe changes in any way.
  605. *
  606. * @listens Manager#statechange
  607. * @param {event} e
  608. */
  609. App.prototype.stateChange = function(e) {
  610. this.autoBake();
  611. // Update the current history state (not creating a new one)
  612. if (this.options.updateUrl) {
  613. this.lastStateUrl = this.manager.controls.generateStateUrl(true, true);
  614. window.history.replaceState({}, "CyberChef", this.lastStateUrl);
  615. }
  616. };
  617. /**
  618. * Handler for the history popstate event.
  619. * Reloads parameters from the URL.
  620. *
  621. * @param {event} e
  622. */
  623. App.prototype.popState = function(e) {
  624. this.loadURIParams();
  625. };
  626. /**
  627. * Function to call an external API from this view.
  628. */
  629. App.prototype.callApi = function(url, type, data, dataType, contentType) {
  630. type = type || "POST";
  631. data = data || {};
  632. dataType = dataType || undefined;
  633. contentType = contentType || "application/json";
  634. let response = null,
  635. success = false;
  636. $.ajax({
  637. url: url,
  638. async: false,
  639. type: type,
  640. data: data,
  641. dataType: dataType,
  642. contentType: contentType,
  643. success: function(data) {
  644. success = true;
  645. response = data;
  646. },
  647. error: function(data) {
  648. success = false;
  649. response = data;
  650. },
  651. });
  652. return {
  653. success: success,
  654. response: response
  655. };
  656. };
  657. export default App;