diff --git a/CHANGES.md b/CHANGES.md index fc8c1f1..927b236 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1123,3 +1123,13 @@ 2026-07-03 09:44 [Edit] /home/steggi/pyramid/lib/widgets/settings_modal.dart 2026-07-03 09:44 [Edit] /home/steggi/pyramid/lib/widgets/settings_modal.dart 2026-07-03 09:45 [Write] /home/steggi/pyramid/README.md +2026-07-03 09:46 [Edit] /home/steggi/pyramid/ROADMAP.md +2026-07-03 09:46 [Edit] /home/steggi/pyramid/PROGRESS.md +2026-07-03 09:49 [Write] /home/steggi/pyramid/lib/core/auth_log.dart +2026-07-03 09:49 [Edit] /home/steggi/pyramid/lib/core/matrix_client.dart +2026-07-03 09:50 [Edit] /home/steggi/pyramid/lib/core/matrix_client.dart +2026-07-03 09:50 [Edit] /home/steggi/pyramid/lib/core/matrix_client.dart +2026-07-03 09:50 [Edit] /home/steggi/pyramid/lib/widgets/settings_modal.dart +2026-07-03 09:50 [Edit] /home/steggi/pyramid/lib/widgets/settings_modal.dart +2026-07-03 09:50 [Edit] /home/steggi/pyramid/lib/widgets/settings_modal.dart +2026-07-03 09:50 [Edit] /home/steggi/pyramid/lib/widgets/settings_modal.dart diff --git a/lib/core/auth_log.dart b/lib/core/auth_log.dart new file mode 100644 index 0000000..1c4bcb9 --- /dev/null +++ b/lib/core/auth_log.dart @@ -0,0 +1,51 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:path/path.dart' as p; +import 'package:path_provider/path_provider.dart'; + +/// Schreibt Login-/Logout-relevante Ereignisse in eine Datei, die einen +/// App-Neustart übersteht. Zweck: den Uta-Random-Logout-Bug nachvollziehen +/// können, auch wenn die App zwischen dem Logout und dem nächsten Blick ins +/// Log neu gestartet wurde (siehe ROADMAP M1). +class AuthLog { + static const _maxLines = 500; + static File? _file; + + static Future _ensureFile() async { + final existing = _file; + if (existing != null) return existing; + final dir = await getApplicationSupportDirectory(); + final file = File(p.join(dir.path, 'auth_log.txt')); + return _file = file; + } + + static Future write(String message) async { + try { + final file = await _ensureFile(); + await file.writeAsString( + '${DateTime.now().toIso8601String()} $message\n', + mode: FileMode.append, + flush: true, + ); + final lines = await file.readAsLines(); + if (lines.length > _maxLines) { + await file.writeAsString( + '${lines.sublist(lines.length - _maxLines).join('\n')}\n', + ); + } + } catch (_) { + // Logging darf selbst nie einen Fehlerpfad (oder gar einen Logout) auslösen. + } + } + + static Future read() async { + try { + final file = await _ensureFile(); + if (!await file.exists()) return '(noch keine Einträge)'; + return await file.readAsString(); + } catch (e) { + return 'Log konnte nicht gelesen werden: $e'; + } + } +} diff --git a/lib/core/matrix_client.dart b/lib/core/matrix_client.dart index 3622917..4f0f8bc 100644 --- a/lib/core/matrix_client.dart +++ b/lib/core/matrix_client.dart @@ -1,3 +1,4 @@ +import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; @@ -10,6 +11,7 @@ import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; import 'package:sqflite/sqflite.dart' as sqflite_native; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; +import 'package:pyramid/core/auth_log.dart'; import 'package:pyramid/core/voip_manager.dart'; final matrixClientProvider = FutureProvider((ref) async { @@ -63,11 +65,23 @@ final matrixClientProvider = FutureProvider((ref) async { ), ); + // Persistiert Warnungen/Fehler des SDK sowie jeden Login-Status-Wechsel, + // damit ein unerwarteter Logout (z. B. Soft-Logout vom Server) auch nach + // einem App-Neustart noch nachvollziehbar ist (ROADMAP M1, Uta-Logout-Bug). + Logs().onLog = (event) { + if (event.level.index <= Level.warning.index) { + unawaited(AuthLog.write('[SDK ${event.level.name}] ${event.toFormattedString()}')); + } + }; + client.onLoginStateChanged.stream.listen((state) { + unawaited(AuthLog.write('LoginState -> ${state.name}')); + }); + await client.init( waitForFirstSync: false, waitUntilLoadCompletedLoaded: false, ); - + // Re-use or create singleton instance try { PyramidVoipManager.instance.client; diff --git a/lib/widgets/settings_modal.dart b/lib/widgets/settings_modal.dart index fff79bd..bf0e8e4 100644 --- a/lib/widgets/settings_modal.dart +++ b/lib/widgets/settings_modal.dart @@ -21,6 +21,7 @@ import 'package:pretty_qr_code/pretty_qr_code.dart'; import 'package:path_provider/path_provider.dart'; import 'package:pointycastle/export.dart' as pc; import 'package:pyramid/core/app_state.dart'; +import 'package:pyramid/core/auth_log.dart'; import 'package:pyramid/core/e2ee_diagnostics.dart'; import 'package:pyramid/core/livekit_call_manager.dart'; import 'package:pyramid/core/matrix_client.dart'; @@ -596,6 +597,7 @@ class _LogoutNavButtonState extends ConsumerState<_LogoutNavButton> { try { widget.onClose(); final client = await ref.read(matrixClientProvider.future); + unawaited(AuthLog.write('User-initiated logout (Settings-Sidebar)')); await client.logout(); } catch (_) {} } @@ -4839,6 +4841,7 @@ class _DeleteAccountDialogState extends ConsumerState<_DeleteAccountDialog> { return; } // Konto wurde gelöscht — ausloggen + unawaited(AuthLog.write('User-initiated logout (Konto gelöscht)')); try { await client.logout(); } catch (_) {} } @@ -5137,6 +5140,7 @@ class _AboutSection extends ConsumerWidget { ); if (confirm == true) { final client = await ref.read(matrixClientProvider.future); + unawaited(AuthLog.write('User-initiated logout (Settings)')); await client.logout().catchError((_) {}); } }