import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; /// LiveKit-Beitritts-Token vom Pyramid-Server holen. /// /// Früher hat die App die Tokens selbst signiert – dafür steckte das /// LiveKit-Secret im App-Code, und wer die App hatte, konnte sich für jeden /// Raum und jede Identität ein Token ausstellen. Jetzt signiert nur noch der /// Server (`/api/livekit-token` im Dashboard-Server auf dem Pi): Er prüft den /// Matrix-Login per whoami und setzt die LiveKit-Identität selbst auf die /// geprüfte Matrix-ID. Siehe docs/LIVEKIT_TOKEN_MIGRATION.md. class LiveKitTokenService { static const endpoint = 'https://dashboard.steggi-matrix.work/api/livekit-token'; static const fallbackUrl = 'wss://livekit.steggi-matrix.work'; static const _timeout = Duration(seconds: 15); /// Liefert Token und LiveKit-Adresse für [room]. Wirft /// [LiveKitTokenException] mit einer verständlichen Meldung. static Future<({String token, String url})> fetch({ required String room, required String matrixToken, http.Client? httpClient, }) async { final client = httpClient ?? http.Client(); try { final res = await client .post( Uri.parse(endpoint), headers: {'Content-Type': 'application/json'}, body: jsonEncode({'room': room, 'matrix_token': matrixToken}), ) .timeout(_timeout); if (res.statusCode == 401) { throw const LiveKitTokenException( 'Anmeldung vom Anrufserver abgelehnt – bitte neu anmelden.', ); } if (res.statusCode != 200) { throw LiveKitTokenException( 'Anrufserver antwortet nicht richtig (HTTP ${res.statusCode}).', ); } final data = jsonDecode(res.body); final token = data is Map ? data['token'] : null; if (token is! String || token.isEmpty) { throw const LiveKitTokenException( 'Anrufserver hat kein Zugangstoken geliefert.', ); } final url = (data as Map)['url']; return ( token: token, url: url is String && url.startsWith('wss://') ? url : fallbackUrl, ); } on LiveKitTokenException { rethrow; } on TimeoutException { throw const LiveKitTokenException( 'Anrufserver nicht erreichbar (Zeitüberschreitung).', ); } on SocketException { throw const LiveKitTokenException( 'Anrufserver nicht erreichbar – Internetverbindung prüfen.', ); } on http.ClientException { throw const LiveKitTokenException( 'Anrufserver nicht erreichbar – Internetverbindung prüfen.', ); } on FormatException { throw const LiveKitTokenException( 'Anrufserver hat eine unlesbare Antwort geschickt.', ); } finally { if (httpClient == null) client.close(); } } } class LiveKitTokenException implements Exception { final String message; const LiveKitTokenException(this.message); @override String toString() => message; }