/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; | |||||
})(); |