Loopback als Vorgabe, verständliche Meldung bei Mixed Content

Die feste Adresse 192.168.180.135 der SwyxTray-App wird überall durch
127.0.0.1 ersetzt: Die App läuft auf dem Arbeitsplatz des Anwenders, die
Verbindung bleibt damit auf dem Rechner und braucht keine Firewall-Freigabe.

Scheitert der Verbindungsaufbau, weil eine über HTTPS ausgelieferte Seite
kein unverschlüsseltes ws:// öffnen darf, meldet der Browser nur "The
operation is insecure." – describeConnectFailure() benennt stattdessen den
Grund und den Ausweg (wss:// oder Loopback).

node_modules/ wird nun auf jeder Ebene ignoriert, nicht nur unter frontend/.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-24 17:16:41 +02:00
co-authored by Claude Opus 5
parent 01594a1f3d
commit 521ceb044b
10 changed files with 60 additions and 16 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
/** Fallback-Adresse des WebSocket-Servers, falls weder .env noch /api/config etwas liefern. */
export const DEFAULT_WS_HOST = '192.168.180.135'
export const DEFAULT_WS_HOST = '127.0.0.1'
export const DEFAULT_WS_PORT = 17654
export const DEFAULT_WS_PATH = '/ws'
+1 -1
View File
@@ -210,7 +210,7 @@ export default function HomePage() {
value={url}
spellCheck={false}
autoComplete="off"
placeholder="ws://192.168.180.135:17654/ws"
placeholder="ws://127.0.0.1:17654/ws"
onChange={(e) => {
urlTouched.current = true
setUrl(e.target.value)
+43 -1
View File
@@ -41,6 +41,48 @@ const DEFAULT_TIMEOUT_MS = 10000
// selbst fünf Sekunden, bevor sie mit einem Fehler antwortet.
const TAB_TIMEOUT_MS = 15000
/** Loopback gilt dem Browser als vertrauenswürdig dorthin ist `ws://` auch von HTTPS aus erlaubt. */
function isLoopback(hostname: string): boolean {
const host = hostname.replace(/^\[|\]$/g, '').toLowerCase()
return (
host === 'localhost' ||
host.endsWith('.localhost') ||
host === '::1' ||
/^127\./.test(host)
)
}
/**
* Eine über HTTPS ausgelieferte Seite darf keine unverschlüsselte `ws://`-Verbindung
* öffnen. Der Browser wirft dafür nur ein nichtssagendes SecurityError
* ("The operation is insecure."), deshalb wird der Grund hier selbst benannt.
*/
export function describeConnectFailure(url: string, message: string): string {
let target: URL | null = null
try {
target = new URL(url)
} catch {
// Wirklich kaputte Adresse dann bleibt es bei der Browser-Meldung.
}
if (
target &&
target.protocol === 'ws:' &&
typeof window !== 'undefined' &&
window.location.protocol === 'https:' &&
!isLoopback(target.hostname)
) {
return (
`Diese Seite läuft über HTTPS; der Browser blockiert deshalb die unverschlüsselte ` +
`Verbindung zu ${url} (Mixed Content). Die SwyxTray-App muss dafür selbst TLS ` +
`anbieten (wss://). Läuft sie auf demselben Rechner wie der Browser, ist ` +
`ws://127.0.0.1:${target.port || '17654'}${target.pathname} erlaubt.`
)
}
return `Ungültige WebSocket-Adresse: ${message}`
}
/**
* WebSocket-Client für die SwyxTray-App. Die Verbindung wird im Browser
* aufgebaut; Quittungen werden über die numerische `id` zugeordnet,
@@ -235,7 +277,7 @@ export class SwyxTrayClient {
socket = new WebSocket(url)
} catch (e) {
const message = e instanceof Error ? e.message : String(e)
this.emit('error', `Ungültige WebSocket-Adresse: ${message}`)
this.emit('error', describeConnectFailure(url, message))
this.emit('status', 'closed')
return
}