9dc83f7bcb
Ursache gefunden: Ohne Refresh-Token eskaliert das matrix-SDK jeden
Soft-Logout (z. B. abgelaufenes Access-Token, M_UNKNOWN_TOKEN mit
soft_logout=true) automatisch zu einem echten, destruktiven Logout —
_handleSoftLogout() versucht refreshAccessToken(), das ohne
gespeicherten Refresh-Token sofort mit "No refresh token available"
scheitert, und ruft danach client.logout() auf (client.dart:2374-2384
im matrix-Paket, Version 6.2.0).
Mit refreshToken: true beim Login bekommt der Client vom Server einen
Refresh-Token und kann einen Soft-Logout künftig durch stillen
Token-Refresh überstehen, statt die Sitzung zu zerstören. Falls der
Server keine Refresh-Tokens unterstützt, bleibt das Verhalten
unverändert (response.refreshToken ist dann null).
Wichtig: gilt nur für neue Logins. Bereits eingeloggte Geräte (z. B.
Uta) profitieren erst nach einmaligem Neu-Login. Zur Diagnose siehe
den mit 63e4919 eingeführten AuthLog.
UNGETESTET (Pi, kein GUI/Login hier möglich) – nächster PC-Lauf muss
den vollen Kreislauf prüfen: Login → Nachrichten → Logout → erneuter
Login → alte verschlüsselte Nachrichten noch lesbar?
116 lines
3.5 KiB
Dart
116 lines
3.5 KiB
Dart
import 'dart:async';
|
|
import 'dart:io';
|
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:matrix/matrix.dart' as mx;
|
|
import 'package:pyramid/core/fcm_push_service.dart';
|
|
import 'package:pyramid/core/matrix_client.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
const _prefHomeserver = 'homeserver_url';
|
|
|
|
final homeserverProvider = StateProvider<String?>((ref) => null);
|
|
|
|
Future<void> initHomeserver(WidgetRef ref) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final saved = prefs.getString(_prefHomeserver);
|
|
ref.read(homeserverProvider.notifier).state = saved;
|
|
}
|
|
|
|
Future<void> saveHomeserver(WidgetRef ref, String url) async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setString(_prefHomeserver, url);
|
|
ref.read(homeserverProvider.notifier).state = url;
|
|
}
|
|
|
|
enum LoginStatus { idle, loading, error }
|
|
|
|
class LoginFormState {
|
|
final LoginStatus status;
|
|
final String? errorMessage;
|
|
|
|
const LoginFormState({this.status = LoginStatus.idle, this.errorMessage});
|
|
|
|
LoginFormState copyWith({LoginStatus? status, String? errorMessage}) =>
|
|
LoginFormState(
|
|
status: status ?? this.status,
|
|
errorMessage: errorMessage,
|
|
);
|
|
}
|
|
|
|
class LoginNotifier extends StateNotifier<LoginFormState> {
|
|
final Ref _ref;
|
|
LoginNotifier(this._ref) : super(const LoginFormState());
|
|
|
|
Future<void> login({
|
|
required String username,
|
|
required String password,
|
|
required String homeserver,
|
|
String? deviceName,
|
|
}) async {
|
|
state = state.copyWith(status: LoginStatus.loading, errorMessage: null);
|
|
|
|
final client = _ref.read(matrixClientProvider).valueOrNull;
|
|
if (client == null) {
|
|
state = state.copyWith(
|
|
status: LoginStatus.error,
|
|
errorMessage: 'Client nicht initialisiert',
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (client.isLogged()) {
|
|
state = state.copyWith(status: LoginStatus.idle);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
final serverUri = Uri.https(
|
|
homeserver.replaceAll(RegExp(r'^https?://'), ''),
|
|
'',
|
|
);
|
|
await client.checkHomeserver(serverUri);
|
|
|
|
await client.login(
|
|
mx.LoginType.mLoginPassword,
|
|
identifier: mx.AuthenticationUserIdentifier(user: username),
|
|
password: password,
|
|
initialDeviceDisplayName: deviceName ?? 'Pyramid',
|
|
// Ohne Refresh-Token eskaliert das SDK jeden Soft-Logout (z. B. nach
|
|
// Ablauf des Access-Tokens) sofort zu einem echten, destruktiven
|
|
// Logout, weil `refreshAccessToken()` sonst mangels Refresh-Token
|
|
// fehlschlägt (siehe matrix-Paket Client._handleSoftLogout). Das ist
|
|
// der wahrscheinlichste Auslöser des Uta-Random-Logout-Bugs.
|
|
refreshToken: true,
|
|
);
|
|
|
|
if (Platform.isAndroid) unawaited(initFcm(client));
|
|
|
|
state = state.copyWith(status: LoginStatus.idle);
|
|
} on mx.MatrixException catch (e) {
|
|
state = state.copyWith(
|
|
status: LoginStatus.error,
|
|
errorMessage: e.errorMessage,
|
|
);
|
|
} catch (e) {
|
|
state = state.copyWith(
|
|
status: LoginStatus.error,
|
|
errorMessage: e.toString(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
final loginNotifierProvider =
|
|
StateNotifierProvider<LoginNotifier, LoginFormState>((ref) {
|
|
return LoginNotifier(ref);
|
|
});
|
|
|
|
final isLoggedInProvider = StreamProvider<bool>((ref) async* {
|
|
final client = await ref.watch(matrixClientProvider.future);
|
|
yield client.isLogged();
|
|
await for (final loginState in client.onLoginStateChanged.stream) {
|
|
yield loginState == mx.LoginState.loggedIn;
|
|
}
|
|
});
|