feat: use alternative ports if default ports are already in use

This commit is contained in:
nekename committed 2025-10-10 18:56:32 +01:00
1 parent 799af63e1d
commit 343689c00d
13 files changed
+71 -25

No files matched your search

+1 -1
View File
@@ -1,7 +1,7 @@
{
"identifier": "corsfetch",
"local": true,
"remote": { "urls": ["http://localhost:57118"] },
"remote": { "urls": ["http://localhost:*"] },
"windows": ["*"],
"permissions": ["cors-fetch:default"]
}
+5
View File
@@ -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<String, Category> {
CATEGORIES.read().await.clone()
+2 -1
View File
@@ -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,
+19 -4
View File
@@ -31,6 +31,20 @@ enum PluginInstance {
pub static DEVICE_NAMESPACES: Lazy<RwLock<HashMap<String, String>>> = Lazy::new(|| RwLock::new(HashMap::new()));
static INSTANCES: Lazy<Mutex<HashMap<String, PluginInstance>>> = Lazy::new(|| Mutex::new(HashMap::new()));
pub static PORT_BASE: Lazy<u16> = 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);
+1 -1
View File
@@ -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)]
{
+1 -1
View File
@@ -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
}
+3 -4
View File
@@ -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 @@
<summary class="text-xl font-semibold dark:text-neutral-300">
{#if icon || (actions[0] && plugins.find((x) => x.id == actions[0].plugin) && categories[name].actions.every((x) => x.plugin == actions[0].plugin))}
<img
src={icon
? (!icon.startsWith("opendeck/") ? "http://localhost:57118/" + icon : icon.replace("opendeck", ""))
: "http://localhost:57118/" + plugins.find((x) => 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))}
>
<img
src={!action.icon.startsWith("opendeck/") ? "http://localhost:57118/" + action.icon : action.icon.replace("opendeck", "")}
src={!action.icon.startsWith("opendeck/") ? getWebserverUrl(action.icon) : action.icon.replace("opendeck", "")}
alt={$localisations?.[action.plugin]?.[action.uuid]?.Tooltip ?? action.tooltip}
class="w-12 h-12 rounded-xs pointer-events-none"
/>
+5 -4
View File
@@ -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 @@
<div class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{#each installed.sort((a, b) => (a.builtin && !b.builtin) ? -1 : (b.builtin && !a.builtin) ? 1 : a.id.localeCompare(b.id)) as plugin}
<ListedPlugin
icon="http://localhost:57118/{plugin.icon}"
icon={getWebserverUrl(plugin.icon)}
name={($localisations && $localisations[plugin.id] && $localisations[plugin.id].Name) ? $localisations[plugin.id].Name : plugin.name}
subtitle={plugin.version}
disconnected={!plugin.registered}
+8 -7
View File
@@ -3,6 +3,7 @@
import type { DeviceInfo } from "$lib/DeviceInfo";
import type { Profile } from "$lib/Profile";
import { getWebserverUrl, getWebSocketPort } from "$lib/ports";
import { inspectedInstance } from "$lib/propertyInspector";
import { invoke } from "@tauri-apps/api/core";
@@ -27,13 +28,13 @@
coordinates = { row: Math.floor(position / device.columns), column: position % device.columns };
}
if (instance == null || !iframe.src || !iframe.src.startsWith("http://localhost:57118")) return;
if (instance == null || !iframe.src || !iframe.src.startsWith(getWebserverUrl())) return;
const info = JSON.stringify(await invoke("make_info", { plugin: instance.action.plugin }));
iframe?.contentWindow?.postMessage({
event: "connect",
payload: [
57116,
getWebSocketPort(),
instance.context,
"registerPropertyInspector",
info,
@@ -50,7 +51,7 @@
},
}),
],
}, "http://localhost:57118");
}, getWebserverUrl());
}
const closePopup = (context: string) => {
@@ -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)}
+18
View File
@@ -0,0 +1,18 @@
import { invoke } from "@tauri-apps/api/core";
let portBase = 57116;
let initialised = false;
export async function initPortBase(): Promise<void> {
if (initialised) return;
portBase = await invoke<number>("get_port_base");
initialised = true;
}
export function getWebSocketPort(): number {
return portBase;
}
export function getWebserverUrl(path: string = ""): string {
return `http://localhost:${portBase + 2}/${path}`;
}
+3 -1
View File
@@ -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)) {
+3
View File
@@ -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();
</script>
<svelte:window on:dragover={(event) => event.preventDefault()} on:drop={(event) => event.preventDefault()} />
+2 -1
View File
@@ -9,6 +9,7 @@
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true
"strict": true,
"allowImportingTsExtensions": true
}
}