Browse Source

so, this sorta works

master
Martin Dørum 2 years ago
commit
38f45a9eda
10 changed files with 540 additions and 0 deletions
  1. 1
    0
      .gitignore
  2. 46
    0
      package-lock.json
  3. 16
    0
      package.json
  4. 182
    0
      server.js
  5. 83
    0
      web/console/conn.js
  6. 22
    0
      web/console/index.html
  7. 44
    0
      web/console/script.js
  8. 3
    0
      web/console/style.css
  9. 14
    0
      web/index.html
  10. 129
    0
      web/target.js

+ 1
- 0
.gitignore View File

@@ -0,0 +1 @@
/node_modules

+ 46
- 0
package-lock.json View File

@@ -0,0 +1,46 @@
{
"name": "woba-consola",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"async-limiter": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
"integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
},
"mime-db": {
"version": "1.40.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz",
"integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA=="
},
"mime-types": {
"version": "2.1.24",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz",
"integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==",
"requires": {
"mime-db": "1.40.0"
}
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"ultron": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz",
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og=="
},
"ws": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-4.0.0.tgz",
"integrity": "sha512-QYslsH44bH8O7/W2815u5DpnCpXWpEK44FmaHffNwgJI4JMaSZONgPBTOfrxJ29mXKbXak+LsJ2uAkDTYq2ptQ==",
"requires": {
"async-limiter": "~1.0.0",
"safe-buffer": "~5.1.0",
"ultron": "~1.1.0"
}
}
}
}

+ 16
- 0
package.json View File

@@ -0,0 +1,16 @@
{
"name": "woba-consola",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js"
},
"author": "",
"license": "ISC",
"dependencies": {
"mime-types": "^2.1.24",
"ws": "=4.0.0"
}
}

+ 182
- 0
server.js View File

@@ -0,0 +1,182 @@
let http = require("http");
let fs = require("fs");
let crypto = require("crypto");
let urllib = require("url");
let ws = require("ws");
let mime = require('mime-types')

let sessions = {};

class Session {
constructor(id) {
this.id = id;
this.masterPoll = null;
this.timeout = null;
this.resetTimeout();
this.master = null;
this.target = null;
}

resetTimeout() {
if (this.timeout)
clearTimeout(this.timeout);

this.timeout = setTimeout(() => {
console.log(`${this.id}: No activity for 5m, deleting session.`);
sessions[this.id] = null;
}, 5 * 60 * 1000);
}

onMaster(conn) {
this.master = conn;

this.sendTargetMessage({ type: "master-connect" });
if (this.target)
this.sendMasterMessage({ type: "target-connect" });

conn.on("message", msg => {
console.log(`${this.id}: Message from master: ${msg}`);
this.resetTimeout();
this.onMasterMessage(JSON.parse(msg));
});

conn.on("close", () => {
this.master = null;
this.sendTargetMessage({ type: "master-disconnect" });
});
}

onTarget(conn) {
this.target = conn;

this.sendMasterMessage({ type: "target-connect" });
if (this.master)
this.sendTargetMessage({ type: "master-connect" });

conn.on("message", msg => {
console.log(`${this.id}: Message from target: ${msg}`);
this.resetTimeout();
this.onTargetMessage(JSON.parse(msg));
});

conn.on("close", () => {
this.target = null;
this.sendMasterMessage({ type: "target-disconnect" });
});
}

onMasterMessage(msg) {
console.log(`${this.id}: Forwarding from master:`, msg);
this.sendTargetMessage(msg);
}

onTargetMessage(msg) {
console.log(`${this.id}: Forwarding from target:`, msg);
this.sendMasterMessage(msg);
}

sendMasterMessage(msg) {
if (this.master)
this.master.send(JSON.stringify(msg));
}

sendTargetMessage(msg) {
if (this.target)
this.target.send(JSON.stringify(msg));
}
}

function createSession() {
let id = crypto.randomBytes(16).toString("hex");
sessions[id] = new Session(id);
return id;
}

