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 <noreply@anthropic.com>
This commit is contained in:
parent
add405aa65
commit
fe332fdb3f
|
|
@ -756,7 +756,8 @@
|
||||||
"Bash(set DATABASE_URL=postgresql://user:pass@localhost:5432/db)",
|
"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(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(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 <noreply@anthropic.com>\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 <noreply@anthropic.com>\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 <noreply@anthropic.com>\nEOF\n\\)\")"
|
||||||
],
|
],
|
||||||
"deny": [],
|
"deny": [],
|
||||||
"ask": []
|
"ask": []
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,8 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import '../constants/app_constants.dart';
|
import '../constants/app_constants.dart';
|
||||||
import '../error/exceptions.dart';
|
import '../error/exceptions.dart';
|
||||||
|
|
||||||
/// 全局未授权事件控制器,用于通知 401 错误
|
|
||||||
class AuthEventBus {
|
|
||||||
static final AuthEventBus _instance = AuthEventBus._internal();
|
|
||||||
factory AuthEventBus() => _instance;
|
|
||||||
AuthEventBus._internal();
|
|
||||||
|
|
||||||
final _unauthorizedController = StreamController<void>.broadcast();
|
|
||||||
|
|
||||||
/// 监听未授权事件
|
|
||||||
Stream<void> get onUnauthorized => _unauthorizedController.stream;
|
|
||||||
|
|
||||||
/// 触发未授权事件
|
|
||||||
void emitUnauthorized() {
|
|
||||||
_unauthorizedController.add(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
void dispose() {
|
|
||||||
_unauthorizedController.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ApiClient {
|
class ApiClient {
|
||||||
final Dio dio;
|
final Dio dio;
|
||||||
|
|
||||||
|
|
@ -129,8 +107,6 @@ class ApiClient {
|
||||||
case DioExceptionType.badResponse:
|
case DioExceptionType.badResponse:
|
||||||
final statusCode = e.response?.statusCode;
|
final statusCode = e.response?.statusCode;
|
||||||
if (statusCode == 401) {
|
if (statusCode == 401) {
|
||||||
// 触发全局未授权事件,通知应用进行登出处理
|
|
||||||
AuthEventBus().emitUnauthorized();
|
|
||||||
return UnauthorizedException();
|
return UnauthorizedException();
|
||||||
}
|
}
|
||||||
final message = e.response?.data?['error']?['message']?[0] ?? '服务器错误';
|
final message = e.response?.data?['error']?['message']?[0] ?? '服务器错误';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.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/pages/profile/profile_page.dart';
|
||||||
import '../../presentation/widgets/main_shell.dart';
|
import '../../presentation/widgets/main_shell.dart';
|
||||||
import '../../presentation/providers/user_providers.dart';
|
import '../../presentation/providers/user_providers.dart';
|
||||||
import '../network/api_client.dart';
|
|
||||||
import 'routes.dart';
|
import 'routes.dart';
|
||||||
|
|
||||||
/// 不需要登录就能访问的公开路由
|
/// 不需要登录就能访问的公开路由
|
||||||
|
|
@ -28,15 +26,6 @@ const _publicRoutes = {
|
||||||
/// 路由刷新通知器,用于在登录状态变化时刷新路由
|
/// 路由刷新通知器,用于在登录状态变化时刷新路由
|
||||||
class AuthNotifier extends ChangeNotifier {
|
class AuthNotifier extends ChangeNotifier {
|
||||||
bool _isLoggedIn = false;
|
bool _isLoggedIn = false;
|
||||||
StreamSubscription<void>? _unauthorizedSubscription;
|
|
||||||
|
|
||||||
AuthNotifier() {
|
|
||||||
// 监听 401 未授权事件
|
|
||||||
_unauthorizedSubscription = AuthEventBus().onUnauthorized.listen((_) {
|
|
||||||
_isLoggedIn = false;
|
|
||||||
notifyListeners();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get isLoggedIn => _isLoggedIn;
|
bool get isLoggedIn => _isLoggedIn;
|
||||||
|
|
||||||
|
|
@ -46,12 +35,6 @@ class AuthNotifier extends ChangeNotifier {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_unauthorizedSubscription?.cancel();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final authNotifierProvider = ChangeNotifierProvider<AuthNotifier>((ref) {
|
final authNotifierProvider = ChangeNotifierProvider<AuthNotifier>((ref) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue