Filipes Kurznotiz vom 28.09.2026, von links nach rechts:
Kachel sichtbar -> geschlossen -> Vorbereitung -> Wartebereich/
Chat -> Countdown -> LIVE -> Reaction + Gaeste + Chat + PayPal
-> Ende
DREI STAENDE, NICHT SIEBEN
„Wartebereich" und „Countdown" sind keine eigenen Zustaende, sondern
das, was „Vorbereitung" auf dem Bildschirm TUT. Drei Staende, die
sich gegenseitig ausschliessen, sind pruefbar; sieben, von denen sich
vier ueberlappen, sind es nicht.
DAS VIDEO LAEUFT NICHT UEBER DIESEN SERVER
Naheliegend waere: Der Host spielt ab, alle sehen seinen Bildschirm.
Das waere aus zwei Gruenden falsch. Rechtlich ist ein
weitergesendetes YouTube-Video eine oeffentliche Wiedergabe -- genau
die Sache, fuer die Kanaele gesperrt werden. Und technisch kostet es
Bandbreite und Qualitaet.
Jeder Zuschauer laedt das Video deshalb SELBST. Uebertragen wird nur
der Spielstand: Kennung, laeuft/pausiert, Sekunde. Das sind ein paar
Byte, jeder sieht es in voller Qualitaet, und alle sind auf derselben
Sekunde. Nachgefuehrt wird erst ab anderthalb Sekunden Abweichung --
ein Player, dem man jede Sekunde eine neue Position gibt, ruckelt
sichtbar.
DIE KAMERAS LAUFEN DIREKT VON MENSCH ZU MENSCH
Ueber denselben Weg wie die Anrufe im Haus (seit 18.09.), nur mit
mehr Empfaengern. Das hat eine Grenze, und sie ist gerechnet, nicht
geraten: Bei 360p und rund 350 kbit/s sind zwoelf Zuschauer etwa
4 Mbit/s Upload beim Host. Darueber schaltet die Sendung von selbst
auf Ton um -- wer keine Kamera mehr bekommt, hoert alles, sieht das
Video und kann schreiben. Ehrlicher als eine Verbindung, die stockt,
und sichtbar im Regiepult.
Heute sind es elf Menschen im ganzen Haus (gemessen: 1 admin, 1 hand,
1 linke, 4 modi, 4 gast). Die Grenze ist weit weg -- sie steht
trotzdem drin, weil sie sonst erst auffaellt, wenn es zu spaet ist.
DIE SEITE IST ANDERS GEBAUT ALS JEDE ANDERE IM HAUS
Ueberall sonst: Kacheln, Karten, Listen -- man liest, entscheidet,
geht wieder. Hier sitzt man. Eine Stunde, mit anderen, auf EINE
Sache schauend. Deshalb kein Raster, sondern ein SAAL: grosse Flaeche
fuer das Video, Kamerabilder als schwebende Fenster darueber, der
Chat als Schiene daneben. Die Seite scrollt nicht -- ein Video, das
beim Tippen im Chat nach oben rutscht, ist der schnellste Weg, dass
jemand aufhoert zu schreiben.
Fuer den Host ein REGIEPULT: vier senkrechte Regler nebeneinander wie
an einem Mischpult, darueber die Sendung, daneben Gaeste und
Anordnung, unten drei grosse Knoepfe. Es SCHIEBT den Saal, es deckt
ihn nicht zu.
Die Kachel traegt ihren Zustand als Farbe: grau geschlossen,
bernstein in Vorbereitung, rot auf Sendung. Keine Ton-Nummer -- der
Farbraum ist bei 46 voll, und sie braucht auch keine.
PAYPAL: EINE QUELLE
Der Knopf nimmt den Weg, der auf der Unterstuetzen-Seite hinterlegt
ist -- derselbe Eintrag, dieselbe Pflege. Ist dort nichts eingetragen
oder steht er auf unsichtbar, erscheint hier kein Knopf. Eine
geratene Adresse ist an dieser Stelle die gefaehrlichste aller
Abkuerzungen.
=======================================================================
ACHT FEHLER, DIE OHNE MESSUNG LIVE GEGANGEN WAEREN
=======================================================================
1. `data-live` WAR SCHON VERGEBEN. Die Draussen-Kachel bekommt es,
sobald Filipe auf Twitch sendet. Meine Regel haette ihr waehrend
jedes Streams die Farbe genommen -- genau dann, wenn sie wichtig
ist. Heisst jetzt `data-sendung`, und pruef-reaktion haelt beides
auseinander.
2. DIE INHALTSRICHTLINIE HAETTE YOUTUBE LAUTLOS GESPERRT. Die Datei
warnt an genau dieser Stelle selbst davor: Am 27.08.2026 hat
`frame-src 'none'` den Musik-Knopf stillgelegt -- der Knopf
reagierte, das Feld ging auf, und wo die Player sein sollten,
blieb es leer. Hier waere das Ergebnis eine schwarze Leinwand vor
Publikum gewesen. youtube-nocookie.com fuer den Rahmen (setzt keine
Werbekennungen), www.youtube.com fuer die Einbett-API,
i.ytimg.com fuer die Vorschaubilder.
3. KAMERA UND MIKROFON WAREN GESPERRT. Dieselbe Falle, vor der
index.js selbst warnt -- und die am 18.09. schon einmal zugeschlagen
hat. Die Ausnahme ist jetzt eine benannte MENGE statt eines zweiten
Sonderfalls, und pruef-kamera-richtlinie.mjs haelt sie GEGEN DEN
QUELLTEXT: Welche Seite laedt ein Skript, das getUserMedia
aufruft? Genau die muss drinstehen -- und keine andere. Eine
Liste, die abgeleitet wird, kann nicht veralten.
4. ZWEI ANRUFE AN DIESELBE PERSON. Zwischen `await kameraHolen()` und
dem Anlegen der Verbindung laeuft alles andere weiter; jeder Takt
sagte wieder „den kenne ich noch nicht". Der Empfaenger antwortete
auf beide Angebote, und die zweite Antwort traf eine Verbindung,
die laengst stand.
5. DAS ANGEBOT GING HINAUS, BEVOR DER EMPFAENGER ZUHOEREN KONNTE.
Gemessen:
[spur] an [3] reaktion_signal | offen: [2,1]
...
[spur] Strom auf fuer 3 Lenny
Die Anmeldung ist ein gewoehnlicher Abruf und sofort durch, der
Ereignisstrom eine stehende Verbindung. Der Host erfaehrt vom
Neuankoemmling also zuverlaessig, BEVOR der zuhoeren kann.
Die Richtung ist jetzt umgedreht: Wer bereit ist, BITTET um den
Anruf -- er ist der Einzige, der das sicher weiss. Dazu ein
eigener, schneller Takt (2,5 s) und eine Ruecknahme, wenn ein
Angebot bei niemandem ankommt.
6. EIN VIDEO MIT TON STARTET NICHT VON ALLEIN. `videoWidth` war 640,
das Bild kam also an -- und das Fenster blieb schwarz. Kein
Fehler, keine Meldung, es passiert einfach nichts. Die Kameras
starten jetzt stumm (stumm darf losgehen), ein Knopf schaltet den
Ton frei, und die erste Beruehrung der Seite tut es ohnehin.
7. DIE LADE AM HANDY GING NICHT AUF. Gemessen: ein 390x775 grosser
Saal mit 219 px Video und 556 px Leere darunter. Statt den Knopf
zu reparieren, ist die Lade weg -- unter Kopfleiste und Video
bleiben auf einem Telefon rund 550 px, das ist mehr Chat, als eine
Lade je zeigen wuerde. Ein Zustand weniger ist besser als ein
Zustand, der funktioniert.
8. `sendBeacon` KANN NUR POST. Beim Schliessen des Fensters wird ein
gewoehnlicher Abruf abgebrochen; mein DELETE waere nie angekommen,
und jeder haette zwei Minuten lang als anwesend gegolten.
Dazu drei Funde der Hauspruefungen, alle von mir verursacht:
17 Schriftgroessen unter der Lesbarkeitsgrenze von 11,5 px, elf
Maschinenworte ohne deutschen Satz, und ein Aufbewahrungseintrag ohne
Rechtsgrundlage.
=======================================================================
GEMESSEN
pruef-reaktion 74 Punkte, 0 Fehler (11 Abschnitte)
pruef-kamera-richtlinie 10 Punkte, 0 Fehler (neu, abgeleitet)
mess-reaktion beide Kameras kommen an, 640 px, laufen --
beim Zuschauer UND beim Host. Diese Messung
hat einen Rueckgabewert: Alles andere kann
gruen sein, und trotzdem sitzt jeder vor
einem schwarzen Rechteck.
pruef-handy 180 (vorher 177), pruef-notizen 79,
pruef-aufbewahrung 45, pruef-meldungen 8, pruef-css-klassen 33,
pruef-struktur 35, pruef-crew-adresse 161,
pruef-haus-trennung 100, pruef-start-ansicht 160 -- alle 0 Fehler.
139 lines
5.8 KiB
JavaScript
139 lines
5.8 KiB
JavaScript
/* =====================================================================
|
|
WER DIE KAMERA DARF — und wer sie NICHT darf (28.09.2026)
|
|
|
|
In index.js steht eine Sperre: Mikrofon und Kamera sind auf JEDER
|
|
Seite verboten, ausser auf wenigen benannten. Das ist richtig und
|
|
soll so bleiben -- ohne sie koennte ein eingeschleustes Skript
|
|
ueberall das Mikrofon anschalten.
|
|
|
|
Die Sperre hat aber eine unangenehme Eigenschaft: Wenn sie zu weit
|
|
geht, merkt es niemand beim Bauen. Die Seite laedt, die Knoepfe
|
|
sind da, und erst wenn jemand auf „Kamera an" drueckt, kommt eine
|
|
Meldung, die auf die falsche Faehrte fuehrt -- man sucht dann in
|
|
den Browsereinstellungen. Genau so ist es am 18.09.2026 beim
|
|
Anruf passiert und am 28.09.2026 bei der Reaction.
|
|
|
|
---------------------------------------------------------------------
|
|
DESHALB WIRD HIER NICHT EINE LISTE GEPRUEFT, SONDERN ABGELEITET
|
|
|
|
Gesucht wird im Quelltext: Welche Seite laedt ein Skript, das
|
|
`getUserMedia` aufruft? Genau diese Seiten brauchen die Erlaubnis.
|
|
Eine Liste, die gegen den Quelltext gehalten wird, kann nicht
|
|
veralten -- wer morgen eine dritte Seite mit Kamera baut, bekommt
|
|
hier einen roten Punkt statt in drei Wochen einen Anruf.
|
|
|
|
UND DIE GEGENRICHTUNG WIRD MITGEPRUEFT. Eine Seite, die die
|
|
Erlaubnis hat und sie nicht braucht, ist eine offene Tuer ohne
|
|
Grund. Auch das faellt hier auf.
|
|
|
|
Aufrufen mit: node server/pruef-kamera-richtlinie.mjs
|
|
===================================================================== */
|
|
import { readFileSync, readdirSync } from "node:fs";
|
|
import { dirname, join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const HIER = dirname(fileURLToPath(import.meta.url));
|
|
const WURZEL = join(HIER, "..");
|
|
const SEITEN_ORDNER = join(WURZEL, "workspace");
|
|
const JS_ORDNER = join(SEITEN_ORDNER, "assets", "js");
|
|
|
|
let fehler = 0, geprueft = 0;
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
const melde = (t) => console.log(t);
|
|
|
|
/* ---------- 1. Welche Skripte fassen die Kamera an? --------------------- */
|
|
melde("=== 1. Welche Skripte die Kamera anfassen ===");
|
|
|
|
const jsDateien = readdirSync(JS_ORDNER).filter((n) => n.endsWith(".js"));
|
|
const mitKamera = new Set();
|
|
for (const name of jsDateien) {
|
|
const text = readFileSync(join(JS_ORDNER, name), "utf8");
|
|
/* Der Aufruf selbst, nicht das Wort in einem Kommentar: Es muss
|
|
eine Klammer folgen. Sonst zaehlte jede Erklaerung mit, in der
|
|
`getUserMedia` erwaehnt wird -- und davon gibt es hier viele. */
|
|
if (/getUserMedia\s*\(/.test(text.replace(/\/\*[\s\S]*?\*\//g, ""))) {
|
|
mitKamera.add(name);
|
|
}
|
|
}
|
|
ok(mitKamera.size > 0,
|
|
`${mitKamera.size} Skript(e) rufen getUserMedia auf: ${[...mitKamera].join(", ")}`);
|
|
|
|
/* ---------- 2. Welche Seiten laden diese Skripte? ----------------------- */
|
|
melde("");
|
|
melde("=== 2. Welche Seiten sie laden ===");
|
|
|
|
const seiten = readdirSync(SEITEN_ORDNER).filter((n) => n.endsWith(".html"));
|
|
const brauchen = new Set();
|
|
for (const seite of seiten) {
|
|
const html = readFileSync(join(SEITEN_ORDNER, seite), "utf8");
|
|
for (const js of mitKamera) {
|
|
if (html.includes(`assets/js/${js}`)) { brauchen.add(seite); break; }
|
|
}
|
|
}
|
|
ok(brauchen.size > 0,
|
|
`${brauchen.size} Seite(n) brauchen die Erlaubnis: ${[...brauchen].sort().join(", ")}`);
|
|
|
|
/* ---------- 3. Und was steht in index.js? ------------------------------- */
|
|
melde("");
|
|
melde("=== 3. Der Abgleich ===");
|
|
|
|
const indexText = readFileSync(join(HIER, "index.js"), "utf8");
|
|
const zeile = indexText.match(/const MIT_KAMERA = \[([^\]]*)\]/);
|
|
ok(!!zeile, "die Menge MIT_KAMERA steht in index.js");
|
|
|
|
const erlaubt = new Set();
|
|
if (zeile) {
|
|
for (const m of zeile[1].matchAll(/workspace\\\/([a-z0-9-]+)\\\.html/g)) {
|
|
erlaubt.add(m[1] + ".html");
|
|
}
|
|
}
|
|
ok(erlaubt.size > 0, `sie nennt ${erlaubt.size} Seite(n): ${[...erlaubt].sort().join(", ")}`);
|
|
|
|
const fehlend = [...brauchen].filter((s) => !erlaubt.has(s));
|
|
ok(fehlend.length === 0,
|
|
fehlend.length
|
|
? `FEHLT: ${fehlend.join(", ")} laedt ein Kamera-Skript, steht aber nicht in MIT_KAMERA `
|
|
+ "-- dort geht die Kamera nicht an, ohne dass jemand einen Fehler sieht"
|
|
: "jede Seite, die die Kamera braucht, steht drin");
|
|
|
|
const zuviel = [...erlaubt].filter((s) => !brauchen.has(s));
|
|
ok(zuviel.length === 0,
|
|
zuviel.length
|
|
? `ZU VIEL: ${zuviel.join(", ")} hat die Erlaubnis, braucht sie aber nicht `
|
|
+ "-- eine offene Tuer ohne Grund"
|
|
: "und keine Seite hat sie, ohne sie zu brauchen");
|
|
|
|
/* ---------- 4. Die Gegenprobe ------------------------------------------
|
|
Eine Pruefung, die nur bestaetigen kann, bestaetigt nichts. Hier
|
|
wird erfunden, was passiert waere, haette jemand die Reaction
|
|
vergessen. */
|
|
melde("");
|
|
melde("=== 4. Gegenprobe ===");
|
|
{
|
|
const erfunden = new Set([...erlaubt].filter((s) => s !== "reaktion.html"));
|
|
const haetteGefehlt = [...brauchen].filter((s) => !erfunden.has(s));
|
|
ok(haetteGefehlt.includes("reaktion.html"),
|
|
"ohne reaktion.html in der Menge wuerde die Pruefung rot "
|
|
+ `(${haetteGefehlt.join(", ") || "sie wuerde NICHT anschlagen"})`);
|
|
}
|
|
|
|
/* ---------- 5. Und die Sperre steht ueberhaupt ---------------------------
|
|
Wenn jemand die Zeile ganz entfernt, waeren Kamera und Mikrofon
|
|
ueberall offen -- und keine der Pruefungen oben wuerde das sehen,
|
|
denn beide Mengen waeren dann leer und stimmten wunderbar ueberein. */
|
|
melde("");
|
|
melde("=== 5. Die Sperre selbst ===");
|
|
{
|
|
ok(/microphone=\(\), camera=\(\)/.test(indexText),
|
|
"die Vorgabe verbietet Mikrofon und Kamera");
|
|
ok(/microphone=\(self\), camera=\(self\)/.test(indexText),
|
|
"und oeffnet sie nur fuer die eigene Seite, nicht fuer fremde Rahmen");
|
|
ok(/Permissions-Policy/.test(indexText),
|
|
"der Kopf heisst Permissions-Policy");
|
|
}
|
|
|
|
melde("");
|
|
melde(`${geprueft} Pruefungen, ${fehler} Fehler`);
|
|
melde(fehler === 0 ? "ALLES IN ORDNUNG" : "NICHT IN ORDNUNG");
|
|
process.exit(fehler === 0 ? 0 : 1);
|