Server-Adresse: das Schema richtet sich nach dem Ziel

Beim Neuverbinden stand "localhost:5173" im Feld, die App meldete "nicht
erreichbar". Ursache war basisUrlNormalisieren(): ohne Schema setzte es
blind https:// davor - womit die eigene Vorlage der App unbrauchbar war,
denn im Feld steht als Beispiel 192.168.1.20:8123, und im Heimnetz gibt
es kein Zertifikat.

Jetzt nach dem Ziel: localhost, 127.x, 10.x, 192.168.x, 172.16-31.x, ::1
und .local/.home.arpa/.localhost bekommen http, alles andere weiter https
(datametric360.de laeuft ueber Cloudflare). Steht ein Schema da, wird
nichts geraten.

Vier Regressionstests, darunter der gemeldete Fall, die Vorlage aus dem
Feld und 172.32.0.1 als Gegenprobe zum privaten Bereich.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-04 14:15:31 +02:00
parent 9652a436e0
commit e237bf1e8b
6 changed files with 119 additions and 4 deletions
+35 -1
View File
@@ -13,7 +13,8 @@ Debug-Knopf und der HA-Zugang bei den Zugaengen; der Cache-Brecher der Bilder ha
jetzt am Foto statt am App-Start; dazu ein Audit mit zwei Befunden in der
Wisch-Zeile und der Auswahlliste der App; der Leerzustand der Karte ist deckend,
dazu die Rueckfrage vor loeschenden Aktionen als eigenes Blatt statt als
Browserdialog, `2026.9.4.17`,
Browserdialog; das geratene Schema der Server-Adresse richtet sich jetzt nach dem
Ziel, `2026.9.4.18`,
Abschnitte CC bis CK. Davor: Geraetezeit statt Ankunftszeit - die Wurzel hinter der 3-km-Fahrt:
das Fahrtfenster stand in Geraetezeit, der Verlauf war nach Ankunftszeit sortiert. Dazu die neun
Paritaetsbefunde und die verunreinigte Batteriehistorie, `2026.9.4.1`, Abschnitt CB. Davor: Der Regler stellt den Schlaf-Timeout des Dongles, flespi lesend und
@@ -10363,3 +10364,36 @@ nicht** - das haette den eingefuegten Token des Eigentuemers geloescht;
Abbrechen schliesst das Blatt, die Sitzung bleibt.
Kein `window.confirm` mehr im Quelltext ausser dem dokumentierten Rueckfall.
### Nachtrag (2026.9.4.18): das geratene Schema war falsch herum
Beim Neuverbinden stand „localhost:5173" im Adressfeld, und die App meldete
„Der Server ist unter dieser Adresse nicht erreichbar". Ursache war nicht die
Eingabe, sondern `basisUrlNormalisieren()`: fehlte das Schema, setzte es
**blind `https://`** davor.
Damit war die **eigene Vorlage der App** unbrauchbar - im Eingabefeld steht
als Beispiel `192.168.1.20:8123`, und genau so eingetippt entstand
`https://192.168.1.20:8123`. Im Heimnetz gibt es kein Zertifikat, Home
Assistant spricht dort `http`.
Geraten wird jetzt nach dem Ziel: `localhost`, `127.x`, `10.x`,
`192.168.x`, `172.16-31.x`, `::1`, `.local`/`.home.arpa`/`.localhost` bekommen
`http`, alles andere weiterhin `https` - datametric360.de laeuft ueber
Cloudflare, dort waere `http` ein Rueckschritt. Steht ein Schema da, wird
nichts geraten.
Vier Regressionstests (`src/api/umgebung.test.ts`), darunter der gemeldete
Fall und die Vorlage aus dem Eingabefeld; `172.32.0.1` als Gegenprobe, weil
der private Bereich bei `172.31` endet.
Ohne Entsprechung im Panel, notwendigerweise: es kennt keine Server-Adresse.
**Nebenbefund, nicht reproduzierbar:** unmittelbar nach dem Verbinden zeigte
die App einmalig „401 Unauthorized" auf einer Datenabfrage, obwohl die
Pruefung beim Einrichten (`GET /api/`) durchgegangen war. Direkt danach
gemessen: derselbe gespeicherte Token liefert an beiden Endpunkten **200**,
und ein Neuladen zeigt die App vollstaendig. Der Verdacht ist ein Rest der
alten Sitzung im Speicher der Seite (getrennt und ohne Neuladen neu
verbunden); wiederholt sich das, gehoert die Reihenfolge in `fertig()` ->
`AngemeldeteApp` -> `DatenAnbieter` genauer angesehen.
+48
View File
@@ -0,0 +1,48 @@
/**
* Das Schema der Server-Adresse.
*
* Anlass: beim Neuverbinden stand „localhost:5173" im Feld — die App machte
* daraus `https://localhost:5173` und meldete „Der Server ist unter dieser
* Adresse nicht erreichbar". Das Beispiel im Feld selbst
* (`192.168.1.20:8123`) hatte dasselbe Problem: im Heimnetz gibt es kein
* Zertifikat, Home Assistant spricht dort `http`.
*/
import { describe, expect, it } from "vitest"
import { basisUrlNormalisieren } from "./umgebung"
describe("basisUrlNormalisieren", () => {
it("nimmt http fuer das eigene Netz", () => {
// Genau die Vorlage aus dem Eingabefeld, und der gemeldete Fall.
expect(basisUrlNormalisieren("192.168.1.20:8123")).toBe("http://192.168.1.20:8123")
expect(basisUrlNormalisieren("localhost:5173")).toBe("http://localhost:5173")
expect(basisUrlNormalisieren("127.0.0.1:8123")).toBe("http://127.0.0.1:8123")
expect(basisUrlNormalisieren("10.0.0.5:8123")).toBe("http://10.0.0.5:8123")
expect(basisUrlNormalisieren("172.16.3.4:8123")).toBe("http://172.16.3.4:8123")
expect(basisUrlNormalisieren("homeassistant.local:8123")).toBe(
"http://homeassistant.local:8123",
)
})
it("bleibt bei https fuer alles von aussen Erreichbare", () => {
// Der echte Betriebsfall: die Domain laeuft ueber Cloudflare.
expect(basisUrlNormalisieren("datametric360.de")).toBe("https://datametric360.de")
expect(basisUrlNormalisieren("ha.example.com:8123")).toBe("https://ha.example.com:8123")
// 172.32 liegt bereits AUSSERHALB des privaten Bereichs (nur 16-31).
expect(basisUrlNormalisieren("172.32.0.1")).toBe("https://172.32.0.1")
})
it("raet gar nicht, wenn ein Schema dasteht", () => {
expect(basisUrlNormalisieren("https://192.168.1.20:8123")).toBe("https://192.168.1.20:8123")
expect(basisUrlNormalisieren("http://datametric360.de")).toBe("http://datametric360.de")
})
it("schneidet abschliessende Schraegstriche ab", () => {
// Sonst entstehen Adressen wie "https://host//api/states".
expect(basisUrlNormalisieren(" https://datametric360.de/// ")).toBe(
"https://datametric360.de",
)
expect(basisUrlNormalisieren("192.168.1.20:8123/")).toBe("http://192.168.1.20:8123")
})
})
+34 -1
View File
@@ -129,9 +129,42 @@ export async function zugangVerwerfen(): Promise<void> {
/** Schneidet abschließende Schrägstriche ab und ergänzt fehlendes Schema.
Ohne das entstehen sonst Adressen wie "https://host//api/states". */
/**
* Adressen, die per Definition im eigenen Netz liegen - dort gibt es kein
* Zertifikat, also spricht der Server http.
*
* Home Assistant im Heimnetz ist genau dieser Fall: "192.168.1.20:8123" steht
* sogar als Beispiel im Eingabefeld. Bis zum 04.09.2026 setzte die App davor
* blind https:// - die eigene Vorlage, so eingetippt, ergab damit eine
* Adresse, unter der nichts antwortet ("Der Server ist unter dieser Adresse
* nicht erreichbar", gemeldet beim Neuverbinden).
*
* Umgekehrt bleibt https richtig fuer alles, was von aussen erreichbar ist -
* datametric360.de laeuft ueber Cloudflare, dort waere http ein Rueckschritt.
* Deshalb nicht generell umgestellt, sondern nach dem Ziel unterschieden.
*/
function imEigenenNetz(host: string): boolean {
const h = host.toLowerCase().replace(/:[0-9]+$/, "").replace(/^\[|\]$/g, "");
if (h === "localhost" || h === "::1") return true;
if (h.endsWith(".local") || h.endsWith(".home.arpa") || h.endsWith(".localhost")) return true;
const v4 = /^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/.exec(h);
if (!v4) return false;
const a = Number(v4[1]);
const b = Number(v4[2]);
return a === 127 || a === 10 || (a === 192 && b === 168) || (a === 172 && b >= 16 && b <= 31);
}
/** Schneidet abschliessende Schraegstriche ab und ergaenzt fehlendes Schema.
Ohne das entstehen sonst Adressen wie "https://host//api/states".
Geraten wird nur, wenn kein Schema dasteht - wer http:// oder https://
tippt, bekommt genau das. */
export function basisUrlNormalisieren(eingabe: string): string {
let url = eingabe.trim();
if (!/^https?:\/\//i.test(url)) url = `https://${url}`;
if (!/^https?:\/\//i.test(url)) {
const ziel = url.split("/")[0] ?? "";
url = `${imEigenenNetz(ziel) ? "http" : "https"}://${url}`;
}
return url.replace(/\/+$/, "");
}
@@ -1 +1 @@
{"version":"2026.9.4.17","sha256":"72442f3c75c5a9a467d552e55ce3892686d7bdecb7ff147920bf3bbcb78cb1e3","bytes":322203,"gebaut":"2026-09-04T12:06:58Z"}
{"version":"2026.9.4.18","sha256":"d8b50e70f23ee796ac1618a94ee49893c202bed6139863e840fa76456b2694ca","bytes":322373,"gebaut":"2026-09-04T12:14:55Z"}
@@ -1,7 +1,7 @@
{
"domain": "audi_dashboard",
"name": "Audi Dashboard",
"version": "2026.9.4.17",
"version": "2026.9.4.18",
"documentation": "https://gitea.nothaft.cloud/paul/audi-app/src/branch/main/README.md",
"issue_tracker": "https://gitea.nothaft.cloud/paul/audi-app/issues",
"codeowners": [