| @@ -0,0 +1 @@ | |||
| /node_modules | |||
| @@ -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" | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -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" | |||
| } | |||
| } | |||
| @@ -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); | |||
| }); | |||
| }); | |||
| @@ -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)); | |||
| } | |||
| } | |||
| @@ -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> | |||
| @@ -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 = ""; | |||
| }); | |||
| @@ -0,0 +1,3 @@ | |||
| #target-script { | |||
| margin-top: 0px; | |||
| } | |||
| @@ -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> | |||
| @@ -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; | |||
| })(); | |||