Final commit.

This commit is contained in:
Dennis Nemec
2026-06-01 17:12:28 +02:00
parent 3ecbc82885
commit a9bf8ecdd1
385 changed files with 29081 additions and 12089 deletions

View File

@ -55,6 +55,13 @@ class KeycloakOidcTokenProvider implements AuthTokenProvider {
String? _refreshToken;
Map<String, dynamic>? _idTokenClaims;
/// Single-flight-Guard: hält den gerade laufenden Refresh, damit mehrere
/// gleichzeitige Aufrufer (Bootstrap: Restore + PaymentMethodsCubit +
/// Folge-Requests) sich EINEN Refresh teilen statt parallele
/// `flutter_appauth.token()`-Calls auszulösen (die nativ blockieren/haken
/// können → App hängt nach Hot-Restart am Splash/Login).
Future<String?>? _refreshInFlight;
final StreamController<AuthSessionEvent> _events =
StreamController<AuthSessionEvent>.broadcast();
@ -166,6 +173,19 @@ class KeycloakOidcTokenProvider implements AuthTokenProvider {
final rt = _refreshToken;
if (rt == null) return null;
// Single-flight: läuft bereits ein Refresh, hängen wir uns dran, statt
// einen zweiten `flutter_appauth.token()`-Call zu starten. `??=`
// evaluiert die rechte Seite nur, wenn noch kein Refresh läuft.
return _refreshInFlight ??= _performRefresh(rt).whenComplete(() {
_refreshInFlight = null;
});
}
/// Führt EINEN Token-Refresh aus. Bei Erfolg werden die Tokens übernommen
/// und der neue Access-Token zurückgegeben (ohne Event — stiller Refresh).
/// Bei Fehler ist die Session tot: lokal aufräumen, `AuthSessionExpired`
/// emittieren, `null` zurück.
Future<String?> _performRefresh(String rt) async {
try {
final result = await _appAuth.token(
TokenRequest(
@ -187,11 +207,17 @@ class KeycloakOidcTokenProvider implements AuthTokenProvider {
return _accessToken;
} on Exception {
// Refresh hat nicht funktioniert — Session ist tot, nicht
// wiederherstellbar. Aufrufer kriegen null zurück, AuthBloc
// bekommt SessionExpired.
// wiederherstellbar. Reihenfolge bewusst: erst State leeren + Event
// feuern, DANN best-effort den Storage löschen — so kann ein
// werfendes `delete` weder das Event verschlucken noch eine Exception
// aus `currentAccessToken()` leaken.
_clearSession();
await _storage.delete(key: _refreshTokenStorageKey);
_events.add(const AuthSessionExpired());
try {
await _storage.delete(key: _refreshTokenStorageKey);
} catch (e) {
debugPrint('currentAccessToken: Refresh-Token-Delete fehlgeschlagen: $e');
}
return null;
}
}