Skip to content

Commit e42626d

Browse files
Giu PlataniaGiu Platania
authored andcommitted
fixe WS issue
1 parent 9c06c62 commit e42626d

File tree

5 files changed

+74
-10
lines changed

5 files changed

+74
-10
lines changed

TASK.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# TASKS
2-
- 2026-03-01: DONE. Add a remote launcher script that installs the backend from local source, installs npm when needed, and starts the API plus UI.
2+
- 2026-03-01: DONE. Add a remote launcher script that installs the backend from local source, installs npm when needed, and starts the API plus UI with the WebSocket URL derived from the API URL by default.
33
- 2026-03-01: DONE. Add remote access documentation for LAN and public IP RCH deployments.
44
- 2026-02-26: DONE. Add LXMF outbound delivery acknowledgement handling and log recipient delivery confirmations to the event feed.
55
- 2026-02-26: DONE. Abort gateway startup when API host/port is already bound to prevent duplicate hub instances and ratchet contention.

docs/remoteAccess.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ This guide explains how to reach a Reticulum Community Hub (RCH) instance from a
77
- `rch start` is intended for local desktop use and binds the API to `127.0.0.1`.
88
- For LAN or WAN access, run the gateway directly with `--api-host 0.0.0.0` (or another reachable interface).
99
- For a single-command launcher that installs the backend from local source into a venv and starts both services, run `./run_server_ui_remote.sh`.
10+
- `run_server_ui_remote.sh` only preconfigures the REST base URL by default; the UI derives the WebSocket URL from that value unless you explicitly set `VITE_RTH_WS_BASE_URL`.
1011
- Remote clients must authenticate with `RTH_API_KEY`.
1112
- Remote requests can use either `X-API-Key: <key>` or `Authorization: Bearer <key>`.
1213
- If the hub is reachable over the public internet, prefer a VPN or HTTPS/WSS behind a reverse proxy.

run_server_ui_remote.sh

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ case "$RTH_PUBLIC_SCHEME" in
103103
esac
104104

105105
VITE_RTH_BASE_URL="${VITE_RTH_BASE_URL:-${RTH_PUBLIC_SCHEME}://${RTH_PUBLIC_HOST}:${RTH_API_PORT}}"
106-
VITE_RTH_WS_BASE_URL="${VITE_RTH_WS_BASE_URL:-${RTH_PUBLIC_WS_SCHEME}://${RTH_PUBLIC_HOST}:${RTH_API_PORT}}"
106+
EFFECTIVE_WS_BASE_URL="${VITE_RTH_WS_BASE_URL:-${RTH_PUBLIC_WS_SCHEME}://${RTH_PUBLIC_HOST}:${RTH_API_PORT}}"
107107

108108
if command -v python3 >/dev/null 2>&1; then
109109
PYTHON_CMD="python3"
@@ -179,7 +179,20 @@ echo "Starting hub + northbound API at ${VITE_RTH_BASE_URL} (bind ${RTH_API_HOST
179179
BACKEND_PID=$!
180180

181181
echo "Starting UI dev server at http://${RTH_PUBLIC_HOST}:${RTH_UI_PORT} (bind ${RTH_UI_HOST}:${RTH_UI_PORT})"
182-
(cd ui && VITE_RTH_BASE_URL="$VITE_RTH_BASE_URL" VITE_RTH_WS_BASE_URL="$VITE_RTH_WS_BASE_URL" npm run dev -- --host "$RTH_UI_HOST" --port "$RTH_UI_PORT") &
182+
if [[ -n "${VITE_RTH_WS_BASE_URL:-}" ]]; then
183+
(
184+
cd ui && \
185+
VITE_RTH_BASE_URL="$VITE_RTH_BASE_URL" \
186+
VITE_RTH_WS_BASE_URL="$VITE_RTH_WS_BASE_URL" \
187+
npm run dev -- --host "$RTH_UI_HOST" --port "$RTH_UI_PORT"
188+
) &
189+
else
190+
(
191+
cd ui && \
192+
VITE_RTH_BASE_URL="$VITE_RTH_BASE_URL" \
193+
npm run dev -- --host "$RTH_UI_HOST" --port "$RTH_UI_PORT"
194+
) &
195+
fi
183196
UI_PID=$!
184197

