diff --git a/apps/lib/app/app.dart b/apps/lib/app/app.dart index a2ab9be..797fb83 100644 --- a/apps/lib/app/app.dart +++ b/apps/lib/app/app.dart @@ -9,6 +9,8 @@ import '../l10n/app_localizations.dart'; import '../features/auth/presentation/bloc/auth_bloc.dart'; import '../features/auth/presentation/bloc/auth_event.dart'; import '../features/auth/presentation/bloc/auth_state.dart'; +import '../features/chat/presentation/bloc/chat_bloc.dart'; +import '../data/cache/cache_scope.dart'; import 'services/app_prewarm_orchestrator.dart'; import 'router/app_router.dart'; import '../core/theme/app_theme.dart'; @@ -23,11 +25,30 @@ class LinksyApp extends StatefulWidget { class _LinksyAppState extends State { late final AuthBloc _authBloc; late final GoRouter _router; + int _cacheScopeVersion = 0; + + Future _onAuthenticated(String userId) async { + _cacheScopeVersion += 1; + final scopeKey = 'user:$userId:v$_cacheScopeVersion'; + CacheScope.configureProvider(() => scopeKey); + await sl().switchUser(userId); + await sl().ensureStartedFor(userId); + } + + Future _onUnauthenticated() async { + _cacheScopeVersion += 1; + final scopeKey = 'anonymous:v$_cacheScopeVersion'; + CacheScope.configureProvider(() => scopeKey); + await sl().switchUser(null); + sl().reset(); + } @override void initState() { super.initState(); _authBloc = sl(); + const initialScopeKey = 'anonymous:v0'; + CacheScope.configureProvider(() => initialScopeKey); _authBloc.add(AuthStarted()); _router = createAppRouter(_authBloc); } @@ -45,12 +66,10 @@ class _LinksyAppState extends State { child: BlocListener( listener: (context, state) { if (state is AuthAuthenticated) { - unawaited( - sl().ensureStartedFor(state.user.id), - ); + unawaited(_onAuthenticated(state.user.id)); } if (state is AuthUnauthenticated) { - sl().reset(); + unawaited(_onUnauthenticated()); } }, child: MaterialApp.router( diff --git a/apps/lib/app/services/app_prewarm_orchestrator.dart b/apps/lib/app/services/app_prewarm_orchestrator.dart index 221f13a..8f047b3 100644 --- a/apps/lib/app/services/app_prewarm_orchestrator.dart +++ b/apps/lib/app/services/app_prewarm_orchestrator.dart @@ -37,6 +37,7 @@ class AppPrewarmOrchestrator extends ChangeNotifier { String? _userId; Future? _running; + int _runToken = 0; bool get isBootBlocking => _status == AppPrewarmStatus.running; @@ -51,6 +52,7 @@ class AppPrewarmOrchestrator extends ChangeNotifier { } _userId = userId; + final runToken = ++_runToken; _status = AppPrewarmStatus.running; notifyListeners(); @@ -60,7 +62,7 @@ class AppPrewarmOrchestrator extends ChangeNotifier { _runPrewarmUnreadInbox(), ]); - final running = _runWithBudget(tasks); + final running = _runWithBudget(tasks, userId: userId, runToken: runToken); _running = running; return running.whenComplete(() { if (identical(_running, running)) { @@ -93,15 +95,30 @@ class AppPrewarmOrchestrator extends ChangeNotifier { return _inboxRepository.getMessages(isRead: false); } - Future _runWithBudget(Future tasks) async { + Future _runWithBudget( + Future tasks, { + required String userId, + required int runToken, + }) async { + bool isLatestRun() => _runToken == runToken && _userId == userId; + try { await tasks.timeout(bootBudget); + if (!isLatestRun()) { + return; + } _status = AppPrewarmStatus.completed; notifyListeners(); } on TimeoutException { + if (!isLatestRun()) { + return; + } _status = AppPrewarmStatus.timedOut; notifyListeners(); } catch (_) { + if (!isLatestRun()) { + return; + } _status = AppPrewarmStatus.failed; notifyListeners(); } @@ -110,6 +127,7 @@ class AppPrewarmOrchestrator extends ChangeNotifier { void reset() { _userId = null; _running = null; + _runToken += 1; if (_status != AppPrewarmStatus.idle) { _status = AppPrewarmStatus.idle; notifyListeners(); diff --git a/apps/test/app/services/app_prewarm_orchestrator_test.dart b/apps/test/app/services/app_prewarm_orchestrator_test.dart index 1901f5f..0047d75 100644 --- a/apps/test/app/services/app_prewarm_orchestrator_test.dart +++ b/apps/test/app/services/app_prewarm_orchestrator_test.dart @@ -73,6 +73,7 @@ class _FakeChatApi implements ChatApi { @override Future> streamRunEvents( String threadId, { + required String runId, String? lastEventId, }) { throw UnimplementedError();