Critical: - Fix MXC URI resolution: all avatars/images now resolve mxc:// to HTTP - Sync persistence: only write changed rooms, batch message upserts - lastActivityAt uses room.lastEvent.originServerTs, not creation time High: - Shared MatrixAvatar widget replaces 6 duplicate implementations - CallScreen decodes roomId before LiveKit JWT fetch - Decline button actually dismisses incoming call overlay - EventTypes constants replace raw string literals - LiveKitService uses lazy auth reads, onDispose disconnects Medium: - CallController is keepAlive with timer/room cleanup - authRepository is keepAlive (used from keepAlive notifier) - StreamController not closed in stopListening (crash fix) - Index on messages.roomId for query performance - 400ms debounce on user search - Static DateFormat in MessageBubble - Hardcoded strings replaced with AppConfig refs - Duplicate isDirectMessage field removed from RoomModel - E2EE profile claim corrected to Phase 3 Shared utilities: - lib/shared/widgets/matrix_avatar.dart - lib/shared/utils/mxc_url.dart - lib/shared/utils/room_preview.dart - lib/shared/utils/matrix_id.dart rawJson column removed (unused, caused main-thread jsonEncode) Schema migrated to v2 with roomId index. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
96 lines
3.2 KiB
Dart
96 lines
3.2 KiB
Dart
// Version: 1.1.0 | Created: 2026-04-01 | Updated: 2026-04-02
|
|
// Auth repository: handles all Matrix login/logout API interactions.
|
|
// Uses the Matrix Dart SDK — no raw HTTP calls for auth.
|
|
|
|
import 'package:matrix/matrix.dart';
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
|
|
import '../../../core/config/app_config.dart';
|
|
import '../../../core/network/matrix_client.dart';
|
|
import '../domain/auth_failure.dart';
|
|
|
|
part 'auth_repository.g.dart';
|
|
|
|
@Riverpod(keepAlive: true)
|
|
AuthRepository authRepository(Ref ref) {
|
|
return AuthRepository(client: ref.watch(matrixClientProvider));
|
|
}
|
|
|
|
/// Handles authentication interactions with the Matrix homeserver.
|
|
class AuthRepository {
|
|
AuthRepository({required Client client}) : _client = client;
|
|
|
|
final Client _client;
|
|
|
|
/// Attempts password login. Returns [LoginResponse] on success,
|
|
/// throws [AuthFailure] on failure.
|
|
///
|
|
/// Matrix error codes mapped:
|
|
/// M_FORBIDDEN → [InvalidCredentials]
|
|
/// M_USER_DEACTIVATED → [AccountDisabled]
|
|
/// Network error → [NetworkError]
|
|
Future<LoginResponse> login({
|
|
required String username,
|
|
required String password,
|
|
}) async {
|
|
try {
|
|
// Set homeserver directly — avoids network round-trips and version checks
|
|
// that checkHomeserver() makes. The setter is public in matrix 0.33.0.
|
|
_client.homeserver = Uri.parse(AppConfig.matrixBaseUrl);
|
|
return await _client.login(
|
|
LoginType.mLoginPassword,
|
|
identifier: AuthenticationUserIdentifier(user: username),
|
|
password: password,
|
|
initialDeviceDisplayName: AppConfig.appName,
|
|
);
|
|
} on MatrixException catch (e) {
|
|
throw switch (e.errcode) {
|
|
'M_FORBIDDEN' => const AuthFailure.invalidCredentials(),
|
|
'M_USER_DEACTIVATED' => const AuthFailure.accountDisabled(),
|
|
_ => AuthFailure.serverError(
|
|
statusCode: e.response?.statusCode,
|
|
message: e.errorMessage,
|
|
),
|
|
};
|
|
} on Exception catch (e) {
|
|
// Covers SocketException, TimeoutException, etc.
|
|
final msg = e.toString().toLowerCase();
|
|
if (msg.contains('socket') ||
|
|
msg.contains('connection') ||
|
|
msg.contains('host lookup') ||
|
|
msg.contains('timeout')) {
|
|
throw AuthFailure.networkError(message: e.toString());
|
|
}
|
|
throw AuthFailure.unknown(message: e.toString());
|
|
}
|
|
}
|
|
|
|
/// Logs out the current session on the homeserver.
|
|
/// Silently succeeds if the token is already invalid (network-first logout).
|
|
Future<void> logout() async {
|
|
try {
|
|
await _client.logout();
|
|
} on MatrixException {
|
|
// Token already invalid — treat as successful logout.
|
|
} on Exception {
|
|
// Network offline — proceed with local cleanup regardless.
|
|
}
|
|
}
|
|
|
|
/// Restores an existing Matrix session using a stored access token.
|
|
Future<void> restoreSession({
|
|
required String accessToken,
|
|
required String userId,
|
|
required String deviceId,
|
|
}) async {
|
|
await _client.init(
|
|
newToken: accessToken,
|
|
newUserID: userId,
|
|
newDeviceID: deviceId,
|
|
newDeviceName: AppConfig.appName,
|
|
newHomeserver: Uri.parse(AppConfig.matrixBaseUrl),
|
|
newOlmAccount: null,
|
|
);
|
|
}
|
|
}
|