import 'package:flutter/foundation.dart'; import 'package:matrix/matrix.dart'; /// Kennung der Account-Daten, mit denen wir den laufenden Sync aufwecken. const logoutWakeAccountDataType = 'io.pyramid.logout_wake'; /// Meldet [client] ab, ohne dass eine verspätete Server-Antwort die NÄCHSTE /// Anmeldung wieder löscht. /// /// Hintergrund (SDK-Wettlauf, ROADMAP M1, gefunden 2026-10-07): matrix 6.2 /// (und Upstream `main`) prüft bei einer FEHLER-Antwort des Sync-Long-Polls /// nicht, ob die Anfrage noch aktuell ist. Läuft beim Abmelden noch ein /// Long-Poll (bis 30 s) mit dem alten Token, kommt dessen 401 /// (M_UNKNOWN_TOKEN) später zurück und das SDK ruft `clear()` – meldet sich /// der Nutzer in der Zwischenzeit auf demselben Client neu an (Pyramid /// benutzt nach dem Logout dasselbe Client-Objekt weiter), wird die frische /// Session gelöscht: ein „Random-Logout“ direkt nach dem Login. /// /// Ablauf hier: /// 1. Sync-Schleife anhalten (nach der laufenden Anfrage keine neue). /// 2. Laufenden Long-Poll WECKEN: geänderte Account-Daten lässt der Server /// sofort antworten – statt bis zu 30 s zu warten. /// 3. Die laufende Anfrage regulär (mit gültigem Token) zu Ende gehen lassen. /// 4. Erst dann serverseitig abmelden – es ist keine Anfrage mehr unterwegs. /// /// Schritt 2/3 sind „best effort“: scheitern sie (offline), wird nach /// höchstens ~8 s trotzdem abgemeldet – nie schlechter als das bisherige /// direkte `logout()`. /// /// Live gemessen (test/live_logout_race_test.dart, 2026-10-07): die /// verspätete 401 tritt real auf, kommt aber meist an, BEVOR ein Mensch sich /// neu angemeldet hat – das Risiko ist klein, dieser Weg schließt es aus. Future logoutWithoutStraySync( Client client, { Duration settleTimeout = const Duration(seconds: 5), }) async { client.backgroundSync = false; // Liefert den laufenden Sync (oder startet einen kurzen ohne Long-Poll). final inFlight = client.oneShotSync(timeout: Duration.zero); final userId = client.userID; if (userId != null) { try { await client .setAccountData(userId, logoutWakeAccountDataType, { 'ts': DateTime.now().millisecondsSinceEpoch, }) .timeout(const Duration(seconds: 3)); } catch (e) { debugPrint('[LOGOUT] Sync-Wecken fehlgeschlagen (egal): $e'); } } try { await inFlight.timeout(settleTimeout); } catch (e) { debugPrint('[LOGOUT] laufender Sync nicht rechtzeitig fertig: $e'); } await client.logout(); }