diff --git a/AGENTS.md b/AGENTS.md index ee20603..7e0c4ea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/companion-app/src/api/umgebung.test.ts b/companion-app/src/api/umgebung.test.ts new file mode 100644 index 0000000..34ac24e --- /dev/null +++ b/companion-app/src/api/umgebung.test.ts @@ -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") + }) +}) diff --git a/companion-app/src/api/umgebung.ts b/companion-app/src/api/umgebung.ts index 0d1cff5..71f6801 100644 --- a/companion-app/src/api/umgebung.ts +++ b/companion-app/src/api/umgebung.ts @@ -129,9 +129,42 @@ export async function zugangVerwerfen(): Promise { /** 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(/\/+$/, ""); } diff --git a/custom_components/audi_dashboard/frontend/app/bundle.json b/custom_components/audi_dashboard/frontend/app/bundle.json index 7311a83..5669de6 100644 --- a/custom_components/audi_dashboard/frontend/app/bundle.json +++ b/custom_components/audi_dashboard/frontend/app/bundle.json @@ -1 +1 @@ -{"version":"2026.9.4.17","sha256":"72442f3c75c5a9a467d552e55ce3892686d7bdecb7ff147920bf3bbcb78cb1e3","bytes":322203,"gebaut":"2026-09-04T12:06:58Z"} \ No newline at end of file +{"version":"2026.9.4.18","sha256":"d8b50e70f23ee796ac1618a94ee49893c202bed6139863e840fa76456b2694ca","bytes":322373,"gebaut":"2026-09-04T12:14:55Z"} \ No newline at end of file diff --git a/custom_components/audi_dashboard/frontend/app/bundle.zip b/custom_components/audi_dashboard/frontend/app/bundle.zip index 6571b28..c8f4582 100644 Binary files a/custom_components/audi_dashboard/frontend/app/bundle.zip and b/custom_components/audi_dashboard/frontend/app/bundle.zip differ diff --git a/custom_components/audi_dashboard/manifest.json b/custom_components/audi_dashboard/manifest.json index b4b2926..3ac28fd 100644 --- a/custom_components/audi_dashboard/manifest.json +++ b/custom_components/audi_dashboard/manifest.json @@ -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": [