fix: gate frontend polling timers and intercept unauthenticated requests to prevent 403 errors on login screen

This commit is contained in:
ssbg04 committed 2026-07-28 22:36:16 +08:00
1 parent 750cfba699
commit 5c9f81f0c9
8 files changed
+42 -42

No files matched your search

@@ -33,6 +33,21 @@ class ApiConstants {
InterceptorsWrapper(
onRequest: (options, handler) {
options.baseUrl = _baseUrl;
// Prevent unauthenticated or malformed token requests from hitting the server
final authHeader = options.headers['Authorization']?.toString() ?? '';
if (authHeader == 'Bearer null' ||
authHeader == 'Bearer ' ||
authHeader == 'Bearer undefined') {
return handler.reject(
DioException(
requestOptions: options,
type: DioExceptionType.cancel,
error: 'Unauthenticated request cancelled: Invalid Authorization header ($authHeader)',
),
);
}
return handler.next(options);
},
),
+3 -34
View File
@@ -77,44 +77,16 @@ class AuthNotifier extends AsyncNotifier<UserModel?> {
}
void _invalidateAllProviders() {
// Documents & folders
// Only reset persistent filter and navigation states.
// autoDispose FutureProviders clean themselves up automatically when screens unmount;
// invalidating them during logout triggers unnecessary unauthenticated API requests.
ref.invalidate(documentQueryProvider);
ref.invalidate(documentPageProvider);
ref.invalidate(documentMutationProvider);
ref.invalidate(printQueueMutationProvider);
ref.invalidate(trashMutationProvider);
ref.invalidate(requirementMutationProvider);
ref.invalidate(folderMutationProvider);
ref.invalidate(openedFolderProvider);
// Archives
ref.invalidate(archiveQueryProvider);
ref.invalidate(archiveDocumentQueryProvider);
ref.invalidate(archiveMutationProvider);
// Students
ref.invalidate(studentQueryProvider);
ref.invalidate(studentMutationProvider);
// Users & notifications
ref.invalidate(usersProvider);
ref.invalidate(notificationsProvider);
// Navigation
ref.invalidate(activeTabProvider);
// Dashboard
ref.invalidate(dashboardDataProvider);
// Setup / Settings
ref.invalidate(academicYearsListProvider);
ref.invalidate(sectionsListProvider);
ref.invalidate(gradeLevelsListProvider);
ref.invalidate(setupMutationProvider);
// Reports
ref.invalidate(selectedAcademicYearIdProvider);
ref.invalidate(academicYearsProvider);
ref.invalidate(selectedGradeLevelProvider);
ref.invalidate(selectedSectionIdProvider);
ref.invalidate(selectedStatusFilterProvider);
@@ -123,9 +95,6 @@ class AuthNotifier extends AsyncNotifier<UserModel?> {
ref.invalidate(missingDocsFilterExpandedProvider);
ref.invalidate(yearlyComparisonSelectedYearsProvider);
ref.invalidate(yearlyComparisonSelectedStatusesProvider);
// OCR
ref.invalidate(ocrProvider);
}
Future<void> refreshUser() async {
@@ -84,9 +84,12 @@ class _ArchivesScreenState extends ConsumerState<ArchivesScreen>
_tabController = TabController(length: 2, vsync: this);
_pollingTimer = Timer.periodic(const Duration(seconds: 15), (_) {
if (!mounted) return;
ref.invalidate(archiveStudentFoldersProvider);
ref.invalidate(archiveDocumentPageProvider);
if (mounted &&
ref.read(authProvider).value != null &&
ref.read(activeTabProvider) == 'Archives') {
ref.invalidate(archiveStudentFoldersProvider);
ref.invalidate(archiveDocumentPageProvider);
}
});
_tabController.addListener(() {
@@ -99,7 +99,9 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
void _startPolling() {
_pollingTimer?.cancel();
_pollingTimer = Timer.periodic(const Duration(seconds: 5), (_) {
if (mounted && ref.read(activeTabProvider) == 'Dashboard') {
if (mounted &&
ref.read(authProvider).value != null &&
ref.read(activeTabProvider) == 'Dashboard') {
ref.invalidate(dashboardDataProvider);
ref.invalidate(dashboardKpisProvider);
ref.read(notificationsProvider.notifier).refreshNotifications();
@@ -13,6 +13,7 @@ import '../../shared/inputs/app_search_bar.dart';
import '../../shared/buttons/primary_button.dart';
import '../../providers/document_provider.dart';
import '../../providers/student_provider.dart';
import '../../providers/auth_provider.dart';
import '../../shared/widgets/app_pagination.dart';
import '../../providers/navigation_provider.dart';
@@ -95,7 +96,9 @@ class _DocumentsScreenState extends ConsumerState<DocumentsScreen>
_searchFocusNode.addListener(_onSearchFocusChanged);
_pollingTimer = Timer.periodic(const Duration(seconds: 15), (_) {
if (mounted) {
if (mounted &&
ref.read(authProvider).value != null &&
ref.read(activeTabProvider) == 'Documents') {
if (_tabController.index == 0) {
ref.invalidate(foldersProvider);
ref.invalidate(studentFoldersProvider);
@@ -13,6 +13,7 @@ import '../../shared/buttons/primary_button.dart';
import '../../shared/cards/stat_card.dart';
import '../../providers/reports_provider.dart';
import '../../providers/navigation_provider.dart';
import '../../providers/auth_provider.dart';
import '../../shared/dialogs/file_save_preview_dialog.dart';
import '../documents/widgets/student_profile_modal.dart';
import 'package:fl_chart/fl_chart.dart';
@@ -59,7 +60,9 @@ class _ReportsScreenState extends ConsumerState<ReportsScreen> {
// Polling every 5 seconds
_pollingTimer = Timer.periodic(const Duration(seconds: 5), (_) {
if (mounted && ref.read(activeTabProvider) == 'Reports') {
if (mounted &&
ref.read(authProvider).value != null &&
ref.read(activeTabProvider) == 'Reports') {
_refreshData();
}
});
@@ -7,6 +7,7 @@ import '../../shared/inputs/custom_text_field.dart';
import '../../shared/buttons/primary_button.dart';
import '../../providers/users_provider.dart';
import '../../providers/setup_provider.dart';
import '../../providers/auth_provider.dart';
import '../../../domain/entities/setup_models.dart';
import '../../../domain/entities/system_user.dart';
import '../../shared/dialogs/error_dialog.dart';
@@ -63,7 +64,9 @@ class _TeacherManagementModalState extends ConsumerState<TeacherManagementModal>
// Polling every 5 seconds
_pollingTimer = Timer.periodic(const Duration(seconds: 5), (_) {
_refreshData();
if (mounted && ref.read(authProvider).value != null) {
_refreshData();
}
});
}