function sendFile(path, res) {
let url = urllib.parse(path);
let rs = fs.createReadStream(`web${url.pathname}`);
rs.once("error", err => {
if (err.code == "EISDIR") {
if (url.pathname[url.pathname.length - 1] == "/") {
return sendFile(`${url.pathname}index.html`, res);
} else {
res.writeHead(302, { location: `${url.pathname}/` });
res.end();
return;
}
}

res.writeHead(400);
res.end(err.toString());
});
rs.on("open", () => {
res.writeHead(200, {
"Content-Type": mime.lookup(url.pathname),
});
});
rs.pipe(res);
}

let server = http.createServer((req, res) => {
if (req.method == "GET" || req.method == "HEAD") {
sendFile(req.url, res);
} else if (req.method == "POST" && req.url == "/create") {
let id = createSession(res);
res.writeHead(302, {
location: `/console/?${id}`,
});
res.end("Redirecting...");
}
});
server.listen(8080);

let wss = new ws.Server({ server });

wss.on("connection", conn => {
conn.on("message", function onMessage(msg) {
let obj;
try {
obj = JSON.parse(msg);
} catch (err) {
console.log("Received invalid message");
conn.send(JSON.stringify({
type: "identify-response",
err: "Invalid JSON",
}));
return;
}

if (obj.type != "identify-target" && obj.type != "identify-master") {
conn.send(JSON.stringify({
type: "identify-response",
err: "Not identified",
}));
return;
}

if (!obj.id) {
conn.send(JSON.stringify({
type: "identify-response",
err: "Empty session ID",
}));
return;
}

let sess = sessions[obj.id];
if (sess == null) {
conn.send(JSON.stringify({
type: "identify-response",
err: "Invalid session ID",
}));
return;
}

conn.send(JSON.stringify({ type: "identify-response", err: null }));
conn.off("message", onMessage);

if (obj.type == "identify-master")
sess.onMaster(conn);
else
sess.onTarget(conn);
});
});

+ 83
- 0
web/console/conn.js View File

@@ -0,0 +1,83 @@
class Conn {
constructor(id) {
this.id = id;
this.conn = null;
this.q = [];

this.onstatechange = () => {}
this.onmessage = () => {};

this.createConn();

setInterval(() => this.send({ type: "ping" }), 30000);
}

createConn() {
this.conn = new WebSocket(`${location.protocol.replace("http", "ws")}//${location.host}`);
this.onstatechange("Connecting");
this.connected = false;
this.ready = false;

this.conn.onclose = evt => {
this.conn = null;
this.onstatechange(`Closed: ${evt.code}`);
setTimeout(this.createConn.bind(this), 2000);
}

this.conn.onopen = () => {
this.onstatechange("Identifying");
this.conn.send(JSON.stringify({ type: "identify-master", id: this.id }));

this.q.forEach(el => this.conn.send(el));
this.q = [];
}

this.conn.onmessage = msg => {
console.log(msg.data);
let obj = JSON.parse(msg.data);
switch (obj.type) {
case "identify-response":
if (obj.err == null) {
this.connected = true;
this.onstatechange("Waiting for target");
} else {
this.onstatechange(`Error: ${obj.err}`);
}
break;

case "target-connect":
this.ready = true;
this.onstatechange("Ready");
break;

case "target-disconnect":
this.ready = false;
this.onstatechange("Waiting for target");
break;

case "js-result":
this.onjsresult(obj.err, obj.result);
break;

case "log":
this.onlog(obj.log);
break;

case "pong":
break;

default:
console.warn("Unknown message type:", obj.type);
}
}
}

runJavascript(js) {
this.send({ type: "run-js", js });
}

send(obj) {
if (this.connected)
this.conn.send(JSON.stringify(obj));
}
}

+ 22
- 0
web/console/index.html View File

@@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Woba Consola Console</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div>State: <span id="state">Idle</span></div>
<br>
<div>Insert this into your target site: <pre id="target-script"></pre></div>

<form id="input-form">
<input id="input" disabled>
</form>

<pre id="console"></pre>

