- Direct m.login.password auth against matrix.m8chat.au - Room list with unread badges, last message, timestamps - Chat timeline (text, images, files, replies, reactions) - Profile screen with expandable Notifications and Security sections - Olm E2EE initialisation (web WASM bootstrap) - Global error handler preventing Matrix SDK crashes - GoRouter with refreshListenable (no recreation on auth change) - Feature-first clean architecture: Riverpod + GoRouter + Drift - Deployed to https://app2.m8chat.au Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
96 lines
3.1 KiB
Dart
96 lines
3.1 KiB
Dart
// Version: 1.0.3 | Created: 2026-04-01
|
|
// 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
|
|
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: 'M8Chat',
|
|
);
|
|
} 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: 'M8Chat',
|
|
newHomeserver: Uri.parse(AppConfig.matrixBaseUrl),
|
|
newOlmAccount: null,
|
|
);
|
|
}
|
|
}
|