Final commit.
This commit is contained in:
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user