diff --git a/web/app/src/api.ts b/web/app/src/api.ts index b7aa853..c4fcec8 100644 --- a/web/app/src/api.ts +++ b/web/app/src/api.ts @@ -268,14 +268,53 @@ function geraetename(): string { return `${was} · ${/Firefox/.test(u) ? 'Firefox' : /Chrome/.test(u) ? 'Chrome' : 'Safari'}`; } +/** Eine zufällige Kennung — auch ohne HTTPS. + * + * **`crypto.randomUUID` gibt es nur im secure context.** Das ist HTTPS oder + * `localhost`, sonst nichts. Wer Flaro auf dem NAS betreibt und ihn unter + * `http://192.168.x.x:8097` aufruft, hat die Funktion nicht — und bekam + * mitten in der Einrichtung „crypto.randomUUID is not a function" zu sehen. + * Das ist nicht der Sonderfall, das ist der Normalfall im eigenen Netz. + * + * `crypto.getRandomValues` ist nicht so beschränkt und liefert denselben + * Zufall. Die letzte Stufe mit `Math.random` taugt für Geheimnisse nicht — + * hier geht es aber nur darum, zwei Browser auseinanderzuhalten, und gar + * keine Kennung wäre schlechter als eine schwache. + */ +function zufallskennung(): string { + if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { + return crypto.randomUUID(); + } + + const bytes = new Uint8Array(16); + if (typeof crypto !== 'undefined' && typeof crypto.getRandomValues === 'function') { + crypto.getRandomValues(bytes); + } else { + for (let i = 0; i < bytes.length; i++) bytes[i] = Math.floor(Math.random() * 256); + } + + // Fassung 4 und Variante nach RFC 4122 — Byte 6 und 8 tragen die + // Kennzeichen. Ohne sie sind es sechzehn Zufallsbytes, die nur aussehen + // wie eine UUID. Gesetzt wird beim Umwandeln und nicht über den Index: + // `bytes[6]` gilt dem Typprüfer als womöglich undefiniert, und das + // Gegenteil zu behaupten wäre die unaufrichtigere Lösung. + const hex = Array.from(bytes, (b, i) => { + const wert = i === 6 ? (b & 0x0f) | 0x40 + : i === 8 ? (b & 0x3f) | 0x80 + : b; + return wert.toString(16).padStart(2, '0'); + }).join(''); + return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`; +} + /** Bleibt gleich, damit derselbe Browser nicht als zehn Geräte dasteht. */ function geraetekennung(): string { const name = 'flaro.device'; try { let vorhanden = localStorage.getItem(name); - if (!vorhanden) { vorhanden = crypto.randomUUID(); localStorage.setItem(name, vorhanden); } + if (!vorhanden) { vorhanden = zufallskennung(); localStorage.setItem(name, vorhanden); } return vorhanden; - } catch { return crypto.randomUUID(); } + } catch { return zufallskennung(); } } /* ---------- Mediathek ---------- */