From 343689c00d2fc9dfd2ae6e2ca7d9abecd5ff84a3 Mon Sep 17 00:00:00 2001 From: nekename <63245705+nekename@users.noreply.github.com> Date: Fri, 10 Oct 2025 18:56:32 +0100 Subject: [PATCH] feat: use alternative ports if default ports are already in use --- src-tauri/capabilities/corsfetch.json | 2 +- src-tauri/src/events/frontend/mod.rs | 5 +++++ src-tauri/src/main.rs | 3 ++- src-tauri/src/plugins/mod.rs | 23 +++++++++++++++++---- src-tauri/src/plugins/webserver.rs | 2 +- src-tauri/tauri.conf.json | 2 +- src/components/ActionList.svelte | 7 +++---- src/components/PluginManager.svelte | 9 ++++---- src/components/PropertyInspectorView.svelte | 15 +++++++------- src/lib/ports.ts | 18 ++++++++++++++++ src/lib/rendererHelper.ts | 4 +++- src/routes/+page.svelte | 3 +++ tsconfig.json | 3 ++- 13 files changed, 71 insertions(+), 25 deletions(-) create mode 100644 src/lib/ports.ts diff --git a/src-tauri/capabilities/corsfetch.json b/src-tauri/capabilities/corsfetch.json index 067559f..a891cc8 100644 --- a/src-tauri/capabilities/corsfetch.json +++ b/src-tauri/capabilities/corsfetch.json @@ -1,7 +1,7 @@ { "identifier": "corsfetch", "local": true, - "remote": { "urls": ["http://localhost:57118"] }, + "remote": { "urls": ["http://localhost:*"] }, "windows": ["*"], "permissions": ["cors-fetch:default"] } diff --git a/src-tauri/src/events/frontend/mod.rs b/src-tauri/src/events/frontend/mod.rs index fb0cc83..5bda702 100644 --- a/src-tauri/src/events/frontend/mod.rs +++ b/src-tauri/src/events/frontend/mod.rs @@ -56,6 +56,11 @@ pub async fn update_devices() { let _ = app.get_webview_window("main").unwrap().emit("devices", DEVICES.clone()); } +#[command] +pub async fn get_port_base() -> u16 { + *crate::plugins::PORT_BASE +} + #[command] pub async fn get_categories() -> HashMap { CATEGORIES.read().await.clone() diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index cbf16c8..17e6978 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -40,8 +40,9 @@ async fn main() { let app = match Builder::default() .invoke_handler(tauri::generate_handler![ - frontend::get_devices, frontend::restart, + frontend::get_devices, + frontend::get_port_base, frontend::get_categories, frontend::get_localisations, frontend::get_applications, diff --git a/src-tauri/src/plugins/mod.rs b/src-tauri/src/plugins/mod.rs index e0ba9b4..2b89a70 100644 --- a/src-tauri/src/plugins/mod.rs +++ b/src-tauri/src/plugins/mod.rs @@ -31,6 +31,20 @@ enum PluginInstance { pub static DEVICE_NAMESPACES: Lazy>> = Lazy::new(|| RwLock::new(HashMap::new())); static INSTANCES: Lazy>> = Lazy::new(|| Mutex::new(HashMap::new())); +pub static PORT_BASE: Lazy = Lazy::new(|| { + let mut base = 57116; + loop { + let websocket_result = std::net::TcpListener::bind(format!("0.0.0.0:{}", base)); + let webserver_result = std::net::TcpListener::bind(format!("0.0.0.0:{}", base + 2)); + if websocket_result.is_ok() && webserver_result.is_ok() { + log::debug!("Using ports {} and {}", base, base + 2); + break; + } + base += 1; + } + base +}); + /// Initialise a plugin from a given directory. pub async fn initialise_plugin(path: &path::Path) -> anyhow::Result<()> { let plugin_uuid = path.file_name().unwrap().to_str().unwrap(); @@ -152,10 +166,11 @@ pub async fn initialise_plugin(path: &path::Path) -> anyhow::Result<()> { } let code_path = code_path.unwrap(); - let args = ["-port", "57116", "-pluginUUID", plugin_uuid, "-registerEvent", "registerPlugin", "-info"]; + let port_string = PORT_BASE.to_string(); + let args = ["-port", port_string.as_str(), "-pluginUUID", plugin_uuid, "-registerEvent", "registerPlugin", "-info"]; if code_path.to_lowercase().ends_with(".html") || code_path.to_lowercase().ends_with(".htm") || code_path.to_lowercase().ends_with(".xhtml") { - let url = "http://localhost:57118/".to_owned() + path.join(code_path).to_str().unwrap(); + let url = format!("http://localhost:{}/", *PORT_BASE + 2) + path.join(code_path).to_str().unwrap(); let window = tauri::WebviewWindowBuilder::new(APP_HANDLE.get().unwrap(), plugin_uuid.replace('.', "_"), tauri::WebviewUrl::External(url.parse()?)) .title(plugin_uuid) .visible(false) @@ -180,7 +195,7 @@ pub async fn initialise_plugin(path: &path::Path) -> anyhow::Result<()> { }}; opendeckInit(); "#, - port = 57116, + port = *PORT_BASE, uuid = plugin_uuid, event = "registerPlugin", info = serde_json::to_string(&info)? @@ -431,7 +446,7 @@ pub fn initialise_plugins() { /// Start the WebSocket server that plugins communicate with. async fn init_websocket_server() { - let listener = match TcpListener::bind("0.0.0.0:57116").await { + let listener = match TcpListener::bind(format!("0.0.0.0:{}", *PORT_BASE)).await { Ok(listener) => listener, Err(error) => { error!("Failed to bind plugin WebSocket server to socket: {}", error); diff --git a/src-tauri/src/plugins/webserver.rs b/src-tauri/src/plugins/webserver.rs index 592ac56..a5e28fa 100644 --- a/src-tauri/src/plugins/webserver.rs +++ b/src-tauri/src/plugins/webserver.rs @@ -17,7 +17,7 @@ fn mime(extension: &str) -> String { /// Start a simple webserver to serve files of plugins that run in a browser environment. pub async fn init_webserver(prefix: PathBuf) { let server = { - let listener = std::net::TcpListener::bind("0.0.0.0:57118").unwrap(); + let listener = std::net::TcpListener::bind(format!("0.0.0.0:{}", *super::PORT_BASE + 2)).unwrap(); #[cfg(windows)] { diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 5c98f54..9b04afc 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -65,7 +65,7 @@ } ], "security": { - "csp": "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data:; frame-src http://localhost:57118; script-src 'self' 'unsafe-inline'; connect-src ipc: http://ipc.localhost https://openactionapi.github.io https://api.github.com https://raw.githubusercontent.com https://plugins.amankhanna.me;" + "csp": "default-src 'self'; style-src 'self' 'unsafe-inline'; img-src * data:; frame-src http://localhost:*; script-src 'self' 'unsafe-inline'; connect-src ipc: http://ipc.localhost https://openactionapi.github.io https://api.github.com https://raw.githubusercontent.com https://plugins.amankhanna.me;" }, "withGlobalTauri": true } diff --git a/src/components/ActionList.svelte b/src/components/ActionList.svelte index fe47e0b..08d967e 100644 --- a/src/components/ActionList.svelte +++ b/src/components/ActionList.svelte @@ -3,6 +3,7 @@ import MagnifyingGlass from "phosphor-svelte/lib/MagnifyingGlass"; + import { getWebserverUrl } from "$lib/ports"; import { localisations } from "$lib/settings"; import { PRODUCT_NAME } from "$lib/singletons"; @@ -49,9 +50,7 @@ {#if icon || (actions[0] && plugins.find((x) => x.id == actions[0].plugin) && categories[name].actions.every((x) => x.plugin == actions[0].plugin))} x.id == actions[0].plugin).icon} + src={icon ? (!icon.startsWith("opendeck/") ? getWebserverUrl(icon) : icon.replace("opendeck", "")) : getWebserverUrl(plugins.find((x) => x.id == actions[0].plugin).icon)} alt={name} class="w-5 h-5 rounded-xs ml-1 -mt-1 inline" /> @@ -67,7 +66,7 @@ on:dragstart={(event) => event.dataTransfer?.setData("action", JSON.stringify(action))} > {$localisations?.[action.plugin]?.[action.uuid]?.Tooltip diff --git a/src/components/PluginManager.svelte b/src/components/PluginManager.svelte index 874a9cd..11042bc 100644 --- a/src/components/PluginManager.svelte +++ b/src/components/PluginManager.svelte @@ -4,14 +4,15 @@ import CloudArrowDown from "phosphor-svelte/lib/CloudArrowDown"; import FileArrowUp from "phosphor-svelte/lib/FileArrowUp"; import Gear from "phosphor-svelte/lib/Gear"; - import ListedPlugin from "./ListedPlugin.svelte"; import MagnifyingGlass from "phosphor-svelte/lib/MagnifyingGlass"; + import Trash from "phosphor-svelte/lib/Trash"; + import WarningCircle from "phosphor-svelte/lib/WarningCircle"; + import ListedPlugin from "./ListedPlugin.svelte"; import PluginDetails from "./PluginDetails.svelte"; import Popup from "./Popup.svelte"; import Tooltip from "./Tooltip.svelte"; - import Trash from "phosphor-svelte/lib/Trash"; - import WarningCircle from "phosphor-svelte/lib/WarningCircle"; + import { getWebserverUrl } from "$lib/ports"; import { localisations, settings } from "$lib/settings"; import { actionList, deviceSelector } from "$lib/singletons"; @@ -168,7 +169,7 @@
{#each installed.sort((a, b) => (a.builtin && !b.builtin) ? -1 : (b.builtin && !a.builtin) ? 1 : a.id.localeCompare(b.id)) as plugin} { @@ -62,7 +63,7 @@ iframe.style.width = "100%"; iframe.style.height = "100%"; iframe.style.display = $inspectedInstance == context ? "block" : "none"; - iframe.contentWindow?.postMessage({ event: "windowClosed" }, "http://localhost:57118"); + iframe.contentWindow?.postMessage({ event: "windowClosed" }, getWebserverUrl()); } iframePopupsOpen = iframePopupsOpen.filter((e) => e != context); @@ -140,9 +141,9 @@ statusText: response.statusText, }, }, - }, "http://localhost:57118"); + }, getWebserverUrl()); }).catch((error: any) => { - iframes[data.payload.context]?.contentWindow?.postMessage({ event: "fetchError", payload: { id: data.payload.id, error } }, "http://localhost:57118"); + iframes[data.payload.context]?.contentWindow?.postMessage({ event: "fetchError", payload: { id: data.payload.id, error } }, getWebserverUrl()); }); } }); @@ -176,7 +177,7 @@ title="Property inspector" class="w-full h-full hidden" class:block!={$inspectedInstance == instance.context} - src={"http://localhost:57118/" + instance.action.property_inspector + "|opendeck_property_inspector"} + src={getWebserverUrl(instance.action.property_inspector + "|opendeck_property_inspector")} name={instance.context} bind:this={iframes[instance.context]} on:load={() => iframeOnLoad(instance)} diff --git a/src/lib/ports.ts b/src/lib/ports.ts new file mode 100644 index 0000000..e85341b --- /dev/null +++ b/src/lib/ports.ts @@ -0,0 +1,18 @@ +import { invoke } from "@tauri-apps/api/core"; + +let portBase = 57116; +let initialised = false; + +export async function initPortBase(): Promise { + if (initialised) return; + portBase = await invoke("get_port_base"); + initialised = true; +} + +export function getWebSocketPort(): number { + return portBase; +} + +export function getWebserverUrl(path: string = ""): string { + return `http://localhost:${portBase + 2}/${path}`; +} diff --git a/src/lib/rendererHelper.ts b/src/lib/rendererHelper.ts index 5d7e71d..9faf1e2 100644 --- a/src/lib/rendererHelper.ts +++ b/src/lib/rendererHelper.ts @@ -1,12 +1,14 @@ import type { ActionState } from "./ActionState.ts"; import type { Context } from "./Context.ts"; +import { getWebserverUrl } from "./ports.ts"; + import { invoke } from "@tauri-apps/api/core"; export function getImage(image: string | undefined, fallback: string | undefined): string { if (!image) return fallback ? getImage(fallback, undefined) : "/alert.png"; if (image.startsWith("opendeck/")) return image.replace("opendeck", ""); - if (!image.startsWith("data:")) return "http://localhost:57118/" + image; + if (!image.startsWith("data:")) return getWebserverUrl(image); const svgxmlre = /^data:image\/svg\+xml(?!.*?;base64.*?)(?:;[\w=]*)*,(.+)/; const base64re = /^data:image\/(apng|avif|gif|jpeg|png|svg\+xml|webp|bmp|x-icon|tiff);base64,([A-Za-z0-9+/]+={0,2})?/; if (svgxmlre.test(image)) { diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 5765d61..2b4852c 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -2,6 +2,7 @@ import type { DeviceInfo } from "$lib/DeviceInfo"; import type { Profile } from "$lib/Profile"; + import { initPortBase } from "$lib/ports"; import { inspectedParentAction } from "$lib/propertyInspector"; import { actionList, deviceSelector, profileManager } from "$lib/singletons"; @@ -18,6 +19,8 @@ let devices: { [id: string]: DeviceInfo } = {}; let selectedDevice: string; let selectedProfiles: { [id: string]: Profile } = {}; + + initPortBase(); event.preventDefault()} on:drop={(event) => event.preventDefault()} /> diff --git a/tsconfig.json b/tsconfig.json index e320108..f06842d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "resolveJsonModule": true, "skipLibCheck": true, "sourceMap": true, - "strict": true + "strict": true, + "allowImportingTsExtensions": true } }