Last active
November 14, 2023 18:18
-
-
Save tarasglek/ff3353169d94e82cbd91218ac43188d6 to your computer and use it in GitHub Desktop.
Lets one remote control a shell via webrtc using https://github.com/tarasglek/webrtc-client
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class RTC { | |
pc; | |
dc; | |
_onMessage_cb = null; | |
_onConnected_cb = null; | |
constructor() { | |
this.pc = null; | |
this.dc = null; | |
} | |
onMessage(msg) { | |
if (this._onMessage_cb) { | |
this._onMessage_cb(msg); | |
} | |
this._onMessage_cb = null; | |
} | |
get connected() { | |
return this.pc && this.pc.connectionState == "connected"; | |
} | |
async connectAndcreateOffer() { | |
const config = { | |
iceServers: [ | |
{ | |
urls: "stun:stun.1.google.com:19302", | |
}, | |
], | |
}; | |
this.pc = new RTCPeerConnection(config); | |
this.dc = this.pc.createDataChannel("chat", { | |
negotiated: true, | |
id: 0, | |
}); | |
const dc = this.dc; | |
const pc = this.pc; | |
let self = this; | |
// pc.oniceconnectionstatechange = (ev) => handleChange(pc); | |
// pc.onconnectionstatechange = (ev) => handleChange(pc) | |
dc.onmessage = (ev) => self.onMessage(ev.data); | |
dc.onopen = (ev) => { | |
if (self._onConnected_cb) { | |
self._onConnected_cb(); | |
} | |
self._onConnected_cb = null; | |
}; | |
dc.onerror = (ev) => console.log(`dc.onerror() ${ev}`); | |
await pc.setLocalDescription(await pc.createOffer()); | |
return new Promise((resolve, reject) => { | |
pc.onicecandidate = ({ candidate }) => { | |
// console.log(`[createOffer()] onicecandidate() signalingState: ${this.pc.signalingState} candidate: ${candidate}`); | |
if (candidate) | |
return; | |
const offer = JSON.stringify(pc.localDescription); | |
resolve(offer); | |
}; | |
}); | |
} | |
async handleInput(input) { | |
console.log(`[handleInput()] input: ${input}`); | |
if (!this.pc) { | |
return await this.connectAndcreateOffer(); | |
} | |
else if (this.pc.connectionState != "connected") { | |
console.log(`[pc.connectionState == "${this.pc.connectionState}"]`); | |
{ | |
const answer = JSON.parse(input); | |
let self = this; | |
const ret = new Promise((resolve, reject) => { | |
this._onConnected_cb = () => { | |
resolve("connected"); | |
}; | |
this.pc.setRemoteDescription(new RTCSessionDescription(answer)); | |
}); | |
return await ret; | |
} | |
} | |
// if we got here we are connected | |
// send message | |
// wait for response | |
const retP = new Promise((resolve, reject) => { | |
this.dc.send(input); | |
this._onMessage_cb = resolve; | |
}); | |
let ret = await retP; | |
console.log(`[handleInput()] ret: ${ret}`); | |
return ret; | |
} | |
} | |
/** | |
* This executes shell commands over webrtc connection. It's not able to execute interactive commands like vim, prefer to use tee, etc instead"; | |
* @param cmd Valid shell command | |
*/ | |
export async function webrtc_shell_cmd(cmd:string) { | |
// cache the connection | |
let rtc = window.rtc; | |
if (!rtc) { | |
window.rtc = rtc = new RTC(); | |
const offer = await rtc.handleInput(""); | |
let reply = prompt("Copy this offer to other node, and paste reply", offer); | |
console.log("reply", reply); | |
const replyReply = await rtc.handleInput(reply); | |
console.log(`rtc.connected: ${rtc.connected}`); | |
if (!rtc.connected) { | |
return replyReply; | |
} | |
return webrtc_shell_cmd(cmd); | |
} | |
const replyStr = await rtc.handleInput(cmd); | |
let output = replyStr; | |
try { | |
const reply = JSON.parse(replyStr); | |
if (!reply.error) { | |
output = reply.output.trim(); | |
} | |
} | |
catch (e) { | |
output = | |
replyStr + | |
"\n" + | |
`Caused error: ${e}`; | |
} | |
return '```\n' + output + '\n```'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment