Erfolgreiche Verbindung merken und automatisch wieder aufbauen; Hinweistexte gekürzt

Die zuletzt erfolgreich verbundene SwyxTray-Adresse landet im localStorage;
beim Laden der Seite wird damit sofort wieder verbunden, manuelles Trennen
vergisst sie. Untertitel, Backend-Hinweis und Webhook-Erklärtexte entfernt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-27 10:36:24 +02:00
co-authored by Claude Fable 5
parent 97c3778180
commit 715720fbb3
4 changed files with 64 additions and 27 deletions
+1 -10
View File
@@ -65,10 +65,7 @@ export default function WebhookPanel({ events, status, error, onClear }: Props)
{error && <p className="note note--error">{error}</p>} {error && <p className="note note--error">{error}</p>}
{events.length === 0 ? ( {events.length === 0 ? (
<p className="note"> <p className="note">Noch keine Nachricht eingegangen.</p>
Noch keine Nachricht eingegangen. Ein fremdes System schickt Adressdaten an{' '}
<code>POST /api/webhook</code>; sie erscheinen hier ohne Zutun.
</p>
) : ( ) : (
<ul className="webhook"> <ul className="webhook">
{events.map((event) => ( {events.map((event) => (
@@ -83,12 +80,6 @@ export default function WebhookPanel({ events, status, error, onClear }: Props)
))} ))}
</ul> </ul>
)} )}
<p className="note">
Der Webhook nimmt beliebiges JSON an Objekt wie Liste und zeigt es hier unverändert.
Der Verlauf liegt im Speicher des Backends und ist nach dessen Neustart leer; Leeren"
verwirft ihn auch dort.
</p>
</> </>
) )
} }
+30
View File
@@ -6,6 +6,36 @@ export const DEFAULT_WS_PATH = '/ws'
export const DEFAULT_WS_URL = export const DEFAULT_WS_URL =
import.meta.env.VITE_WS_URL ?? `ws://${DEFAULT_WS_HOST}:${DEFAULT_WS_PORT}${DEFAULT_WS_PATH}` import.meta.env.VITE_WS_URL ?? `ws://${DEFAULT_WS_HOST}:${DEFAULT_WS_PORT}${DEFAULT_WS_PATH}`
const SAVED_WS_URL_KEY = 'swyxweb.wsUrl'
/**
* Zuletzt erfolgreich verbundene Adresse. localStorage kann fehlen oder gesperrt
* sein (Privatmodus, Browser-Einstellung) dann wird schlicht nichts gemerkt.
*/
export function loadSavedWsUrl(): string | null {
try {
return localStorage.getItem(SAVED_WS_URL_KEY)
} catch {
return null
}
}
export function saveWsUrl(url: string): void {
try {
localStorage.setItem(SAVED_WS_URL_KEY, url)
} catch {
// Ohne Speicher kein Wiederverbinden nach Neustart mehr geht nicht verloren.
}
}
export function clearSavedWsUrl(): void {
try {
localStorage.removeItem(SAVED_WS_URL_KEY)
} catch {
// Siehe saveWsUrl.
}
}
export interface ClientConfig { export interface ClientConfig {
websocketUrl: string websocketUrl: string
host: string host: string
-5
View File
@@ -131,11 +131,6 @@ code {
gap: 16px; gap: 16px;
} }
.page__footer {
color: var(--text-muted);
font-size: 13px;
}
.card { .card {
background: var(--surface); background: var(--surface);
border: 1px solid var(--border); border: 1px solid var(--border);
+33 -12
View File
@@ -1,5 +1,5 @@
import { useEffect, useRef, useState, type FormEvent, type ReactNode } from 'react' import { useEffect, useRef, useState, type FormEvent, type ReactNode } from 'react'
import { DEFAULT_WS_URL, fetchClientConfig } from '../config' import { DEFAULT_WS_URL, fetchClientConfig, clearSavedWsUrl, loadSavedWsUrl, saveWsUrl } from '../config'
import { useSwyxTray } from '../hooks/useSwyxTray' import { useSwyxTray } from '../hooks/useSwyxTray'
import { useCallNotifications } from '../hooks/useCallNotifications' import { useCallNotifications } from '../hooks/useCallNotifications'
import { useWebhook } from '../hooks/useWebhook' import { useWebhook } from '../hooks/useWebhook'
@@ -68,13 +68,25 @@ export default function HomePage() {
// Der Benutzer soll seine eingetippte Adresse nicht durch die Backend-Antwort verlieren. // Der Benutzer soll seine eingetippte Adresse nicht durch die Backend-Antwort verlieren.
const urlTouched = useRef(false) const urlTouched = useRef(false)
// Adresse des laufenden Verbindungsversuchs; gemerkt wird sie erst, wenn die
// Verbindung tatsächlich zustande kommt.
const pendingUrl = useRef<string | null>(null)
useEffect(() => { useEffect(() => {
// Mit der zuletzt erfolgreich verbundenen Adresse direkt wieder verbinden
// die Backend-Konfiguration muss dann nicht gefragt werden.
const saved = loadSavedWsUrl()
if (saved) {
urlTouched.current = true
setUrl(saved)
pendingUrl.current = saved
tray.connect(saved)
return
}
const controller = new AbortController() const controller = new AbortController()
fetchClientConfig(controller.signal) fetchClientConfig(controller.signal)
.then((backendUrl) => { .then((backendUrl) => {
if (controller.signal.aborted) return if (controller.signal.aborted) return
setConfigNote(`Adresse vom Backend übernommen: ${backendUrl}`)
if (!urlTouched.current) setUrl(backendUrl) if (!urlTouched.current) setUrl(backendUrl)
}) })
.catch((e: unknown) => { .catch((e: unknown) => {
@@ -83,12 +95,28 @@ export default function HomePage() {
setConfigNote(`Backend-Konfiguration nicht abrufbar (${message}) Standardadresse wird verwendet.`) setConfigNote(`Backend-Konfiguration nicht abrufbar (${message}) Standardadresse wird verwendet.`)
}) })
return () => controller.abort() return () => controller.abort()
// eslint-disable-next-line react-hooks/exhaustive-deps -- nur beim Laden der Seite
}, []) }, [])
// Erst die zustande gekommene Verbindung zählt: dann die Adresse merken, damit
// sie nach Verbindungsverlust oder Neustart automatisch wieder aufgebaut wird.
useEffect(() => {
if (status === 'open' && pendingUrl.current) saveWsUrl(pendingUrl.current)
}, [status])
function handleConnect(event: FormEvent) { function handleConnect(event: FormEvent) {
event.preventDefault() event.preventDefault()
const target = url.trim() const target = url.trim()
if (target) tray.connect(target) if (!target) return
pendingUrl.current = target
tray.connect(target)
}
// Manuelles Trennen heißt auch: nach einem Neustart nicht wieder verbinden.
function handleDisconnect() {
clearSavedWsUrl()
pendingUrl.current = null
tray.disconnect()
} }
function handleSendRaw(event: FormEvent) { function handleSendRaw(event: FormEvent) {
@@ -104,7 +132,7 @@ export default function HomePage() {
<StadtboteSignet className="brand__signet" /> <StadtboteSignet className="brand__signet" />
<div> <div>
<h1 className="brand__name">Stadtbote</h1> <h1 className="brand__name">Stadtbote</h1>
<p className="page__subtitle">SwyxWeb · Telefonie über die SwyxTray-App</p> <p className="page__subtitle">SwyxWeb</p>
</div> </div>
</div> </div>
<StatusBadge status={status} /> <StatusBadge status={status} />
@@ -228,7 +256,7 @@ export default function HomePage() {
<button <button
type="button" type="button"
className="button" className="button"
onClick={tray.disconnect} onClick={handleDisconnect}
disabled={!isBusyConnection} disabled={!isBusyConnection}
> >
Trennen Trennen
@@ -292,13 +320,6 @@ export default function HomePage() {
</section> </section>
</TabPanel> </TabPanel>
</main> </main>
<footer className="page__footer">
SwyxTray · Kommandos <code>call</code> / <code>answer</code> / <code>hangup</code> /{' '}
<code>tabs</code> / <code>opentab</code> / <code>closetab</code> / <code>contacts</code> ·
Zustand über{' '}
<code>snapshot</code> · Standardziel <code>{DEFAULT_WS_URL}</code>
</footer>
</div> </div>
) )
} }