<script src="conn.js"></script>
<script src="script.js"></script>
</body>
</html>

+ 44
- 0
web/console/script.js View File

@@ -0,0 +1,44 @@
let el = {
console: document.querySelector("#console"),
state: document.querySelector("#state"),
targetScript: document.querySelector("#target-script"),
inputForm: document.querySelector("#input-form"),
input: document.querySelector("#input"),
};

let id = location.href.split("?")[1];

let conn = new Conn(id);

function print(msg) {
el.console.innerText = msg + "\n" + el.console.innerText;
}

conn.onstatechange = state => {
el.state.innerText = state;
if (state == "Ready")
el.input.disabled = "";
else
el.input.disabled = "disabled";
}

conn.onjsresult = (err, res) => {
if (err)
print("Error: "+err);
else
print(res);
};

conn.onlog = log => {
print(log);
}

el.targetScript.innerText =
`<script src='${location.protocol}//${location.host}/target.js?${id}'></script>`;

el.inputForm.addEventListener("submit", evt => {
evt.preventDefault();
conn.runJavascript(el.input.value);
print("> "+el.input.value);
el.input.value = "";
});

+ 3
- 0
web/console/style.css View File

@@ -0,0 +1,3 @@
#target-script {
margin-top: 0px;
}

+ 14
- 0
web/index.html View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Woba Consola</title>
</head>
<body>
<h1>Woba Consola</h1>

<form action="/create" method="POST">
<button>Create Session</button>
</form>
</body>
</html>

+ 129
- 0
web/target.js View File

@@ -0,0 +1,129 @@
(function() {

var id = document.currentScript.src.split("?")[1];
var state = "connecting"

var conn = new WebSocket(
location.protocol.replace("http", "ws") + "//" +
location.host);

function toString(val) {
if (typeof val == "string" || typeof val == "function")
return val.toString();

try {
return JSON.stringify(val);
} catch (err) {
return val.toString();
}
}

var logq = [];

function sendLog() {
var str = "";
for (var i in arguments) {
if (i != 0)
str += " ";
str += toString(arguments[i]);
}

if (state == "ready")
conn.send(JSON.stringify({ type: "log", log: str }));
else
logq.push(str);
}

conn.onmessage = function(msg) {
var obj;
try {
obj = JSON.parse(msg.data);
} catch (err) {
alert("Woba Consola: Failed to parse JSON '" + msg.data + "'");
return;
}

switch (obj.type) {
case "identify-response":
if (obj.err != null) {
alert("Woba Consola: Error: " + obj.err);
state = "error";
} else {
state = "waiting-for-master";
}
break;

case "master-connect":
state = "ready";

for (var i in logq)
conn.send(JSON.stringify({ type: "log", log: logq[i] }));
logq = [];
break;

case "master-disconnect":
state = "waiting-for-master";
break;

case "run-js":
var res;
try {
res = toString(eval(obj.js));
} catch (err) {
conn.send(JSON.stringify({
type: "js-result",
err: err.toString(),
}));
return;
}

conn.send(JSON.stringify({
type: "js-result",
result: res,
}));
break;

case "ping":
conn.send(JSON.stringify({ type: "pong" }));
break;

default:
alert("Woba Consola: Unknown message type: " + obj.type);
}
};

conn.onopen = function() {
state = "identifying";
conn.send(JSON.stringify({ type: "identify-target", id: id }));
};

conn.onclose = function(evt) {
state = "closed";
alert("Woba Consola: Connction closed (" + evt.code + ")");
};

var origCons = console;
var newCons = {};

function logProxy(name) {
return function() {
var arr = Array.prototype.slice.call(arguments);
origCons[name].apply(origCons, arr);
arr.unshift(name + ":");
sendLog.apply(null, arr);
}
}

for (var i in origCons) {
if (origCons.hasOwnProperty(i)) {
newCons[i] = logProxy(i);
}
}

window.addEventListener("error", function(evt) {
sendLog("error: " + evt.filename + " : " + evt.lineno + ":", evt.message);
});

window.console = newCons;

})();

Loading…
Cancel
Save