185198
cleanup() {
@@ -195,7 +208,11 @@ trap cleanup EXIT INT TERM
195208
echo
196209
echo "Hub+API: storage=${RTH_STORAGE_DIR} mode=${RTH_HUB_MODE} daemon=${RTH_DAEMON} services=${RTH_SERVICES}"
197210
echo "API URL: ${VITE_RTH_BASE_URL}"
198-
echo "WS URL: ${VITE_RTH_WS_BASE_URL}"
211+
if [[ -n "${VITE_RTH_WS_BASE_URL:-}" ]]; then
212+
echo "WS URL: ${VITE_RTH_WS_BASE_URL} (explicit override)"
213+
else
214+
echo "WS URL: ${EFFECTIVE_WS_BASE_URL} (derived from API URL in the UI)"
215+
fi
199216
echo "UI URL: http://${RTH_PUBLIC_HOST}:${RTH_UI_PORT}"
200217
echo
201218
echo "Press Ctrl+C to stop both processes."

ui/src/stores/connection.spec.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,24 @@ describe("connection store target and auth validation", () => {
4545
expect(connectionStore.authValidationError).toBe("");
4646
expect(connectionStore.hasValidAuthConfig()).toBe(true);
4747
});
48+
49+
it("normalizes a redundant stored websocket base url to follow the base url", () => {
50+
window.localStorage.setItem(
51+
"rth-ui-connection",
52+
JSON.stringify({
53+
baseUrl: "http://10.0.0.5:8000",
54+
wsBaseUrl: "ws://10.0.0.5:8000",
55+
authMode: "apiKey",
56+
apiKey: "secret"
57+
})
58+
);
59+
60+
const connectionStore = useConnectionStore();
61+
62+
expect(connectionStore.wsBaseUrl).toBe("");
63+
expect(connectionStore.resolveWsUrl("/events/system")).toBe("ws://10.0.0.5:8000/events/system");
64+
65+
connectionStore.baseUrl = "http://192.168.1.20:8000";
66+
expect(connectionStore.resolveWsUrl("/events/system")).toBe("ws://192.168.1.20:8000/events/system");
67+
});
4868
});

ui/src/stores/connection.ts

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,16 @@ const normalizeOrigin = (value: string): string => {
5757
}
5858
};
5959

60+
const deriveWsBaseUrl = (origin: string): string => {
61+
const normalizedOrigin = normalizeOrigin(origin);
62+
if (!normalizedOrigin) {
63+
return "";
64+
}
65+
const scheme = normalizedOrigin.startsWith("https") ? "wss" : "ws";
66+
const host = normalizedOrigin.replace(/^https?:\/\//, "");
67+
return `${scheme}://${host}`;
68+
};
69+
6070
const resolveDefaultBaseUrl = (): string => {
6171
const envBaseUrl = import.meta.env.VITE_RTH_BASE_URL;
6272
if (envBaseUrl) {
@@ -80,18 +90,30 @@ const resolveDefaultWsBaseUrl = (): string => {
8090
};
8191

8292
export const useConnectionStore = defineStore("connection", () => {
93+
const defaultBaseUrl = resolveDefaultBaseUrl();
94+
const defaultWsBaseUrl = resolveDefaultWsBaseUrl();
8395
const stored = loadJson<ConnectionState>(STORAGE_KEY, {
84-
baseUrl: resolveDefaultBaseUrl(),
85-
wsBaseUrl: resolveDefaultWsBaseUrl(),
96+
baseUrl: defaultBaseUrl,
97+
wsBaseUrl: defaultWsBaseUrl,
8698
authMode: "none",
8799
token: "",
88100
apiKey: "",
89101
rememberSecrets: false
90102
});
91103

92104
const rememberSecrets = ref<boolean>(stored.rememberSecrets ?? false);
93-
const baseUrl = ref<string>(stored.baseUrl ?? "");
94-
const wsBaseUrl = ref<string>(stored.wsBaseUrl ?? "");
105+
const initialBaseUrl = normalizeOrigin(stored.baseUrl ?? "") || defaultBaseUrl;
106+
const persistedWsBaseUrl = normalizeOrigin(stored.wsBaseUrl ?? "");
107+
const derivedWsBaseUrl = deriveWsBaseUrl(initialBaseUrl);
108+
const initialWsBaseUrl =
109+
persistedWsBaseUrl && persistedWsBaseUrl !== derivedWsBaseUrl
110+
? persistedWsBaseUrl
111+
: defaultWsBaseUrl && normalizeOrigin(defaultWsBaseUrl) !== derivedWsBaseUrl
112+
? normalizeOrigin(defaultWsBaseUrl)
113+
: "";
114+
115+
const baseUrl = ref<string>(initialBaseUrl);
116+
const wsBaseUrl = ref<string>(initialWsBaseUrl);
95117
const authMode = ref<AuthMode>(stored.authMode ?? "none");
96118
const token = ref<string>(rememberSecrets.value ? (stored.token ?? "") : "");
97119
const apiKey = ref<string>(rememberSecrets.value ? (stored.apiKey ?? "") : "");
@@ -259,9 +281,13 @@ export const useConnectionStore = defineStore("connection", () => {
259281
};
260282

261283
const persist = (includeSecrets = rememberSecrets.value) => {
284+
const normalizedBaseUrl = normalizeOrigin(baseUrl.value);
285+
const normalizedWsBaseUrl = normalizeOrigin(wsBaseUrl.value);
286+
const persistedWsUrl =
287+
normalizedWsBaseUrl && normalizedWsBaseUrl !== deriveWsBaseUrl(normalizedBaseUrl) ? normalizedWsBaseUrl : "";
262288
saveJson(STORAGE_KEY, {
263-
baseUrl: baseUrl.value,
264-
wsBaseUrl: wsBaseUrl.value,
289+
baseUrl: normalizedBaseUrl,
290+
wsBaseUrl: persistedWsUrl,
265291
authMode: authMode.value,
266292
token: includeSecrets ? token.value : "",
267293
apiKey: includeSecrets ? apiKey.value : "",

0 commit comments

Comments
 (0)