class Conn { constructor(id) { this.id = id; this.conn = null; this.gotPong = false; this.pongTime = 4000; this.state = "idle"; this.q = []; this.onstatechange = () => {} this.onmessage = () => {}; this.createConn(); setInterval(() => { if (this.state == "pong-timeout") return; if (!this.gotPong) this.setState("pong-timeout"); this.send({ type: "ping" }); this.gotPong = false; }, this.pongTime); } setState(state, msg) { this.state = state; this.onstatechange(msg || state); } createConn() { this.conn = new WebSocket(`${location.protocol.replace("http", "ws")}//${location.host}`); this.setState("connecting"); this.connected = false; this.conn.onclose = evt => { this.conn = null; this.setState("closed", `closed: ${evt.code}`); setTimeout(this.createConn.bind(this), 2000); } this.conn.onopen = () => { this.setState("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.setState("waiting-for-target"); } else { this.setState("error", `error: ${obj.err}`); } break; case "target-connect": this.setState("ready"); break; case "target-disconnect": this.setState("waiting-for-target") break; case "js-result": this.onjsresult(obj.err, obj.result); break; case "log": this.onlog(obj.log); break; case "pong": this.onPong(); break; default: console.warn("Unknown message type:", obj.type); } } } onPong() { this.gotPong = true; if (this.state == "pong-timeout") this.setState("ready"); } runJavascript(js) { this.send({ type: "run-js", js }); } send(obj) { if (this.connected) this.conn.send(JSON.stringify(obj)); } }