Die haeufigste Aussage beim Kunden ist "Internet geht nicht". Bisher musste der Techniker Ping, Traceroute und Browser einzeln bedienen und selbst schlussfolgern, welches Glied hakt. Das Werkzeug misst alle vier Stufen in einem Lauf und benennt das Fehlerglied. Neue native Methoden: dnsResolve (System-Resolver; die Grenze - Server nicht waehlbar, Cache dazwischen - steht im Code und im Ergebnis) und httpsCheck. Bei httpsCheck ist instanceFollowRedirects=false keine Feinheit, sondern der Kern: ein Gaeste-WLAN beantwortet jede Anfrage mit einer Umleitung auf sein Portal. Wer ihr folgt, bekommt ein sauberes 200 und meldet "Internet in Ordnung", obwohl ohne Anmeldung nichts geht. linkInfo liefert zusaetzlich NET_CAPABILITY_VALIDATED und _CAPTIVE_PORTAL - Androids eigenes Urteil, das uns keine einzige Messung kostet. Drei Entscheidungen fuer die Ehrlichkeit der Aussage: 1. Die Kette bricht nicht ab. Sehr viele Router beantworten Ping grundsaetzlich nicht - ein Abbruch dort meldete "Gateway tot" bei voellig gesundem Internet. 2. Eine stille Stufe ist kein Fehler, wenn eine spaetere antwortet. Wer eine Antwort aus dem Internet bekommt, hat sein Gateway benutzt. Ergebnis: gruen mit Hinweis statt rot. 3. Anmeldeseite schlaegt Fehlerglied. Sonst stuende im Kundendokument "Kein Internet - HTTPS antwortet nicht", obwohl der Techniker nur auf "Anmelden" tippen muss. Beim Pruefen aufgefallen und korrigiert. Die Bewertung liegt in kette-bewertung.ts ohne jeden Laufzeit-Import, damit tools/pruefe-kette.mjs sie laden kann: elf konstruierte Faelle gegen von Hand ermittelte Sollwerte, alle korrekt. Der Browser-Mock liefert fuer DNS und HTTPS bewusst nur den Gutfall und beweist nichts - das steht dort auch so. Dabei repariert: tools/pruefe-monitor.mjs importierte eine .mjs-Kopie aus einem /tmp-Sitzungsverzeichnis und lief nach dessen Wegfall nicht mehr, waehrend in der ROADMAP "alle Faelle korrekt" stand. Die Rechnung liegt jetzt in monitor-auswertung.ts, beide Pruefer laden die TS-Datei direkt. Neu in der Debug-Seite: "Internet-Kette pruefen (misst nur, speichert nichts)". Beantwortet vor Ort die erste aller Fragen - kommt das Messgeraet selbst ins Netz? - und ist der einzige Weg, die native Schicht im Emulator zu pruefen, solange die Testinstanz nur HTTP spricht (der WebView blockiert aus der Origin https://localhost jede HTTP-Anfrage, siehe ROADMAP). Im Emulator geprueft: Gutfall (alle vier Stufen mit Messwerten, validated=ja) und Flugmodus (Status "nicht messbar", Fehlerglied Anschluss, ausdruecklich KEINE Aussage ueber das Kundennetz). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
162 lines
6.1 KiB
TypeScript
162 lines
6.1 KiB
TypeScript
/**
|
|
* 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,
|
|
};
|
|
}
|