mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
feat: finish the web-endpoint plugin, add ctx.desktop (B8)
This commit is contained in:
1 parent
1682861853
commit
535126fd1b
50 files changed
+2018
-898
No files matched your search
@@ -1,6 +1,6 @@
|
||||
const { randomUUID } = require("node:crypto");
|
||||
|
||||
function webUrl(value) {
|
||||
function isolatedUrl(value) {
|
||||
const url = new URL(value);
|
||||
if (
|
||||
!["http:", "https:"].includes(url.protocol) ||
|
||||
@@ -8,27 +8,35 @@ function webUrl(value) {
|
||||
url.password
|
||||
) {
|
||||
throw new Error(
|
||||
"Web endpoints require an HTTP(S) URL without embedded credentials",
|
||||
"Isolated windows require an HTTP(S) URL without embedded credentials",
|
||||
);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
function createWebEndpointWindows({ BrowserWindow, session, getMainWindow }) {
|
||||
/**
|
||||
* Opens a BrowserWindow in its own non-persistent session, isolated from the
|
||||
* main Termix window's cookies and storage. Used for any URL a plugin wants
|
||||
* shown without sharing Termix's own session (a tunnelled or direct host web
|
||||
* UI today; core has no other caller yet).
|
||||
*
|
||||
* Two ways in, both trusted: the renderer's own IPC call (checked against the
|
||||
* main window's webContents before reaching here) and the embedded backend's
|
||||
* fork IPC request (trusted because it is this app's own forked process, not
|
||||
* arbitrary web content -- see the "backend-request" handler below).
|
||||
*/
|
||||
function createIsolatedWindows({ BrowserWindow, session, getMainWindow }) {
|
||||
const certificates = new WeakMap();
|
||||
async function open(event, options = {}) {
|
||||
async function open(options = {}) {
|
||||
const main = getMainWindow();
|
||||
if (
|
||||
!main ||
|
||||
event.sender !== main.webContents ||
|
||||
event.senderFrame !== main.webContents.mainFrame
|
||||
) {
|
||||
throw new Error("Only the main Termix window can open web endpoints");
|
||||
if (!main) {
|
||||
throw new Error("No window to attach an isolated window to");
|
||||
}
|
||||
const url = webUrl(options.url);
|
||||
const isolated = session.fromPartition(`web-endpoint-${randomUUID()}`, {
|
||||
cache: false,
|
||||
});
|
||||
const url = isolatedUrl(options.url);
|
||||
const isolated = session.fromPartition(
|
||||
options.partition || `isolated-window-${randomUUID()}`,
|
||||
{ cache: false },
|
||||
);
|
||||
isolated.setPermissionRequestHandler((_contents, _permission, callback) =>
|
||||
callback(false),
|
||||
);
|
||||
@@ -57,7 +65,7 @@ function createWebEndpointWindows({ BrowserWindow, session, getMainWindow }) {
|
||||
win.setMenu(null);
|
||||
const canNavigate = (target) => {
|
||||
try {
|
||||
return target === "about:blank" || !!webUrl(target);
|
||||
return target === "about:blank" || !!isolatedUrl(target);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
@@ -90,7 +98,7 @@ function createWebEndpointWindows({ BrowserWindow, session, getMainWindow }) {
|
||||
const win = new BrowserWindow({
|
||||
width: 1100,
|
||||
height: 800,
|
||||
title: `Web endpoint — ${url.hostname}`,
|
||||
title: options.title || url.hostname,
|
||||
webPreferences: preferences,
|
||||
});
|
||||
configure(win);
|
||||
@@ -125,4 +133,4 @@ function createWebEndpointWindows({ BrowserWindow, session, getMainWindow }) {
|
||||
}
|
||||
return { open, handleCertificateError };
|
||||
}
|
||||
module.exports = { createWebEndpointWindows, webUrl };
|
||||
module.exports = { createIsolatedWindows, isolatedUrl };
|
||||
+40
-11
@@ -633,15 +633,45 @@ if (isInsecureModeEnabled()) {
|
||||
app.commandLine.appendSwitch("--enable-features=NetworkService");
|
||||
|
||||
let mainWindow = null;
|
||||
const { createWebEndpointWindows } = require("./web-endpoint-window.cjs");
|
||||
const webEndpointWindows = createWebEndpointWindows({
|
||||
const { createIsolatedWindows } = require("./isolated-window.cjs");
|
||||
const isolatedWindows = createIsolatedWindows({
|
||||
BrowserWindow,
|
||||
session,
|
||||
getMainWindow: () => mainWindow,
|
||||
});
|
||||
ipcMain.handle("open-isolated-web-endpoint", (event, options) =>
|
||||
webEndpointWindows.open(event, options),
|
||||
);
|
||||
|
||||
// Requests from the embedded backend over the fork IPC channel (the other
|
||||
// direction from the "shutdown" message main already sends it). A plugin's
|
||||
// ctx.desktop.openIsolatedWindow is the one caller today.
|
||||
const BACKEND_REQUEST_HANDLERS = {
|
||||
"open-isolated-window": (payload) => isolatedWindows.open(payload),
|
||||
};
|
||||
|
||||
async function handleBackendRequest(msg) {
|
||||
if (!msg || msg.type !== "backend-request") return;
|
||||
const { id, channel, payload } = msg;
|
||||
const handler = BACKEND_REQUEST_HANDLERS[channel];
|
||||
const reply = (response) => {
|
||||
if (backendProcess && !backendProcess.killed) {
|
||||
try {
|
||||
backendProcess.send({ type: "backend-response", id, ...response });
|
||||
} catch (error) {
|
||||
logToFile("Failed to reply to backend request:", error.message);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (!handler) {
|
||||
reply({ ok: false, error: `Unknown backend request channel: ${channel}` });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await handler(payload);
|
||||
reply({ ok: true, result });
|
||||
} catch (error) {
|
||||
reply({ ok: false, error: error.message || String(error) });
|
||||
}
|
||||
}
|
||||
|
||||
let backendProcess = null;
|
||||
let backendStartFailed = false;
|
||||
// Why the embedded backend died, once it has. Null while it is healthy
|
||||
@@ -826,12 +856,7 @@ app.on(
|
||||
"certificate-error",
|
||||
(event, _webContents, url, error, certificate, callback) => {
|
||||
if (
|
||||
webEndpointWindows.handleCertificateError(
|
||||
event,
|
||||
_webContents,
|
||||
url,
|
||||
callback,
|
||||
)
|
||||
isolatedWindows.handleCertificateError(event, _webContents, url, callback)
|
||||
)
|
||||
return;
|
||||
if (isWebEndpointCertificateAllowed(url)) {
|
||||
@@ -1155,6 +1180,10 @@ function startBackendServer() {
|
||||
logToFile("[backend:stderr]", chunk.trim());
|
||||
});
|
||||
|
||||
backendProcess.on("message", (msg) => {
|
||||
void handleBackendRequest(msg);
|
||||
});
|
||||
|
||||
backendProcess.on("exit", (code, signal) => {
|
||||
logToFile(`Backend process exited with code ${code}, signal ${signal}`);
|
||||
if (!resolved && code !== 0) {
|
||||
|
||||
@@ -16,7 +16,6 @@ const ALLOWED_INVOKE_CHANNELS = new Set([
|
||||
"save-remote-sync-jwt",
|
||||
"test-server-connection",
|
||||
"allow-invalid-certificate-for-origin",
|
||||
"open-isolated-web-endpoint",
|
||||
]);
|
||||
|
||||
function invokeAllowed(channel, ...args) {
|
||||
|
||||
Reference in new issue
Block a user