logoutWithoutStraySync (lib/core/session_logout.dart): Sync-Schleife anhalten, laufenden Long-Poll per Account-Daten wecken und auslaufen lassen, erst dann abmelden - es ist keine Anfrage mit altem Token mehr unterwegs, die per 401 ein zweites clear() ausloesen koennte. Genutzt an allen drei Abmelde-Stellen (Einstellungen, Ueber, Konto geloescht) und im E2E-Live-Test. Live gemessen: die verspaetete 401 tritt real auf, kam aber stets vor dem Abschluss einer Neuanmeldung an - Risiko war klein, ist jetzt ausgeschlossen. test/live_logout_race_test.dart in scripts/test.ps1 -Live. Co-Authored-By: Claude Opus 5.5 <[email protected]>
59 lines
2.5 KiB
Dart
59 lines
2.5 KiB
Dart
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<void> 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();
|
||
}
|