From fe332fdb3f92ada2249c2bfddc0563692c31335b Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 12 Jan 2026 09:44:53 -0800 Subject: [PATCH] fix(mining-app): remove AuthEventBus to fix Riverpod state race condition The AuthEventBus was causing "Cannot use ref functions after the dependency of a provider changed" error when 401 responses triggered logout during provider rebuilds. Now 401 handling is done through normal exception flow in splash page and route guards respond to isLoggedInProvider state changes. Co-Authored-By: Claude Opus 4.5 --- .claude/settings.local.json | 3 ++- .../lib/core/network/api_client.dart | 24 ------------------- .../lib/core/router/app_router.dart | 17 ------------- 3 files changed, 2 insertions(+), 42 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index e6ffa67d..14573e6f 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -756,7 +756,8 @@ "Bash(set DATABASE_URL=postgresql://user:pass@localhost:5432/db)", "Bash(cmd /c \"set DATABASE_URL=postgresql://user:pass@localhost:5432/db && npx prisma migrate dev --name add_nickname_to_synced_legacy_users --create-only\")", "Bash(dir \"c:\\\\Users\\\\dong\\\\Desktop\\\\rwadurian\\\\frontend\")", - "Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(mining-app\\): fix login bugs and connect contribution page to real API\n\nLogin fixes:\n- Add AuthEventBus for global 401 error handling with auto-logout\n- Add route guards with GoRouter redirect to protect authenticated routes\n- Remove setMockUser\\(\\) security vulnerability and legacy login\\(\\) dead code\n- Remove unused AuthInterceptor class\n\nContribution page:\n- Add ContributionRecord entity and model for records API\n- Connect contribution details card to GET /accounts/{id}/records endpoint\n- Display real team stats \\(direct referrals, unlocked levels/tiers\\)\n- Calculate expiration countdown from actual record data\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")" + "Bash(git commit -m \"$\\(cat <<''EOF''\nfeat\\(mining-app\\): fix login bugs and connect contribution page to real API\n\nLogin fixes:\n- Add AuthEventBus for global 401 error handling with auto-logout\n- Add route guards with GoRouter redirect to protect authenticated routes\n- Remove setMockUser\\(\\) security vulnerability and legacy login\\(\\) dead code\n- Remove unused AuthInterceptor class\n\nContribution page:\n- Add ContributionRecord entity and model for records API\n- Connect contribution details card to GET /accounts/{id}/records endpoint\n- Display real team stats \\(direct referrals, unlocked levels/tiers\\)\n- Calculate expiration countdown from actual record data\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")", + "Bash(dependency of a provider changed\" error when 401 responses triggered\nlogout during provider rebuilds.\n\nNow 401 handling is done through normal exception flow in splash page\nand route guards respond to isLoggedInProvider state changes.\n\nCo-Authored-By: Claude Opus 4.5 \nEOF\n\\)\")" ], "deny": [], "ask": [] diff --git a/frontend/mining-app/lib/core/network/api_client.dart b/frontend/mining-app/lib/core/network/api_client.dart index d7e8921d..8d3bcee0 100644 --- a/frontend/mining-app/lib/core/network/api_client.dart +++ b/frontend/mining-app/lib/core/network/api_client.dart @@ -1,30 +1,8 @@ -import 'dart:async'; import 'package:dio/dio.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../constants/app_constants.dart'; import '../error/exceptions.dart'; -/// 全局未授权事件控制器,用于通知 401 错误 -class AuthEventBus { - static final AuthEventBus _instance = AuthEventBus._internal(); - factory AuthEventBus() => _instance; - AuthEventBus._internal(); - - final _unauthorizedController = StreamController.broadcast(); - - /// 监听未授权事件 - Stream get onUnauthorized => _unauthorizedController.stream; - - /// 触发未授权事件 - void emitUnauthorized() { - _unauthorizedController.add(null); - } - - void dispose() { - _unauthorizedController.close(); - } -} - class ApiClient { final Dio dio; @@ -129,8 +107,6 @@ class ApiClient { case DioExceptionType.badResponse: final statusCode = e.response?.statusCode; if (statusCode == 401) { - // 触发全局未授权事件,通知应用进行登出处理 - AuthEventBus().emitUnauthorized(); return UnauthorizedException(); } final message = e.response?.data?['error']?['message']?[0] ?? '服务器错误'; diff --git a/frontend/mining-app/lib/core/router/app_router.dart b/frontend/mining-app/lib/core/router/app_router.dart index cc7774ac..1f6eaa11 100644 --- a/frontend/mining-app/lib/core/router/app_router.dart +++ b/frontend/mining-app/lib/core/router/app_router.dart @@ -1,4 +1,3 @@ -import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; @@ -14,7 +13,6 @@ import '../../presentation/pages/asset/asset_page.dart'; import '../../presentation/pages/profile/profile_page.dart'; import '../../presentation/widgets/main_shell.dart'; import '../../presentation/providers/user_providers.dart'; -import '../network/api_client.dart'; import 'routes.dart'; /// 不需要登录就能访问的公开路由 @@ -28,15 +26,6 @@ const _publicRoutes = { /// 路由刷新通知器,用于在登录状态变化时刷新路由 class AuthNotifier extends ChangeNotifier { bool _isLoggedIn = false; - StreamSubscription? _unauthorizedSubscription; - - AuthNotifier() { - // 监听 401 未授权事件 - _unauthorizedSubscription = AuthEventBus().onUnauthorized.listen((_) { - _isLoggedIn = false; - notifyListeners(); - }); - } bool get isLoggedIn => _isLoggedIn; @@ -46,12 +35,6 @@ class AuthNotifier extends ChangeNotifier { notifyListeners(); } } - - @override - void dispose() { - _unauthorizedSubscription?.cancel(); - super.dispose(); - } } final authNotifierProvider = ChangeNotifierProvider((ref) {