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