/** * Bewertung der Internet-Kette — bewusst OHNE jeden Laufzeit-Import. * * Warum eine eigene Datei: die Fehlerpfade dieses Werkzeugs (kein Gateway, * DNS tot, Anmeldeseite, HTTPS blockiert) lassen sich im Browser-Mock nicht * ehrlich nachstellen — dort ist immer alles grün. Geprüft wird deshalb mit * `tools/pruefe-kette.mjs` gegen konstruierte Stufen mit von Hand ermitteltem * Sollwert. Damit dieser Prüfer die Funktion laden kann, darf hier nichts * stehen, was `scanner.ts` (und damit Capacitor) nachzieht: nur `import type`, * das beim Ausführen mit Node restlos verschwindet. * * Der Ausweg „Datei von Hand nach .mjs kopieren" wurde beim Monitor gewählt * und hat sich als Sackgasse erwiesen: die Kopie lag in einem * Sitzungsverzeichnis, das Prüfwerkzeug lief nach dessen Wegfall nicht mehr, * und im Repo stand trotzdem „alle Fälle korrekt". */ import type { MeasureStatus } from '../types'; /** Eine Stufe der Kette, so wie sie gemessen wurde */ export interface KettenStufe { /** Kurzname für das Kundendokument („Gateway", „Namensauflösung" …) */ name: string; /** * true = Stufe hat geantwortet * false = Stufe hat nicht geantwortet * null = nicht messbar (die Voraussetzung fehlte, z.B. kein Gateway bekannt) */ ok: boolean | null; /** Messwert/Antwort als Klartext für das Protokoll */ detail: string; /** * true = diese Stufe darf allein kein Rot auslösen, solange eine spätere * Stufe geantwortet hat. Sehr viele Router beantworten Ping grundsätzlich * nicht; wer eine Antwort aus dem Internet bekommt, hat sein Gateway aber * nachweislich benutzt. */ stillErlaubt?: boolean; } /** Ergebnis der Kettenbewertung */ export interface KettenUrteil { status: MeasureStatus; label: string; /** benanntes Fehlerglied, leer wenn die Kette durchgeht */ fehlerglied: string; hinweis?: string; } /** * Aus den gemessenen Stufen das Urteil bilden. * * @param stufen die Stufen in Reihenfolge (Anschluss zuerst, HTTPS zuletzt) * @param portalErkannt Anmeldeseite erkannt (Weiterleitung oder Systemurteil) * @param systemUrteil Androids NET_CAPABILITY_VALIDATED, falls bekannt */ export function bewerteKette( stufen: KettenStufe[], portalErkannt: boolean, systemUrteil?: boolean, ): KettenUrteil { // Gar nichts messbar (kein Anschluss) — Status 3 statt einer Falschaussage // über das Kundennetz. Ohne eigene IP haben wir schlicht nicht gemessen. const anschluss = stufen[0]; if (!anschluss || anschluss.ok === null) { return { status: 3, label: 'Nicht messbar — kein aktiver Netzwerkanschluss', fehlerglied: 'Anschluss', hinweis: 'Das Messgerät hatte keine IP-Adresse. Es wurde nichts über das Kundennetz gemessen.', }; } /* * Die Anmeldeseite wird VOR der Fehlerglied-Suche entschieden. Sie erklärt * sämtliche anderen Symptome: ein Gäste-WLAN-Portal beantwortet den * HTTPS-Abruf mit einer Umleitung (die Stufe gilt damit als nicht bestanden) * und biegt oft auch die Namensauflösung um. Ohne diesen Vorrang stand im * Kundendokument „Kein Internet — HTTPS-Abruf antwortet nicht", obwohl der * Server sauber geantwortet hat und der Techniker nur auf „Anmelden" tippen * muss. Das ist der Unterschied zwischen einem Anruf beim Provider und * einem erledigten Termin. */ if (portalErkannt) { return { status: 1, label: 'Anmeldeseite im Weg — Internet erst nach Anmeldung', fehlerglied: 'Anmeldeseite', hinweis: 'Die Anfrage wurde auf eine Anmeldeseite umgeleitet (typisch für ein ' + 'Gäste-WLAN). Ohne Anmeldung kommt kein Gerät ins Internet — Kameras, ' + 'Wechselrichter und Wallboxen können sich dort grundsätzlich nicht melden.', }; } // Index der letzten Stufe, die geantwortet hat — entscheidet darüber, ob das // Schweigen einer früheren Stufe entschuldigt ist. const letzteAntwortIdx = stufen.reduce((idx, s, i) => (s.ok === true ? i : idx), -1); // Fehlerglied: erste Stufe ohne Antwort, deren Schweigen nicht durch eine // spätere Antwort erklärt ist. let fehlerglied = ''; for (let i = 0; i < stufen.length; i++) { const s = stufen[i]; if (s.ok !== false) continue; if (s.stillErlaubt && letzteAntwortIdx > i) continue; fehlerglied = s.name; break; } if (fehlerglied !== '') { return { status: 2, label: `Kein Internet — ${fehlerglied} antwortet nicht`, fehlerglied, }; } const httpsStufe = stufen[stufen.length - 1]; const internetGeht = httpsStufe?.ok === true; // Alles geantwortet, aber Android hält die Verbindung nicht für bestätigt: // „HTTPS geht, irgendetwas anderes klemmt trotzdem" — gelb, nicht grün. if (internetGeht && systemUrteil === false) { return { status: 1, label: 'Internet erreichbar, vom System aber nicht bestätigt', fehlerglied: '', hinweis: 'Alle Stufen haben geantwortet, Android stuft die Verbindung dennoch ' + 'nicht als bestätigt ein. Das deutet auf eine teilweise gefilterte ' + 'Verbindung hin (Jugendschutz, Firewall, Portsperre).', }; } // Eine Stufe, die nicht messbar war (z.B. kein Gateway gemeldet), ist kein // Erfolg — sie darf aber auch nicht als Störung ausgewiesen werden. const stille = stufen.filter((s) => s.ok === false && s.stillErlaubt); const unbekannt = stufen.filter((s) => s.ok === null && s.stillErlaubt); const hinweise: string[] = []; if (stille.length) { hinweise.push( `${stille.map((s) => s.name).join(' und ')} antwortet nicht auf Ping, ` + 'leitet den Verkehr aber weiter — das ist bei vielen Routern die ' + 'Werkseinstellung und keine Störung.', ); } if (unbekannt.length) { hinweise.push( `${unbekannt.map((s) => s.name).join(' und ')} konnte nicht geprüft werden ` + '(vom System nicht gemeldet).', ); } return { status: 0, label: 'Internet erreichbar — Gateway, Namensauflösung und HTTPS in Ordnung', fehlerglied: '', hinweis: hinweise.length ? hinweise.join(' ') : undefined, }; }