Files
pyramid/lib/core/matrix_client.dart
T
Bernd Steckmeister 63e491952d feat: persistentes Auth-Log für Logout-Diagnose (M1)
Neuer AuthLog schreibt Login-Status-Wechsel und SDK-Warnungen/-Fehler
in eine Datei im App-Support-Verzeichnis, die einen Neustart übersteht.
Zusätzlich werden alle drei bewusst ausgelösten Logout-Buttons geloggt,
damit sich künftig unterscheiden lässt: Nutzer-Logout vs. vom SDK
automatisch ausgelöster Logout (Soft-Logout/M_UNKNOWN_TOKEN).

Reiner Logging-Zusatz, keine Verhaltensänderung – erster Schritt laut
ROADMAP M1 ("Erst Logging an allen Logout-Pfaden einbauen").
2026-07-03 09:50:55 +02:00

98 lines
3.2 KiB
Dart

import 'dart:async';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_vodozemac/flutter_vodozemac.dart' as vod;
import 'package:matrix/encryption/utils/key_verification.dart';
import 'package:matrix/matrix.dart';
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<Client>((ref) async {
final DatabaseFactory factory;
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
// Use the native sqflite plugin — Android/iOS have system SQLite built-in,
// no .so loading required.
factory = sqflite_native.databaseFactory;
} else if (!kIsWeb) {
sqfliteFfiInit();
factory = databaseFactoryFfi;
} else {
throw UnsupportedError('Web not supported');
}
final appDir = await getApplicationSupportDirectory();
final dbPath = p.join(appDir.path, 'pyramid.sqlite');
final db = await factory.openDatabase(dbPath);
// Optimization for Windows/FFI to prevent "database is locked" errors
try {
await db.execute('PRAGMA journal_mode=WAL');
await db.execute('PRAGMA busy_timeout = 5000');
} catch (e) {
if (kDebugMode) debugPrint('Could not set PRAGMAs: $e');
}
final sdkDb = await MatrixSdkDatabase.init('pyramid', database: db);
final client = Client(
'Pyramid',
database: sdkDb,
// Custom State-Events, die beim App-Start mit aus der DB geladen werden
// müssen — sonst sind Space-Banner & Voice-Presence nach Neustart leer,
// bis der jeweilige Raum vollständig nachgeladen wird.
importantStateEvents: {
'io.pyramid.space.banner',
'io.pyramid.voice.presence',
},
verificationMethods: {
KeyVerificationMethod.numbers,
KeyVerificationMethod.emoji,
KeyVerificationMethod.qrShow,
},
logLevel: kReleaseMode ? Level.warning : Level.verbose,
nativeImplementations: NativeImplementationsIsolate(
compute,
vodozemacInit: () => vod.init(),
),
);
// 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;
} catch (_) {
PyramidVoipManager(client);
}
return client;
});
final secureStorageProvider = Provider<FlutterSecureStorage>((ref) {
return const FlutterSecureStorage();
});