Phase B: Token-Provider und AuthBloc robust gegen Storage-Plugin-Fehler
Beobachtung: Nach 'flutter_secure_storage' frisch dazugepackt ohne Cold-Restart kam eine MissingPluginException auf dem AuthBloc-Stream durch (read auf channel plugins.it_nomads.com/flutter_secure_storage) und hat den ganzen Bloc-Event-Loop mitgerissen. Fix: - KeycloakOidcTokenProvider.restoreSession / _persistRefreshToken / logout fangen Plugin-Exceptions ab und loggen sie über debugPrint, statt sie hochzureichen. Restore-Pfad endet sauber mit 'kein Restore möglich', Login-Pfad hält den Token in Memory weiter. - AuthBloc._handleRestore mit eigener try/catch als zweite Schutzschicht für jeden anderen Fehler aus dem Provider. Bestehender Cold-Restart-Workaround (App stoppen + flutter run) für die ursprüngliche MissingPluginException bleibt natürlich nötig — diese Änderung sorgt nur dafür, dass künftige Storage-Probleme (Keychain zerschossen, Restore-Backup, …) nicht die Auth komplett killen.
This commit is contained in:
@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_appauth/flutter_appauth.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
|
||||
@ -100,8 +101,21 @@ class KeycloakOidcTokenProvider implements AuthTokenProvider {
|
||||
/// Versucht, eine vorhandene Session aus der Secure Storage zu
|
||||
/// reaktivieren. Liefert `true`, wenn anschließend ein gültiger
|
||||
/// Access-Token verfügbar ist.
|
||||
///
|
||||
/// Schluckt Fehler aus dem Storage-Plugin (z. B. nicht-registriertes
|
||||
/// Native-Modul nach Plugin-Update ohne Cold-Restart) — der Caller
|
||||
/// landet dann sauber im "kein Restore möglich"-Pfad statt mit einer
|
||||
/// MissingPluginException auf dem AuthBloc-Stream.
|
||||
Future<bool> restoreSession() async {
|
||||
final stored = await _storage.read(key: _refreshTokenStorageKey);
|
||||
final String? stored;
|
||||
try {
|
||||
stored = await _storage.read(key: _refreshTokenStorageKey);
|
||||
} catch (e, st) {
|
||||
debugPrint(
|
||||
'restoreSession: konnte Refresh-Token nicht lesen: $e\n$st',
|
||||
);
|
||||
return false;
|
||||
}
|
||||
if (stored == null || stored.isEmpty) return false;
|
||||
_refreshToken = stored;
|
||||
|
||||
@ -119,7 +133,11 @@ class KeycloakOidcTokenProvider implements AuthTokenProvider {
|
||||
/// in der Session zu halten.
|
||||
Future<void> logout() async {
|
||||
_clearSession();
|
||||
await _storage.delete(key: _refreshTokenStorageKey);
|
||||
try {
|
||||
await _storage.delete(key: _refreshTokenStorageKey);
|
||||
} catch (e) {
|
||||
debugPrint('logout: Refresh-Token konnte nicht gelöscht werden: $e');
|
||||
}
|
||||
_events.add(const AuthLoggedOut());
|
||||
}
|
||||
|
||||
@ -187,7 +205,13 @@ class KeycloakOidcTokenProvider implements AuthTokenProvider {
|
||||
Future<void> _persistRefreshToken() async {
|
||||
final rt = _refreshToken;
|
||||
if (rt == null) return;
|
||||
await _storage.write(key: _refreshTokenStorageKey, value: rt);
|
||||
try {
|
||||
await _storage.write(key: _refreshTokenStorageKey, value: rt);
|
||||
} catch (e) {
|
||||
// Nicht fatal — Refresh-Token bleibt in-memory verfügbar,
|
||||
// wir verlieren nur das Restore-Verhalten beim nächsten Start.
|
||||
debugPrint('Refresh-Token konnte nicht persistiert werden: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void _clearSession() {
|
||||
|
||||
@ -74,10 +74,15 @@ class AuthBloc extends Bloc<AuthEvent, AuthState> {
|
||||
RestoreSessionRequested event,
|
||||
Emitter<AuthState> emit,
|
||||
) async {
|
||||
final restored = await tokenProvider.restoreSession();
|
||||
if (!restored) {
|
||||
// Kein gültiger Token → bleibt bei Unauthenticated, kein Snackbar
|
||||
// (das ist der normale Cold-Start-Pfad).
|
||||
try {
|
||||
await tokenProvider.restoreSession();
|
||||
// Erfolg landet via Stream als ProviderSessionChanged.loggedIn.
|
||||
// Misserfolg: bleibt bei Unauthenticated — kein Snackbar, das
|
||||
// ist der normale Cold-Start-Pfad.
|
||||
} catch (err, st) {
|
||||
debugPrint('Restore-Session fehlgeschlagen: $err\n$st');
|
||||
// State unverändert (Unauthenticated). Kein Snackbar — das
|
||||
// wäre für den Cold-Start zu viel Lärm.
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user