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