Added client frontend
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* HTTP tunnel over WebRTC - Client UI implementation
|
||||
* Author: Konstantinos Drakontidis
|
||||
* Email: gedra100sh@gmail.com
|
||||
*/
|
||||
import { app, BrowserWindow, Menu, ipcMain } from "electron";
|
||||
import { ChildProcessWithoutNullStreams, spawn } from "node:child_process";
|
||||
import { glob } from "glob";
|
||||
import * as net from "net";
|
||||
import * as path from "path";
|
||||
|
||||
var backend: ChildProcessWithoutNullStreams;
|
||||
var shutting_down = false;
|
||||
|
||||
// Graceful shutdown
|
||||
function shutdown() {
|
||||
// Check if shutdown has already been called
|
||||
if (!shutting_down) {
|
||||
shutting_down = true;
|
||||
|
||||
if (backend) {
|
||||
// Kill backend child by sending SIGINT
|
||||
backend.stdout.removeAllListeners();
|
||||
backend.stderr.removeAllListeners();
|
||||
backend.once("exit", () => {
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
backend.stdin.write("exit\n");
|
||||
setTimeout(() => { process.exit(-1) }, 5000);
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an available port number
|
||||
function getAvailablePort(): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const server = net.createServer();
|
||||
server.listen(0, () => {
|
||||
const address = server.address();
|
||||
if (typeof address === "object" && address !== null) {
|
||||
const port = address.port;
|
||||
server.close(() => resolve(port));
|
||||
} else {
|
||||
reject(new Error("Could not get port"));
|
||||
}
|
||||
});
|
||||
server.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
const main_window = new BrowserWindow({
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false
|
||||
},
|
||||
resizable: false,
|
||||
width: 800,
|
||||
height: 600
|
||||
});
|
||||
|
||||
// Load main window and disable menu
|
||||
await main_window.loadFile(path.join(__dirname, "..", "views", "main.html"));
|
||||
Menu.setApplicationMenu(null);
|
||||
|
||||
var line_buffer = "";
|
||||
var connected = false;
|
||||
|
||||
// Generate a port number and search for the peer binary
|
||||
const http_port = await getAvailablePort();
|
||||
const peer_exec = await glob(path.join(process.resourcesPath, "extra", "peer*").replaceAll("\\", "/"));
|
||||
if (peer_exec.length) {
|
||||
// Create child
|
||||
backend = spawn(peer_exec[0], [http_port.toString()], {
|
||||
cwd: path.join(process.resourcesPath, "extra")
|
||||
});
|
||||
|
||||
backend.on("spawn", () => {
|
||||
main_window.webContents.send("backend_spawn");
|
||||
});
|
||||
backend.stdout.on("data", (data: Buffer) => {
|
||||
main_window.webContents.send("backend_info", data.toString());
|
||||
line_buffer += data.toString();
|
||||
|
||||
// Scan each line from stdout to check if datachannel opened
|
||||
var lines = line_buffer.split("\n");
|
||||
line_buffer = lines.pop() || "";
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.trim() === "Opened data channel.") {
|
||||
connected = true;
|
||||
main_window.webContents.send("backend_dc", http_port.toString());
|
||||
main_window.setResizable(true);
|
||||
main_window.maximize();
|
||||
}
|
||||
}
|
||||
});
|
||||
backend.stderr.on("data", (data: Buffer) => {
|
||||
main_window.webContents.send("backend_error", data.toString());
|
||||
});
|
||||
backend.on("close", async () => {
|
||||
// Check if main window is currently showing and if not redirect
|
||||
if (connected) {
|
||||
await main_window.loadFile(path.join(__dirname, "..", "views", "main.html"));
|
||||
main_window.unmaximize();
|
||||
main_window.setSize(800, 600, true)
|
||||
main_window.setResizable(false);
|
||||
}
|
||||
main_window.webContents.send("backend_quit");
|
||||
});
|
||||
} else {
|
||||
process.exit(-1);
|
||||
}
|
||||
});
|
||||
|
||||
// Handling basic HTTP authentication
|
||||
let authCallback: ((user: string, pass: string) => void) | null = null;
|
||||
app.on("login", (event, webContents, request, authInfo, callback) => {
|
||||
event.preventDefault();
|
||||
authCallback = callback;
|
||||
|
||||
const loginWin = new BrowserWindow({
|
||||
parent: BrowserWindow.getFocusedWindow() ?? undefined,
|
||||
width: 300,
|
||||
height: 180,
|
||||
resizable: false,
|
||||
modal: true,
|
||||
frame: false,
|
||||
webPreferences: {
|
||||
nodeIntegration: true,
|
||||
contextIsolation: false,
|
||||
},
|
||||
});
|
||||
|
||||
loginWin.loadFile(path.join(__dirname, "..", "views", "login.html"));
|
||||
});
|
||||
|
||||
app.on("window-all-closed", () => {
|
||||
shutdown();
|
||||
});
|
||||
|
||||
app.on("quit", () => {
|
||||
shutdown();
|
||||
});
|
||||
|
||||
ipcMain.on("login-submit", (_event, { username, password }) => {
|
||||
if (authCallback) {
|
||||
authCallback(username, password);
|
||||
authCallback = null;
|
||||
}
|
||||
BrowserWindow.getFocusedWindow()?.close();
|
||||
});
|
||||
|
||||
ipcMain.on("login-cancel", () => {
|
||||
shutdown();
|
||||
});
|
||||
Reference in New Issue
Block a user