From 1a735886e9b4f7a4e87612ab9a1867c96993fd83 Mon Sep 17 00:00:00 2001 From: Devanarayanan Date: Mon, 1 Jun 2026 14:07:25 +0000 Subject: [PATCH] chore: increase network and operation timeout thresholds --- .example.env | 4 ++-- mobile/lib/config/app_config.dart | 2 +- mobile/lib/main.dart | 2 +- mobile/lib/providers/auth_provider.dart | 18 +++++++++++++++--- .../screens/attendance_calendar_screen.dart | 7 ++++++- mobile/lib/screens/dashboard_screen.dart | 7 ++++++- mobile/lib/screens/navigation_shell.dart | 7 ++++++- mobile/lib/screens/tracking_screen.dart | 7 ++++++- mobile/lib/services/api_service.dart | 9 +++++++-- mobile/lib/services/dio_service.dart | 16 ++++++++++++---- mobile/lib/services/jwe_service.dart | 2 +- mobile/pubspec.yaml | 2 +- package-lock.json | 4 ++-- package.json | 2 +- public/openapi/openapi.yaml | 2 +- 15 files changed, 68 insertions(+), 23 deletions(-) diff --git a/.example.env b/.example.env index 8409d2db..a55a066d 100644 --- a/.example.env +++ b/.example.env @@ -44,7 +44,7 @@ NEXT_PUBLIC_APP_NAME=GhostClass # âš ī¸ App version displayed in footer and health checks # 🔨 Build-time (Infisical `/build-time` folder) -NEXT_PUBLIC_APP_VERSION=4.4.7 +NEXT_PUBLIC_APP_VERSION=4.4.8 # âš ī¸ Your production domain WITHOUT https:// # All URL-based variables are derived from this. @@ -365,7 +365,7 @@ JWE_PRIVATE_KEY= # âš ī¸ Minimum supported app version required to bypass forced update # 🚀 Runtime (Infisical `/runtime` folder → Server Env Var) -MIN_APP_VERSION=4.4.7 +MIN_APP_VERSION=4.4.8 # â„šī¸ Enforce Firebase App Check for all mobile clients in production # Valid: "true", "false" (default: false in dev, true recommended in prod) diff --git a/mobile/lib/config/app_config.dart b/mobile/lib/config/app_config.dart index 89b1475a..1f7b5b39 100644 --- a/mobile/lib/config/app_config.dart +++ b/mobile/lib/config/app_config.dart @@ -76,7 +76,7 @@ class AppConfig { /// Current application version (derived from Infisical compilation injection). static String get appVersion => - const String.fromEnvironment('APP_VERSION', defaultValue: '4.4.7'); + const String.fromEnvironment('APP_VERSION', defaultValue: '4.4.8'); /// Commit SHA injected by CI for release builds. static String get appCommitSha => diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index be85f76b..03aee25a 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -29,7 +29,7 @@ class MyHttpOverrides extends HttpOverrides { HttpClient createHttpClient(SecurityContext? context) { final client = super.createHttpClient(context) ..connectionTimeout = kDebugMode - ? const Duration(seconds: 40) + ? const Duration(seconds: 45) : const Duration(seconds: 30); // In debug mode, we allow untrusted certificates ONLY if they match our expected hostname. diff --git a/mobile/lib/providers/auth_provider.dart b/mobile/lib/providers/auth_provider.dart index 552c4032..f7ebf9d0 100644 --- a/mobile/lib/providers/auth_provider.dart +++ b/mobile/lib/providers/auth_provider.dart @@ -279,7 +279,11 @@ class AuthNotifier extends AsyncNotifier ); syncRes = await api .syncMobileAuth(supabaseToken) - .timeout(const Duration(seconds: 30)); + .timeout( + kDebugMode + ? const Duration(seconds: 45) + : const Duration(seconds: 30), + ); } on TimeoutException catch (e, st) { AppLogger.e( 'AuthNotifier [HEAL-$healAttemptId]: syncMobileAuth timed out (attempt 1)', @@ -298,7 +302,11 @@ class AuthNotifier extends AsyncNotifier ); syncRes = await api .syncMobileAuth(supabaseToken) - .timeout(const Duration(seconds: 30)); + .timeout( + kDebugMode + ? const Duration(seconds: 45) + : const Duration(seconds: 30), + ); } on Object catch (e, st) { AppLogger.e( 'AuthNotifier [HEAL-$healAttemptId]: syncMobileAuth retry failed', @@ -1541,7 +1549,11 @@ class AuthNotifier extends AsyncNotifier .read(supabaseClientProvider) .auth .refreshSession() - .timeout(const Duration(seconds: 30)); + .timeout( + kDebugMode + ? const Duration(seconds: 45) + : const Duration(seconds: 30), + ); return res.session?.accessToken; } return session.accessToken; diff --git a/mobile/lib/screens/attendance_calendar_screen.dart b/mobile/lib/screens/attendance_calendar_screen.dart index 95467160..cd4d18aa 100644 --- a/mobile/lib/screens/attendance_calendar_screen.dart +++ b/mobile/lib/screens/attendance_calendar_screen.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:ghostclass/logic/attendance_utils.dart' as utils; @@ -126,7 +127,11 @@ class _AttendanceCalendarScreenState ref.read(dashboardProvider.future), ref.read(trackingProvider.future), ref.read(academicProvider.future), - ]).timeout(const Duration(seconds: 30)); + ]).timeout( + kDebugMode + ? const Duration(seconds: 45) + : const Duration(seconds: 30), + ); } on Object catch (e, st) { AppLogger.e('AttendanceCalendarScreen: Retry failed', e, st); } diff --git a/mobile/lib/screens/dashboard_screen.dart b/mobile/lib/screens/dashboard_screen.dart index 2857ef4b..aff1256a 100644 --- a/mobile/lib/screens/dashboard_screen.dart +++ b/mobile/lib/screens/dashboard_screen.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:ghostclass/providers/auth_provider.dart'; @@ -81,7 +82,11 @@ class _DashboardScreenState extends ConsumerState { try { await ref .read(dashboardProvider.future) - .timeout(const Duration(seconds: 30)); + .timeout( + kDebugMode + ? const Duration(seconds: 45) + : const Duration(seconds: 30), + ); } on Object catch (e, st) { AppLogger.e('DashboardScreen: Retry failed', e, st); } diff --git a/mobile/lib/screens/navigation_shell.dart b/mobile/lib/screens/navigation_shell.dart index 995f1dbb..7ed95c40 100644 --- a/mobile/lib/screens/navigation_shell.dart +++ b/mobile/lib/screens/navigation_shell.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:ui'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_animate/flutter_animate.dart'; @@ -731,7 +732,11 @@ class _NavigationShellState extends ConsumerState { await Future.wait([ ref.read(dashboardProvider.future), ref.read(trackingProvider.future), - ]).timeout(const Duration(seconds: 30)); + ]).timeout( + kDebugMode + ? const Duration(seconds: 45) + : const Duration(seconds: 30), + ); AppLogger.i( 'NavigationShell: Outage recovery wait completed (or partial success).', ); diff --git a/mobile/lib/screens/tracking_screen.dart b/mobile/lib/screens/tracking_screen.dart index ceb636b7..d6dfca4f 100644 --- a/mobile/lib/screens/tracking_screen.dart +++ b/mobile/lib/screens/tracking_screen.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -65,7 +66,11 @@ class _TrackingScreenState extends ConsumerState try { await ref .read(trackingProvider.future) - .timeout(const Duration(seconds: 30)); + .timeout( + kDebugMode + ? const Duration(seconds: 45) + : const Duration(seconds: 30), + ); } on Object catch (e, st) { AppLogger.e('TrackingScreen: Retry failed', e, st); } diff --git a/mobile/lib/services/api_service.dart b/mobile/lib/services/api_service.dart index 52ce3ea5..057ce313 100644 --- a/mobile/lib/services/api_service.dart +++ b/mobile/lib/services/api_service.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:dio/dio.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:ghostclass/config/app_config.dart'; import 'package:ghostclass/logic/app_exception.dart'; @@ -192,8 +193,12 @@ class ApiService { '${AppConfig.ghostclassApiUrl}/cron/sync?t=${now.millisecondsSinceEpoch}', options: Options( headers: {'Authorization': 'Bearer $supabaseToken'}, - sendTimeout: const Duration(seconds: 30), - receiveTimeout: const Duration(seconds: 30), + sendTimeout: kDebugMode + ? const Duration(seconds: 45) + : const Duration(seconds: 30), + receiveTimeout: kDebugMode + ? const Duration(seconds: 45) + : const Duration(seconds: 30), ), ); _lastSyncAt = DateTime.now(); diff --git a/mobile/lib/services/dio_service.dart b/mobile/lib/services/dio_service.dart index 717c12a4..c1bcb672 100644 --- a/mobile/lib/services/dio_service.dart +++ b/mobile/lib/services/dio_service.dart @@ -16,7 +16,7 @@ import 'package:sentry_dio/sentry_dio.dart'; /// Configures interceptors for JWE, Sentry, and authentication headers. class DioService { DioService(this._ref) { - const timeout = kDebugMode ? Duration(seconds: 40) : Duration(seconds: 30); + const timeout = kDebugMode ? Duration(seconds: 45) : Duration(seconds: 30); dio = Dio( BaseOptions( @@ -162,7 +162,11 @@ class DioService { ? _appCheck.getLimitedUseToken() : _appCheck.getToken(); - return await tokenFuture.timeout(const Duration(seconds: 10)); + return await tokenFuture.timeout( + kDebugMode + ? const Duration(seconds: 45) + : const Duration(seconds: 30), + ); } on Object catch (e, st) { lastError = e; final isTransient = _isTransientAppCheckFailure(e); @@ -202,7 +206,9 @@ class DioService { isNew = true; } appCheckToken = await _limitedTokenFetchInFlight!.timeout( - const Duration(seconds: 10), + kDebugMode + ? const Duration(seconds: 45) + : const Duration(seconds: 30), ); if (isNew) { AppLogger.safeUnawait( @@ -229,7 +235,9 @@ class DioService { isNew = true; } appCheckToken = await _tokenFetchInFlight!.timeout( - const Duration(seconds: 10), + kDebugMode + ? const Duration(seconds: 45) + : const Duration(seconds: 30), ); if (isNew) { AppLogger.safeUnawait( diff --git a/mobile/lib/services/jwe_service.dart b/mobile/lib/services/jwe_service.dart index 5b9bf069..752d6e05 100644 --- a/mobile/lib/services/jwe_service.dart +++ b/mobile/lib/services/jwe_service.dart @@ -21,7 +21,7 @@ import 'package:shared_preferences/shared_preferences.dart'; class JweService { JweService._internal() { const networkTimeout = kDebugMode - ? Duration(seconds: 40) + ? Duration(seconds: 45) : Duration(seconds: 30); _dio = Dio( diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 79f7469d..e4aa3fa1 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 4.4.7+1 +version: 4.4.8+1 environment: sdk: ^3.11.4 diff --git a/package-lock.json b/package-lock.json index 68494108..1d4ef492 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ghostclass", - "version": "4.4.7", + "version": "4.4.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "ghostclass", - "version": "4.4.7", + "version": "4.4.8", "dependencies": { "@hookform/resolvers": "^5.2.2", "@radix-ui/react-alert-dialog": "^1.1.15", diff --git a/package.json b/package.json index 6d61355f..86002c51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ghostclass", - "version": "4.4.7", + "version": "4.4.8", "private": true, "engines": { "node": ">=22.12.0", diff --git a/public/openapi/openapi.yaml b/public/openapi/openapi.yaml index 43504d4a..4645d3f3 100644 --- a/public/openapi/openapi.yaml +++ b/public/openapi/openapi.yaml @@ -6,7 +6,7 @@ openapi: 3.1.0 info: title: GhostClass API - version: 4.4.7 + version: 4.4.8 description: | **GhostClass API** provides endpoints for authentication, profile synchronization, attendance integrations with EzyGo, telemetry, and build provenance.