faketerminal.js (23525B)
1 if (!window.FakeTerminal) { 2 window.FakeTerminal = {}; 3 } 4 5 if (!window.FakeTerminal.command) { 6 window.FakeTerminal.command = {}; 7 } 8 9 window.FakeTerminal.defaultOptions = { 10 username: "root", 11 hostname: window.location.host, 12 history: 1e3, 13 prompt: "[%username%@%hostname%: %cwd%] ", 14 login: null, 15 cwd: "~" 16 }; 17 18 window.FakeTerminal.main = function(el, options) { 19 var base = this; 20 if (!jQuery) { 21 throw "FakeTerminal: jQuery required"; 22 } else { 23 var $ = jQuery; 24 } 25 base.$el = $(el); 26 base.originalHtml = ""; 27 base.existingText = []; 28 base.executingCommand = { 29 instance: null, 30 deferred: null 31 }; 32 base.keymap = { 33 ENTER: 13, 34 UP: 38, 35 DOWN: 40, 36 C: 67, 37 D: 68, 38 U: 85 39 }; 40 base.output = null; 41 base.input = null; 42 base.filesystem = null; 43 base.history = null; 44 base.__construct = function() { 45 base.$el.trigger("ft:init", [ base ]); 46 base.options = $.extend({}, window.FakeTerminal.defaultOptions, options); 47 base.originalHtml = base.$el.get(0).outerHTML; 48 base.existingText = base.$el.get(0).innerHTML ? base.$el.get(0).innerHTML.split("\n") : []; 49 base.$el.addClass("faketerminal").empty(); 50 base.bindListeners(); 51 base.output = new window.FakeTerminal.output(base); 52 base.input = new window.FakeTerminal.input(base); 53 base.filesystem = new window.FakeTerminal.filesystem(base); 54 base.history = new window.FakeTerminal.history(base); 55 for (var i = 0, j = base.existingText.length; i < j; i++) { 56 if (base.existingText.length > 1 && i === 0) { 57 continue; 58 } else if (base.existingText.length > 1 && i === base.existingText.length - 1) { 59 continue; 60 } 61 base.output.write($.trim(base.existingText[i])); 62 } 63 base.input.focus(); 64 base.$el.trigger("ft:ready", [ base ]); 65 if (base.options.login) { 66 base.exec(base.options.login); 67 } 68 }; 69 base.bindListeners = function() { 70 base.$el.on("click", function() { 71 base.input.focus(); 72 }).on("keyup", function(e) { 73 if (e.ctrlKey && e.which === base.keymap.C) { 74 base.input.ctrlC(); 75 } else if (e.ctrlKey && e.which === base.keymap.U) { 76 base.input.ctrlU(); 77 } 78 }); 79 }; 80 base.findCommand = function(command) { 81 var cmdInstance; 82 if (typeof window.FakeTerminal.command[command] === "function") { 83 cmdInstance = new window.FakeTerminal.command[command](base); 84 } 85 return cmdInstance; 86 }; 87 base.getPrompt = function() { 88 var hostname, username, cwd, text; 89 if (typeof base.options.hostname === "function") { 90 hostname = base.options.hostname.call(); 91 } else { 92 hostname = base.options.hostname; 93 } 94 if (typeof base.options.username === "function") { 95 username = base.options.username.call(); 96 } else { 97 username = base.options.username; 98 } 99 if (typeof base.options.cwd === "function") { 100 cwd = base.options.cwd.call(); 101 } else { 102 cwd = base.options.cwd; 103 } 104 username = username.toLowerCase().replace(/[^a-z0-9]/g, ""); 105 text = base.options.prompt; 106 text = text.replace(/%hostname%/g, hostname); 107 text = text.replace(/%username%/g, username); 108 text = text.replace(/%cwd%/g, cwd); 109 return base.colorize(text); 110 }; 111 base.colorize = function(line) { 112 line = line.replace(/<([a-zA-Z].+?)>/g, '<span class="color--$1">', line); 113 line = line.replace(/<\/([a-zA-Z].+)>/g, "</span>", line); 114 return line; 115 }; 116 base.scrollToBottom = function() { 117 base.$el.scrollTop(base.$el.get(0).scrollHeight); 118 return base; 119 }; 120 base.destroy = function() { 121 base.$el.replaceWith($(base.originalHtml)); 122 base.$el.trigger("ft:destroy", [ base ]); 123 return base; 124 }; 125 base.exec = function(commandString, hidden) { 126 var deferred, command, userArgs, commandInstance; 127 deferred = new $.Deferred(); 128 commandString = $.trim(commandString); 129 if (!hidden) { 130 base.output.write(commandString, true); 131 } 132 if (commandString.length === 0) { 133 deferred.reject(); 134 return deferred.promise(); 135 } 136 command = $.trim(commandString.split(" ").slice(0, 1)); 137 userArgs = commandString.split(" ").slice(1); 138 commandInstance = base.findCommand(command); 139 if (commandInstance) { 140 if (base.executingCommand.instance) { 141 base.executingCommand.instance.terminate(); 142 base.executingCommand.instance = null; 143 base.executingCommand.deferred = null; 144 } 145 base.input.disable(); 146 base.executingCommand.instance = commandInstance; 147 base.executingCommand.deferred = commandInstance.execute.apply(commandInstance, userArgs).done(function() { 148 deferred.resolve(arguments); 149 }).fail(function() { 150 deferred.reject(arguments); 151 }).always(function() { 152 base.input.enable().focus(); 153 }); 154 } else if (command.length > 0) { 155 if (!hidden) { 156 base.output.write('command not found: "' + command + '"'); 157 } 158 deferred.reject(); 159 } else { 160 deferred.reject(); 161 } 162 if (!hidden) { 163 base.history.push(command); 164 } 165 base.$el.trigger("ft:command", [ base, command ]); 166 return deferred.promise(); 167 }; 168 base.__construct(); 169 }; 170 171 window.FakeTerminal.output = function(instance) { 172 var base = this; 173 base.$screen = null; 174 base.__construct = function() { 175 base.$screen = $("<div>").addClass("faketerminal__screen"); 176 instance.$el.append(base.$screen); 177 return base; 178 }; 179 base.write = function(line, prompt) { 180 var $line = $("<div>").addClass("faketerminal__screen__line"); 181 if (prompt) { 182 $line.append($("<div>").addClass("faketerminal__prompt").html(instance.getPrompt())); 183 } 184 line = instance.colorize(line); 185 line = line.replace(/ /g, " ", line); 186 line = line.replace(/<span class="/g, '<span class="', line); 187 line = line.replace(/<div class="/g, '<div class="', line); 188 $line.append(line); 189 base.$screen.append($line); 190 instance.scrollToBottom(); 191 return base; 192 }; 193 base.clear = function() { 194 base.$screen.empty(); 195 return base; 196 }; 197 return base.__construct(); 198 }; 199 200 window.FakeTerminal.input = function(instance) { 201 var base = this; 202 base.$input = null; 203 base.$request = null; 204 base.$commandLine = null; 205 base.$inputRequest = null; 206 base.__construct = function() { 207 base.$prompt = $("<div>").addClass("faketerminal__prompt").attr("autocorrect", "off").attr("autocapitalize", "none").html(instance.getPrompt()); 208 base.$input = $("<input>").on("keyup", function(e) { 209 switch (e.which) { 210 case instance.keymap.ENTER: 211 instance.exec(base.read()).done(function() { 212 base.$prompt.html(instance.getPrompt()); 213 }); 214 break; 215 216 case instance.keymap.UP: 217 case instance.keymap.DOWN: 218 base.set(instance.history.browse(e.which)); 219 break; 220 } 221 }); 222 base.$commandLine = $("<div>").addClass("faketerminal__commandline"); 223 base.$request = $("<input>"); 224 base.$inputRequest = $("<div>").addClass("faketerminal__commandline faketerminal__commandline--request"); 225 instance.$el.append(base.$commandLine.append(base.$prompt).append(base.$input)).append(base.$inputRequest.append(base.$request)); 226 base.enable(); 227 base.disableRequest(); 228 return base; 229 }; 230 base.read = function() { 231 var value = base.$input.val(); 232 base.$input.val(""); 233 return value; 234 }; 235 base.request = function(type) { 236 type = type || "TEXT"; 237 type = type.toUpperCase(); 238 switch (type) { 239 case "TEXT": 240 return base.requestText(); 241 break; 242 243 case "BOOL": 244 case "BOOLEAN": 245 return base.requestBool(); 246 break; 247 248 case "PASSWORD": 249 return base.requestPassword(); 250 break; 251 252 default: 253 throw "Invalid request type"; 254 break; 255 } 256 }; 257 base.requestText = function(muteOutput) { 258 var deferred = new $.Deferred(); 259 base.enableRequest(); 260 base.$request.on("keyup", function(e) { 261 if (e.which === instance.keymap.ENTER) { 262 var value = $.trim(base.$request.val()); 263 if (!muteOutput) { 264 instance.output.write(value); 265 } 266 deferred.resolve(value); 267 instance.$el.trigger("ft:command", [ instance, value ]); 268 } 269 }); 270 deferred.always(function() { 271 base.disableRequest(); 272 base.$request.off("keyup"); 273 }); 274 return deferred.promise(); 275 }; 276 base.requestBool = function() { 277 var deferred = new $.Deferred(); 278 base.requestText().done(function(value) { 279 value = $.trim(String(value).toLowerCase()); 280 if ([ "1", "true", "yes", "y", "ok" ].indexOf(value) !== -1) { 281 deferred.resolve(); 282 } else { 283 deferred.reject(); 284 } 285 }); 286 return deferred.promise(); 287 }; 288 base.requestPassword = function() { 289 var deferred = new $.Deferred(); 290 base.$request.addClass("is-password"); 291 instance.output.write("Password:"); 292 base.requestText(true).done(function(value) { 293 base.$request.removeClass("is-password"); 294 deferred.resolve(value); 295 }); 296 return deferred.promise(); 297 }; 298 base.set = function(command) { 299 base.$input.val(command); 300 }; 301 base.focus = function() { 302 if (base.$input.is(":visible")) { 303 base.$input.focus(); 304 } else if (base.$request.is(":visible")) { 305 base.$request.focus(); 306 } 307 return base; 308 }; 309 base.enable = function() { 310 base.$commandLine.show(); 311 base.focus(); 312 return base; 313 }; 314 base.disable = function() { 315 base.$commandLine.hide(); 316 base.$input.val(""); 317 return base; 318 }; 319 base.enableRequest = function() { 320 base.$inputRequest.show(); 321 base.focus(); 322 return base; 323 }; 324 base.disableRequest = function() { 325 base.$inputRequest.hide(); 326 base.$request.val(""); 327 return base; 328 }; 329 base.ctrlC = function() { 330 if (instance.executingCommand.instance) { 331 instance.executingCommand.instance.terminate(); 332 instance.executingCommand.instance = null; 333 instance.executingCommand.deferred = null; 334 instance.output.write("^C", true); 335 } else { 336 var value = base.read(); 337 if (value.length) { 338 instance.output.write(value, true); 339 instance.output.write("^C", true); 340 } 341 instance.output.write("", true); 342 } 343 base.disableRequest(); 344 base.focus(); 345 return base; 346 }; 347 base.ctrlU = function() { 348 base.$input.val(""); 349 return base; 350 }; 351 return base.__construct(); 352 }; 353 354 window.FakeTerminal.filesystem = function(instance) { 355 var base = this; 356 base.__construct = function() { 357 return base; 358 }; 359 return base.__construct(); 360 }; 361 362 window.FakeTerminal.history = function(instance) { 363 var base = this; 364 base.counter = 0; 365 base.items = []; 366 base.browseIndex = null; 367 base.__construct = function() { 368 return base; 369 }; 370 base.push = function(command) { 371 base.counter++; 372 base.items.push({ 373 counter: base.counter, 374 command: command 375 }); 376 if (base.items.length > instance.options.history) { 377 base.items = base.items.slice(base.items.length - instance.options.history); 378 } 379 }; 380 base.browse = function(direction) { 381 if (direction === instance.keymap.UP) { 382 if (base.browseIndex === null) { 383 base.browseIndex = base.items.length; 384 } 385 base.browseIndex--; 386 if (base.browseIndex < 0) { 387 base.browseIndex = 0; 388 } 389 } else if (direction === instance.keymap.DOWN) { 390 if (base.browseIndex === null) { 391 base.browseIndex = 0; 392 } 393 base.browseIndex++; 394 if (base.browseIndex >= base.items.length) { 395 base.browseIndex = base.items.length; 396 } 397 } 398 if (base.items[base.browseIndex]) { 399 return base.items[base.browseIndex].command; 400 } else { 401 return null; 402 } 403 }; 404 return base.__construct(); 405 }; 406 407 window.FakeTerminal.command = function(instance) { 408 var base = this; 409 base.deferred = new $.Deferred(); 410 base.info = function() { 411 return { 412 private: true 413 }; 414 }; 415 base.execute = function() { 416 base.deferred.resolve(); 417 return base.deferred.promise(); 418 }; 419 base.terminate = function() { 420 base.deferred.reject(); 421 }; 422 }; 423 424 window.FakeTerminal.command.challenge1 = function(instance) { 425 window.FakeTerminal.command.apply(this, arguments); 426 const base = this; 427 base.info = () => { 428 return { 429 description: "Enter challenge 1" 430 }; 431 }; 432 base.execute = () => { 433 window.location = "/challenge/1"; 434 base.deferred.resolve(); 435 return base.deferred.promise(); 436 }; 437 return base; 438 }; 439 440 window.FakeTerminal.command.challenge2 = function(instance) { 441 window.FakeTerminal.command.apply(this, arguments); 442 const base = this; 443 base.info = () => { 444 return { 445 description: "Enter challenge 2" 446 }; 447 }; 448 base.execute = () => { 449 window.location = "/challenge/2"; 450 base.deferred.resolve(); 451 return base.deferred.promise(); 452 }; 453 return base; 454 }; 455 456 window.FakeTerminal.command.challenge3 = function(instance) { 457 window.FakeTerminal.command.apply(this, arguments); 458 const base = this; 459 base.info = () => { 460 return { 461 description: "Enter challenge 3" 462 }; 463 }; 464 base.execute = () => { 465 window.location = "/challenge/3"; 466 base.deferred.resolve(); 467 return base.deferred.promise(); 468 }; 469 return base; 470 }; 471 472 window.FakeTerminal.command.clear = function(instance) { 473 window.FakeTerminal.command.apply(this, arguments); 474 const base = this; 475 base.info = function() { 476 return { 477 description: "Clears the screen" 478 }; 479 }; 480 base.execute = function() { 481 instance.output.clear(); 482 base.deferred.resolve(); 483 return base.deferred.promise(); 484 }; 485 return base; 486 }; 487 488 window.FakeTerminal.command.echo = function(instance) { 489 window.FakeTerminal.command.apply(this, arguments); 490 const base = this; 491 base.info = function() { 492 return { 493 description: "Writes an argument to the standard output" 494 }; 495 }; 496 base.execute = function() { 497 const args = $.makeArray(arguments); 498 let returnVal; 499 returnVal = args.join(" "); 500 returnVal = $.trim(returnVal); 501 returnVal = returnVal.replace(/["']/g, ""); 502 returnVal = returnVal.replace(/["']/g, ""); 503 if (returnVal.length === 0) { 504 returnVal = " "; 505 } 506 instance.output.write(returnVal); 507 base.deferred.resolve(); 508 return base.deferred.promise(); 509 }; 510 return base; 511 }; 512 513 window.FakeTerminal.command.help = function(instance) { 514 window.FakeTerminal.command.apply(this, arguments); 515 const base = this; 516 base.info = function() { 517 return { 518 description: "Displays information about the available commands" 519 }; 520 }; 521 base.execute = function() { 522 let returnVal = []; 523 let commandInfo = {}; 524 if (arguments.length === 0) { 525 returnVal.push("The following commands are available, run <info>help [command]</info> to find out more."); 526 returnVal.push(" "); 527 let commandString = ""; 528 $.each(window.FakeTerminal.command, function(command) { 529 const temp = instance.findCommand(command); 530 if (!temp) { 531 return; 532 } 533 if (typeof temp.info === "function") { 534 commandInfo = temp.info(); 535 if (typeof commandInfo.private === "boolean" && commandInfo.private === true) { 536 return; 537 } 538 } 539 commandString += command + " "; 540 }); 541 returnVal.push(commandString); 542 returnVal.push(" "); 543 } else { 544 const command = instance.findCommand(arguments[0]); 545 if (command) { 546 if (typeof command.info === "function") { 547 commandInfo = command.info(); 548 if (typeof commandInfo.description === "string") { 549 returnVal = [ " ", arguments[0] + " -- <comment>" + commandInfo.description + "</comment>", " " ]; 550 } else if (typeof commandInfo.description === "object") { 551 returnVal = commandInfo.description; 552 } 553 } 554 if (returnVal.length === 0) { 555 returnVal = [ " ", 'No description for "' + command + '"', " " ]; 556 } 557 } else { 558 returnVal = [ " ", '"' + command + '" is not a valid command', " " ]; 559 } 560 } 561 for (let i = 0; i < returnVal.length; i++) { 562 instance.output.write(returnVal[i]); 563 } 564 base.deferred.resolve(); 565 return base.deferred.promise(); 566 }; 567 return base; 568 }; 569 570 window.FakeTerminal.command.history = function(instance) { 571 window.FakeTerminal.command.apply(this, arguments); 572 const base = this; 573 base.info = function() { 574 return { 575 description: "Displays the command history, up to " + instance.options.historyLength + " items" 576 }; 577 }; 578 base.execute = function() { 579 instance.output.write(" "); 580 for (var i = 0; i < instance.history.items.length; i++) { 581 instance.output.write(instance.history.items[i].counter + " " + instance.history.items[i].command); 582 } 583 instance.output.write(" "); 584 base.deferred.resolve(); 585 return base.deferred.promise(); 586 }; 587 return base; 588 }; 589 590 window.FakeTerminal.command.login = function(instance) { 591 window.FakeTerminal.command.apply(this, arguments); 592 const base = this; 593 base.info = () => { 594 return { 595 description: "Login into an existing account" 596 }; 597 }; 598 base.execute = () => { 599 let username; 600 let password; 601 instance.output.write("Username: "); 602 instance.input.request("text").done(value => { 603 username = value; 604 instance.input.request("password").done(async value => { 605 password = value; 606 if (await login(username, password)) { 607 window.location = "/"; 608 } else { 609 instance.output.write("<comment>Username / password combination is wrong</comment>"); 610 } 611 base.deferred.resolve(); 612 }); 613 }).fail(() => { 614 instance.output.write("<error>Something went wrong.</error>"); 615 base.deferred.resolve(); 616 }); 617 return base.deferred.promise(); 618 }; 619 return base; 620 }; 621 622 window.FakeTerminal.command.logout = function(instance) { 623 window.FakeTerminal.command.apply(this, arguments); 624 const base = this; 625 base.info = () => { 626 return { 627 description: "Logout of the current session" 628 }; 629 }; 630 base.execute = async () => { 631 destroyStorage(); 632 if (!await logout()) { 633 instance.output.write("Could not logout!"); 634 } else { 635 window.location = "/"; 636 } 637 base.deferred.resolve(); 638 return base.deferred.promise(); 639 }; 640 }; 641 642 window.FakeTerminal.command.ls = function(instance) { 643 window.FakeTerminal.command.apply(this, arguments); 644 const base = this; 645 base.info = () => { 646 return { 647 description: "List the files in the current working directory" 648 }; 649 }; 650 base.execute = () => { 651 instance.output.write("This is a test"); 652 base.deferred.resolve(); 653 return base.deferred.promise(); 654 }; 655 return base; 656 }; 657 658 window.FakeTerminal.command.register = function(instance) { 659 window.FakeTerminal.command.apply(this, arguments); 660 const base = this; 661 base.info = () => { 662 return { 663 description: "Register an account" 664 }; 665 }; 666 base.execute = () => { 667 let username; 668 let password; 669 instance.output.write("Username: "); 670 instance.input.request("text").done(value => { 671 username = value; 672 instance.input.request("password").done(async value => { 673 password = value; 674 if (await register(username, password)) { 675 instance.output.write(`<comment>User ${username} registered successfully!</comment>`); 676 } else { 677 instance.output.write("<comment>Could not register, something went wrong</comment>"); 678 } 679 base.deferred.resolve(); 680 }); 681 }).fail(() => { 682 instance.output.write("<error>Something went wrong.</error>"); 683 base.deferred.resolve(); 684 }); 685 return base.deferred.promise(); 686 }; 687 return base; 688 }; 689 690 window.FakeTerminal.command.user = function(instance) { 691 window.FakeTerminal.command.apply(this, arguments); 692 const base = this; 693 base.info = () => { 694 return { 695 description: "Configure your user, in our special GUI" 696 }; 697 }; 698 base.execute = () => { 699 window.location = "/userpanel/"; 700 base.deferred.resolve(); 701 return base.deferred.promise(); 702 }; 703 return base; 704 }; 705 706 window.FakeTerminal.command.whoami = function(instance) { 707 window.FakeTerminal.command.apply(this, arguments); 708 const base = this; 709 base.info = function() { 710 return { 711 description: "Prints the user's username to standard output" 712 }; 713 }; 714 base.execute = function() { 715 instance.output.write(instance.options.username); 716 base.deferred.resolve(); 717 return base.deferred.promise(); 718 }; 719 return base; 720 }; 721 722 (function($) { 723 $.fn.faketerminal = function(options) { 724 return this.each(function() { 725 $(this).data("instance", new window.FakeTerminal.main(this, options)); 726 }); 727 }; 728 })(jQuery); 729 //# sourceMappingURL=faketerminal.js.map