2016-03-12 13:50:43 -05:00
|
|
|
"use strict";
|
|
|
|
|
|
|
|
var gSessionId;
|
|
|
|
var gCredentials;
|
|
|
|
var gErrorCount = 0;
|
2016-04-06 21:44:22 -04:00
|
|
|
var gCommandHistory = [];
|
|
|
|
|
|
|
|
var kMaxCommandHistory = 16;
|
2016-03-12 13:50:43 -05:00
|
|
|
|
|
|
|
function enter(event) {
|
|
|
|
if (event.keyCode == 13) {
|
2016-04-06 21:44:22 -04:00
|
|
|
gCommandHistory.push(document.getElementById("input").value);
|
|
|
|
if (gCommandHistory.length > kMaxCommandHistory) {
|
|
|
|
gCommandHistory.shift();
|
|
|
|
}
|
2016-03-12 13:50:43 -05:00
|
|
|
send();
|
|
|
|
event.preventDefault();
|
2016-04-06 21:44:22 -04:00
|
|
|
} else if (event.keyCode == 38) {
|
|
|
|
if (gCommandHistory.length) {
|
|
|
|
var input = document.getElementById("input");
|
|
|
|
gCommandHistory.unshift(input.value);
|
|
|
|
input.value = gCommandHistory.pop();
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
} else if (event.keyCode == 40) {
|
|
|
|
if (gCommandHistory.length) {
|
|
|
|
var input = document.getElementById("input");
|
|
|
|
gCommandHistory.push(input.value);
|
|
|
|
input.value = gCommandHistory.shift();
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
2016-03-12 13:50:43 -05:00
|
|
|
} else if (event.keyCode == 186
|
|
|
|
&& !event.metaKey
|
|
|
|
&& !event.altKey
|
|
|
|
&& !event.ctrlKey
|
|
|
|
&& !event.shiftKey) {
|
2016-04-11 11:54:26 -04:00
|
|
|
var input = document.getElementById("input");
|
|
|
|
var value = input.value;
|
2016-03-12 13:50:43 -05:00
|
|
|
if (value && value[value.length - 1] == '\\') {
|
2016-04-11 11:54:26 -04:00
|
|
|
input.value = value.substring(0, value.length - 1) + ";";
|
2016-03-12 13:50:43 -05:00
|
|
|
event.preventDefault();
|
|
|
|
} else {
|
|
|
|
storeTarget(value);
|
2016-04-11 11:54:26 -04:00
|
|
|
input.value = "";
|
2016-03-12 13:50:43 -05:00
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function url() {
|
|
|
|
var hash = window.location.href.indexOf('#');
|
|
|
|
var question = window.location.href.indexOf('?');
|
2016-04-06 21:30:07 -04:00
|
|
|
var end = -1;
|
|
|
|
if (hash != -1 && (hash < end || end == -1))
|
|
|
|
{
|
|
|
|
end = hash;
|
|
|
|
}
|
|
|
|
if (question != -1 && (question < end || end == -1))
|
|
|
|
{
|
|
|
|
end = question;
|
|
|
|
}
|
2016-03-12 13:50:43 -05:00
|
|
|
return end != -1 ? window.location.href.substring(0, end) : window.location.href;
|
|
|
|
}
|
|
|
|
|
2016-04-06 21:30:07 -04:00
|
|
|
function hash() {
|
|
|
|
return window.location.hash != "#" ? window.location.hash : "";
|
|
|
|
}
|
|
|
|
|
2016-03-12 13:50:43 -05:00
|
|
|
function storeTarget(target) {
|
2016-04-11 11:54:26 -04:00
|
|
|
document.getElementById("target").innerText = target || "";
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function split(container, children) {
|
|
|
|
if (container) {
|
|
|
|
while (container.firstChild) {
|
|
|
|
container.removeChild(container.firstChild);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (children) {
|
|
|
|
for (var i = 0; i < children.length; i++) {
|
|
|
|
if (children[i].name) {
|
|
|
|
var node = document.createElement("div");
|
|
|
|
node.setAttribute("id", "terminal_" + children[i].name);
|
|
|
|
var grow = children[i].grow || "1";
|
|
|
|
var shrink = children[i].shrink || "1";
|
|
|
|
var basis = children[i].basis || "auto";
|
|
|
|
node.setAttribute("style", "flex: " + grow + " " + shrink + " " + basis);
|
2016-04-04 16:26:01 -04:00
|
|
|
|
|
|
|
var classes = ["terminal"];
|
|
|
|
if (children[i].type == "vertical") {
|
|
|
|
classes.push("vbox");
|
|
|
|
} else if (children[i].type == "horizontal") {
|
|
|
|
classes.push("hbox");
|
|
|
|
}
|
|
|
|
node.setAttribute("class", classes.join(" "));
|
2016-03-12 13:50:43 -05:00
|
|
|
container.appendChild(node);
|
|
|
|
} else if (children[i].type) {
|
|
|
|
node = document.createElement("div");
|
|
|
|
if (children[i].type == "horizontal") {
|
|
|
|
node.setAttribute("class", "hbox");
|
|
|
|
} else if (children[i].type == "vertical") {
|
|
|
|
node.setAttribute("class", "vbox");
|
|
|
|
}
|
|
|
|
container.appendChild(node);
|
|
|
|
split(node, children[i].children);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-04-10 20:09:21 -04:00
|
|
|
function receive(data) {
|
|
|
|
for (var i in data.lines) {
|
|
|
|
var line = data.lines[i];
|
|
|
|
|
|
|
|
var target = document.getElementsByClassName("terminal")[0].id;
|
|
|
|
if (line && line.terminal) {
|
|
|
|
if (document.getElementById("terminal_" + line.terminal)) {
|
|
|
|
target = "terminal_" + line.terminal;
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
2016-04-10 20:09:21 -04:00
|
|
|
line = line.value;
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
2016-04-10 20:09:21 -04:00
|
|
|
if (line && line.action == "ping") {
|
|
|
|
gSocket.send(JSON.stringify({action: "pong"}));
|
|
|
|
} else if (line && line.action == "session") {
|
2016-04-10 20:28:42 -04:00
|
|
|
gSessionId = line.sessionId;
|
|
|
|
gCredentials = line.credentials;
|
2016-04-10 20:09:21 -04:00
|
|
|
updateLogin();
|
|
|
|
} else if (line && line[0] && line[0].action == "ready") {
|
|
|
|
if (window.location.hash) {
|
|
|
|
send({event: "hashChange", hash: window.location.hash});
|
|
|
|
}
|
|
|
|
} else if (line && line[0] && line[0].action == "notify") {
|
|
|
|
new Notification(line[0].title, line[0].options);
|
|
|
|
} else if (line && line[0] && line[0].action == "title") {
|
|
|
|
window.document.title = line[0].value;
|
|
|
|
} else if (line && line[0] && line[0].action == "prompt") {
|
|
|
|
var prompt = document.getElementById("prompt");
|
|
|
|
while (prompt.firstChild) {
|
|
|
|
prompt.removeChild(prompt.firstChild);
|
|
|
|
}
|
|
|
|
prompt.appendChild(document.createTextNode(line[0].value));
|
|
|
|
} else if (line && line[0] && line[0].action == "password") {
|
|
|
|
var prompt = document.getElementById("input");
|
|
|
|
prompt.setAttribute("type", line[0].value ? "password" : "text");
|
|
|
|
} else if (line && line[0] && line[0].action == "hash") {
|
|
|
|
window.location.hash = line[0].value;
|
|
|
|
} else if (line && line[0] && line[0].action == "update") {
|
|
|
|
document.getElementById("update").setAttribute("Style", "display: inline");
|
|
|
|
} else if (line && line[0] && line[0].action == "split") {
|
|
|
|
split(document.getElementById("terminals"), line[0].options);
|
|
|
|
} else if (line && line[0] && line[0].action == "postMessageToIframe") {
|
|
|
|
var iframe = document.getElementById("iframe_" + line[0].name);
|
|
|
|
if (iframe) {
|
|
|
|
iframe.contentWindow.postMessage(line[0].message, "*");
|
|
|
|
}
|
2016-03-12 13:50:43 -05:00
|
|
|
} else {
|
2016-04-10 20:09:21 -04:00
|
|
|
print(document.getElementById(target), line);
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
2016-04-10 20:09:21 -04:00
|
|
|
}
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function autoNewLine(terminal) {
|
|
|
|
terminal.appendChild(document.createElement("br"));
|
|
|
|
}
|
|
|
|
|
|
|
|
function print(terminal, data) {
|
|
|
|
autoNewLine(terminal);
|
|
|
|
printStructured(terminal, data);
|
|
|
|
autoScroll(terminal);
|
|
|
|
}
|
|
|
|
|
|
|
|
function printStructured(container, data) {
|
|
|
|
if (typeof data == "string") {
|
|
|
|
container.appendChild(document.createTextNode(data));
|
|
|
|
} else if (data && data[0] !== undefined) {
|
|
|
|
for (var i in data) {
|
|
|
|
printStructured(container, data[i]);
|
|
|
|
}
|
|
|
|
} else if (data && data.action == "clear") {
|
|
|
|
while (container.firstChild) {
|
|
|
|
container.removeChild(container.firstChild);
|
|
|
|
}
|
|
|
|
} else if (data) {
|
|
|
|
var node;
|
|
|
|
if (data.href) {
|
|
|
|
node = document.createElement("a");
|
|
|
|
node.setAttribute("href", data.href);
|
|
|
|
node.setAttribute("target", "_blank");
|
|
|
|
} else if (data.iframe) {
|
|
|
|
node = document.createElement("iframe");
|
|
|
|
node.setAttribute("srcdoc", data.iframe);
|
2016-04-04 16:26:01 -04:00
|
|
|
node.setAttribute("sandbox", "allow-forms allow-scripts allow-top-navigation");
|
2016-03-12 13:50:43 -05:00
|
|
|
node.setAttribute("width", data.width || 320);
|
|
|
|
node.setAttribute("height", data.height || 240);
|
|
|
|
if (data.name) {
|
|
|
|
node.setAttribute("id", "iframe_" + data.name);
|
|
|
|
}
|
|
|
|
} else if (data.image) {
|
|
|
|
node = document.createElement("img");
|
|
|
|
node.setAttribute("src", data.image);
|
|
|
|
} else {
|
|
|
|
node = document.createElement("span");
|
|
|
|
}
|
|
|
|
if (data.style) {
|
|
|
|
node.setAttribute("style", data.style);
|
|
|
|
}
|
|
|
|
if (data.class) {
|
|
|
|
node.setAttribute("class", data.class);
|
|
|
|
}
|
|
|
|
var value = data.value || data.href || data.command || "";
|
|
|
|
if (!value && data.message && data.stackTrace) {
|
|
|
|
printStructured(node, data.message);
|
|
|
|
node.appendChild(document.createElement("br"));
|
|
|
|
printStructured(node, data.fileName + ":" + data.lineNumber + ":");
|
|
|
|
node.appendChild(document.createElement("br"));
|
|
|
|
if (data.stackTrace.length) {
|
|
|
|
for (var i = 0; i < data.stackTrace.length; i++) {
|
|
|
|
printStructured(node, data.stackTrace[i]);
|
|
|
|
node.appendChild(document.createElement("br"));
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
printStructured(node, data.sourceLine);
|
|
|
|
}
|
|
|
|
} else if (value === undefined) {
|
|
|
|
printStructured(node, JSON.stringify(value));
|
|
|
|
} else {
|
|
|
|
printStructured(node, value);
|
|
|
|
}
|
|
|
|
if (data.command) {
|
|
|
|
node.dataset.command = data.command;
|
|
|
|
node.onclick = commandClick;
|
|
|
|
node.setAttribute("class", "command");
|
|
|
|
}
|
|
|
|
container.appendChild(node);
|
|
|
|
} else {
|
|
|
|
printStructured(container, JSON.stringify(data));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function commandClick() {
|
|
|
|
send(this.dataset.command);
|
2016-04-11 11:54:26 -04:00
|
|
|
document.getElementById("input").focus();
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function autoScroll(terminal) {
|
|
|
|
terminal.scrollTop = terminal.scrollHeight - terminal.clientHeight;
|
|
|
|
}
|
|
|
|
|
2016-04-10 20:28:42 -04:00
|
|
|
function setErrorMessage(message) {
|
|
|
|
var node = document.getElementById("status");
|
|
|
|
while (node.firstChild) {
|
|
|
|
node.removeChild(node.firstChild);
|
|
|
|
}
|
|
|
|
node.appendChild(document.createTextNode(message));
|
|
|
|
node.setAttribute("style", "display: inline; color: #dc322f");
|
|
|
|
}
|
|
|
|
|
2016-03-12 13:50:43 -05:00
|
|
|
function send(command) {
|
|
|
|
var value = command;
|
|
|
|
if (!command) {
|
2016-04-11 11:54:26 -04:00
|
|
|
var target = document.getElementById("target").innerText;
|
2016-03-12 13:50:43 -05:00
|
|
|
var prefix = target ? target + " " : "";
|
2016-04-11 11:54:26 -04:00
|
|
|
value = prefix + document.getElementById("input").value;
|
|
|
|
document.getElementById("input").value = "";
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
2016-04-10 20:09:21 -04:00
|
|
|
try {
|
|
|
|
gSocket.send(JSON.stringify({action: "command", command: value}));
|
|
|
|
} catch (error) {
|
2016-04-10 20:28:42 -04:00
|
|
|
setErrorMessage("Send failed: " + error.toString());
|
2016-04-10 20:09:21 -04:00
|
|
|
}
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
function updateLogin() {
|
|
|
|
var login = document.getElementById("login");
|
|
|
|
while (login.firstChild) {
|
|
|
|
login.removeChild(login.firstChild);
|
|
|
|
}
|
|
|
|
|
|
|
|
var a = document.createElement("a");
|
|
|
|
if (gCredentials && gCredentials.session) {
|
|
|
|
a.appendChild(document.createTextNode("logout " + gCredentials.session.name));
|
2016-04-03 15:31:03 -04:00
|
|
|
if (gCredentials.session.google) {
|
|
|
|
gapi.load("auth2", function() {
|
|
|
|
gapi.auth2.init();
|
|
|
|
});
|
|
|
|
a.setAttribute("onclick", "logoutGoogle()");
|
|
|
|
a.setAttribute("href", "#");
|
|
|
|
} else {
|
2016-04-06 21:30:07 -04:00
|
|
|
a.setAttribute("href", "/login/logout?return=" + encodeURIComponent(url() + hash()));
|
2016-04-03 15:31:03 -04:00
|
|
|
}
|
2016-03-12 13:50:43 -05:00
|
|
|
} else {
|
2016-04-14 19:37:23 -04:00
|
|
|
a.appendChild(document.createTextNode("login"));
|
|
|
|
a.setAttribute("href", "/login?return=" + encodeURIComponent(url() + hash()));
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
|
|
|
login.appendChild(a);
|
|
|
|
}
|
|
|
|
|
2016-04-03 15:31:03 -04:00
|
|
|
function logoutGoogle() {
|
|
|
|
gapi.auth2.getAuthInstance().signOut().then(function() {
|
2016-04-06 21:30:07 -04:00
|
|
|
window.location.href = "/login/logout?return=" + encodeURIComponent(url() + hash());
|
2016-04-03 15:31:03 -04:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2016-03-12 13:50:43 -05:00
|
|
|
var gOriginalInput;
|
|
|
|
function dragHover(event) {
|
|
|
|
event.stopPropagation();
|
|
|
|
event.preventDefault();
|
2016-04-11 11:54:26 -04:00
|
|
|
var input = document.getElementById("input");
|
2016-03-12 13:50:43 -05:00
|
|
|
if (event.type == "dragover") {
|
2016-04-11 11:54:26 -04:00
|
|
|
if (!input.classList.contains("drop")) {
|
|
|
|
input.classList.add("drop");
|
|
|
|
gOriginalInput = input.value;
|
|
|
|
input.value = "drop file to upload";
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
|
|
|
} else {
|
2016-04-11 11:54:26 -04:00
|
|
|
input.classList.remove("drop");
|
|
|
|
input.value = gOriginalInput;
|
2016-03-12 13:50:43 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function fixImage(sourceData, maxWidth, maxHeight, callback) {
|
|
|
|
var result = sourceData;
|
|
|
|
var image = new Image();
|
|
|
|
image.crossOrigin = "anonymous";
|
|
|
|
image.referrerPolicy = "no-referrer";
|
|
|
|
image.onload = function() {
|
|
|
|
if (image.width > maxWidth || image.height > maxHeight) {
|
|
|
|
var downScale = Math.min(maxWidth / image.width, maxHeight / image.height);
|
|
|
|
var canvas = document.createElement("canvas");
|
|
|
|
canvas.width = image.width * downScale;
|
|
|
|
canvas.height = image.height * downScale;
|
|
|
|
var context = canvas.getContext("2d");
|
|
|
|
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
|
|
image.width = canvas.width;
|
|
|
|
image.height = canvas.height;
|
|
|
|
context.drawImage(image, 0, 0, image.width, image.height);
|
|
|
|
result = canvas.toDataURL();
|
|
|
|
}
|
|
|
|
callback(result);
|
|
|
|
};
|
|
|
|
image.src = sourceData;
|
|
|
|
}
|
|
|
|
|
|
|
|
function sendImage(image) {
|
|
|
|
fixImage(image, 320, 240, function(result) {
|
|
|
|
send({image: result});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function fileDropRead(event) {
|
|
|
|
sendImage(event.target.result);
|
|
|
|
}
|
|
|
|
|
|
|
|
function fileDrop(event) {
|
|
|
|
dragHover(event);
|
|
|
|
|
|
|
|
var done = false;
|
|
|
|
if (!done) {
|
|
|
|
var files = event.target.files || event.dataTransfer.files;
|
|
|
|
for (var i = 0; i < files.length; i++) {
|
|
|
|
var file = files[i];
|
|
|
|
if (file.type.substring(0, "image/".length) == "image/") {
|
|
|
|
var reader = new FileReader();
|
|
|
|
reader.onloadend = fileDropRead;
|
|
|
|
reader.readAsDataURL(file);
|
|
|
|
done = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!done) {
|
|
|
|
var html = event.dataTransfer.getData("text/html");
|
|
|
|
var match = /<img.*src="([^"]+)"/.exec(html);
|
|
|
|
if (match) {
|
|
|
|
sendImage(match[1]);
|
|
|
|
done = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!done) {
|
|
|
|
var text = event.dataTransfer.getData("text/plain");
|
|
|
|
if (text) {
|
|
|
|
send(text);
|
|
|
|
done = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function enableDragDrop() {
|
|
|
|
var body = document.body;
|
|
|
|
body.addEventListener("dragover", dragHover);
|
|
|
|
body.addEventListener("dragleave", dragHover);
|
|
|
|
|
|
|
|
body.addEventListener("drop", fileDrop);
|
|
|
|
}
|
|
|
|
|
|
|
|
function hashChange() {
|
|
|
|
send({event: 'hashChange', hash: window.location.hash});
|
|
|
|
}
|
|
|
|
|
|
|
|
function focus() {
|
|
|
|
send({event: "focus"});
|
|
|
|
}
|
|
|
|
|
|
|
|
function blur() {
|
|
|
|
send({event: "blur"});
|
|
|
|
}
|
|
|
|
|
|
|
|
function onMessage(event) {
|
|
|
|
send({event: "onWindowMessage", message: event.data});
|
|
|
|
}
|
|
|
|
|
2016-04-10 20:09:21 -04:00
|
|
|
var gSocket;
|
|
|
|
|
2016-04-11 11:54:26 -04:00
|
|
|
window.addEventListener("load", function() {
|
2016-03-12 13:50:43 -05:00
|
|
|
if (Notification) {
|
|
|
|
Notification.requestPermission();
|
|
|
|
}
|
2016-04-11 11:54:26 -04:00
|
|
|
var input = document.getElementById("input");
|
|
|
|
input.addEventListener("keydown", enter);
|
|
|
|
input.focus();
|
2016-03-12 13:50:43 -05:00
|
|
|
window.addEventListener("hashchange", hashChange);
|
|
|
|
window.addEventListener("focus", focus);
|
|
|
|
window.addEventListener("blur", blur);
|
|
|
|
window.addEventListener("message", onMessage, false);
|
|
|
|
enableDragDrop();
|
|
|
|
|
2016-04-10 20:12:19 -04:00
|
|
|
gSocket = new WebSocket(
|
|
|
|
(window.location.protocol == "https:" ? "wss://" : "ws://")
|
2016-04-10 20:09:21 -04:00
|
|
|
+ window.location.hostname
|
|
|
|
+ (window.location.port.length ? ":" + window.location.port : "")
|
|
|
|
+ "/terminal/socket");
|
|
|
|
gSocket.onopen = function() {
|
|
|
|
gSocket.send(JSON.stringify({
|
|
|
|
action: "hello",
|
|
|
|
path: window.location.pathname,
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
gSocket.onmessage = function(event) {
|
|
|
|
receive(JSON.parse(event.data));
|
|
|
|
}
|
2016-04-10 20:28:42 -04:00
|
|
|
gSocket.onclose = function(event) {
|
|
|
|
setErrorMessage("Connection closed with code " + event.code);
|
|
|
|
}
|
2016-03-12 13:50:43 -05:00
|
|
|
});
|