Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .example.env
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion mobile/lib/config/app_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 =>
Expand Down
2 changes: 1 addition & 1 deletion mobile/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
18 changes: 15 additions & 3 deletions mobile/lib/providers/auth_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,11 @@ class AuthNotifier extends AsyncNotifier<AuthenticatedUser?>
);
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)',
Expand All @@ -298,7 +302,11 @@ class AuthNotifier extends AsyncNotifier<AuthenticatedUser?>
);
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',
Expand Down Expand Up @@ -1541,7 +1549,11 @@ class AuthNotifier extends AsyncNotifier<AuthenticatedUser?>
.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;
Expand Down
7 changes: 6 additions & 1 deletion mobile/lib/screens/attendance_calendar_screen.dart
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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);
}
Expand Down
7 changes: 6 additions & 1 deletion mobile/lib/screens/dashboard_screen.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -81,7 +82,11 @@ class _DashboardScreenState extends ConsumerState<DashboardScreen> {
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);
}
Expand Down
7 changes: 6 additions & 1 deletion mobile/lib/screens/navigation_shell.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -731,7 +732,11 @@ class _NavigationShellState extends ConsumerState<NavigationShell> {
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).',
);
Expand Down
7 changes: 6 additions & 1 deletion mobile/lib/screens/tracking_screen.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -65,7 +66,11 @@ class _TrackingScreenState extends ConsumerState<TrackingScreen>
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);
}
Expand Down
9 changes: 7 additions & 2 deletions mobile/lib/services/api_service.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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();
Expand Down
16 changes: 12 additions & 4 deletions mobile/lib/services/dio_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion mobile/lib/services/jwe_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion mobile/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ghostclass",
"version": "4.4.7",
"version": "4.4.8",
"private": true,
"engines": {
"node": ">=22.12.0",
Expand Down
2 changes: 1 addition & 1 deletion public/openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading