Skip to content

Instantly share code, notes, and snippets.

@tarasglek
Last active November 14, 2023 18:18
Show Gist options
  • Save tarasglek/ff3353169d94e82cbd91218ac43188d6 to your computer and use it in GitHub Desktop.
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
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