forked from eatvio/eatvio_app
feat(session): centralize session teardown and refresh
Add SessionLifecycleService to reset user-scoped cubits, analytics, socket id and account-scoped preferences on logout, 401 or account deletion. Run the same refresh after every successful auth path, including registration v1/v2, and ignore 401 responses from stale tokens so an old request cannot terminate the new user's session. Refs: #1113
This commit is contained in:
122
lib/core/session/session_lifecycle_service.dart
Normal file
122
lib/core/session/session_lifecycle_service.dart
Normal file
@@ -0,0 +1,122 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appmetrica_plugin/appmetrica_plugin.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:eatvio_app/core/app_config.dart';
|
||||
import 'package:eatvio_app/core/app_router.dart';
|
||||
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||||
import 'package:eatvio_app/core/firebase_api.dart';
|
||||
import 'package:eatvio_app/core/locator_service.dart';
|
||||
import 'package:eatvio_app/core/prefs.dart';
|
||||
import 'package:eatvio_app/core/prefs_keys.dart';
|
||||
import 'package:eatvio_app/core/routing/route_constants.dart';
|
||||
import 'package:eatvio_app/core/token_storage.dart';
|
||||
import 'package:eatvio_app/core/websockets/ws_manager.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/new_messages_cubit/new_messages_cubit.dart';
|
||||
import 'package:eatvio_app/features/bottom_navigation_bar/presentation/bloc/profile_avatar_cubit/profile_avatar_cubit.dart';
|
||||
import 'package:eatvio_app/features/chat/presentation/bloc/chats_cubit/chats_cubit.dart';
|
||||
import 'package:eatvio_app/features/feed/presentation/bloc/feed_cubit/feed_cubit.dart';
|
||||
import 'package:eatvio_app/features/notifications/presentation/notifications_cubit/notifications_cubit.dart';
|
||||
import 'package:eatvio_app/features/profile/presentation/bloc/profile_cubit/profile_cubit.dart';
|
||||
import 'package:eatvio_app/features/video_calls/presentation/bloc/video_call_session/video_call_session_cubit.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
enum SessionEndReason { logout, unauthorized, accountDeleted }
|
||||
|
||||
class SessionLifecycleService {
|
||||
SessionLifecycleService({
|
||||
required WsManager wsManager,
|
||||
required ErrorReporter errorReporter,
|
||||
}) : _wsManager = wsManager,
|
||||
_errorReporter = errorReporter;
|
||||
|
||||
final WsManager _wsManager;
|
||||
final ErrorReporter _errorReporter;
|
||||
|
||||
int? _authenticatedFingerprint;
|
||||
bool _endingSession = false;
|
||||
|
||||
Future<void> endSession({required SessionEndReason reason}) async {
|
||||
if (_endingSession) return;
|
||||
_endingSession = true;
|
||||
try {
|
||||
await _run(() => _wsManager.clear());
|
||||
await _run(TokenStorage.removeTokenInStorage);
|
||||
await _run(SocketIdStorage.removeSocketIdInStorage);
|
||||
await _run(() => sl<ProfileAvatarCubit>().reset());
|
||||
await _run(() => sl<ChatsCubit>().reset());
|
||||
await _run(() => sl<NotificationsCubit>().reset());
|
||||
await _run(() => sl<NewMessagesCubit>().reset());
|
||||
await _run(() => sl<FeedCubit>().reset());
|
||||
await _run(() async => ProfileCubit.clearMyProfileReference());
|
||||
await _run(() => sl<VideoCallSessionCubit>().endCall());
|
||||
await _run(() async => AppMetrica.setUserProfileID(null));
|
||||
await _run(
|
||||
() => sl<Prefs>().remove(PrefsKeys.isEmailVerificationSkipped),
|
||||
);
|
||||
await _run(() => sl<Prefs>().remove(PrefsKeys.registrationDate));
|
||||
_authenticatedFingerprint = null;
|
||||
await _run(() async {
|
||||
final context = AppRouter.navigatorKey.currentContext;
|
||||
if (context == null) return;
|
||||
final path =
|
||||
AppRouter.appRouter.routeInformationProvider.value.uri.path;
|
||||
if (path != RouteConstants.authScreen) {
|
||||
context.go(RouteConstants.authScreen);
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
_endingSession = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> onAuthenticated() async {
|
||||
String? token;
|
||||
try {
|
||||
token = await TokenStorage.getTokenFromStorage();
|
||||
} catch (e, st) {
|
||||
await _report(e, st);
|
||||
return;
|
||||
}
|
||||
if (token == null || token.isEmpty) return;
|
||||
final fingerprint = token.hashCode;
|
||||
if (_authenticatedFingerprint == fingerprint) return;
|
||||
_authenticatedFingerprint = fingerprint;
|
||||
|
||||
await _run(() async {
|
||||
await _wsManager.clear();
|
||||
await _wsManager.initialize();
|
||||
});
|
||||
await _run(() => sl<ProfileAvatarCubit>().getMyProfile());
|
||||
await _run(() => sl<ChatsCubit>().getChatRoomsWithLoading());
|
||||
await _run(() => sl<NotificationsCubit>().getNotifications());
|
||||
await _run(() => sl<FirebaseApi>().registerPushToken());
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
try {
|
||||
await _run(() => sl<FirebaseApi>().unregisterPushTokens());
|
||||
await _run(
|
||||
() => sl<Dio>().post('${sl<AppConfig>().baseUrl}/api/profile/logout'),
|
||||
);
|
||||
} finally {
|
||||
await endSession(reason: SessionEndReason.logout);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _run(FutureOr<void> Function() action) async {
|
||||
try {
|
||||
await action();
|
||||
} catch (e, st) {
|
||||
await _report(e, st);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _report(Object error, StackTrace stackTrace) {
|
||||
return _errorReporter.report(
|
||||
error,
|
||||
stackTrace,
|
||||
origin: 'SessionLifecycleService',
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user