Add logging, await token callback and widget refresh

Fixes async token handling and improves observability: await the callback in KretaClient to ensure proper async flow, add informative logs for token expiry/refresh and warn on empty 200/201 API responses. Enhance token refresh flow in token_grant with detailed info/warning/severe logs and exception logging for easier debugging. Update widget DB helper to force fresh fetches for timetables/grades when updating widget cache, avoid writing empty caches and add debug prints for counts and cache status. Wire widget refresh into LiveActivityService background fetch (with import and try/catch logging) so iOS widgets get refreshed during background processing.
This commit is contained in:
Horváth Gergely
2026-01-30 10:41:26 +01:00
committed by 4831c0
parent f76b5fbcca
commit 402067d624
4 changed files with 39 additions and 7 deletions

View File

@@ -66,7 +66,7 @@ class KretaClient {
}
_tokenMutex = true;
try {
return callback();
return await callback();
} finally {
_tokenMutex = false;
}
@@ -78,7 +78,7 @@ class KretaClient {
if (now.millisecondsSinceEpoch >=
model.expiryDate!.millisecondsSinceEpoch) {
logger.finest("Token expired, refreshing: $model");
logger.info("Token expired at ${model.expiryDate}, refreshing for user: ${model.studentId}");
var extended = await extendToken(model);
var tokenModel = TokenModel.fromResp(extended);
@@ -86,7 +86,7 @@ class KretaClient {
await isar.tokenModels.put(tokenModel);
});
logger.finest("Token refreshed and saved: $model");
logger.info("Token refreshed successfully. New expiry: ${tokenModel.expiryDate}");
model = tokenModel;
}
@@ -116,6 +116,15 @@ class KretaClient {
if (!url.endsWith("TanuloAdatlap")) {
logger.finest("Response: ${resp.statusCode} ${resp.data}");
}
if (resp.statusCode == 200 || resp.statusCode == 201) {
final responseData = resp.data;
if (responseData == null ||
(responseData is List && responseData.isEmpty) ||
(responseData is Map && responseData.isEmpty)) {
logger.warning("API returned ${resp.statusCode} with empty data for: $url - possible stale session");
}
}
} catch (ex) {
if (ex is Error) {
logger.shout(

View File

@@ -41,6 +41,8 @@ Future<TokenGrantResponse> getAccessToken(String code) async {
}
Future<TokenGrantResponse> extendToken(TokenModel model) async {
logger.info("Extending token for user: ${model.studentId}, institute: ${model.iss}");
final headers = <String, String>{
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
"accept": "*/*",
@@ -60,16 +62,21 @@ Future<TokenGrantResponse> extendToken(TokenModel model) async {
switch (response.statusCode) {
case 200:
logger.info("Token extended successfully for user: ${model.studentId}");
return TokenGrantResponse.fromJson(response.data);
case 400:
logger.warning("Token refresh failed (400) - refresh token expired for user: ${model.studentId}");
throw TokenExpiredException();
case 401:
logger.warning("Token refresh failed (401) - invalid grant for user: ${model.studentId}");
throw InvalidGrantException();
default:
logger.severe("Token refresh failed with unexpected status: ${response.statusCode} for user: ${model.studentId}");
throw Exception(
"Failed to get access token, response code: ${response.statusCode}");
}
} catch (e) {
logger.severe("Token refresh exception for user: ${model.studentId}: $e");
rethrow;
}
}

View File

@@ -64,13 +64,16 @@ class WidgetCacheHelper {
final start = now.subtract(Duration(days: 7));
final end = now.add(Duration(days: 14));
final lessons = await client.getTimeTable(start, end);
final lessons = await client.getTimeTable(start, end, forceCache: false);
final widgetFile = File(p.join(dataDir.path, "widget_state.json"));
if (lessons.response != null) {
debugPrint('Android widget cache: ${lessons.response!.length} lessons (cached: ${lessons.cached})');
widgetFile.writeAsString(
jsonEncode(WidgetCacheHelper.toJson(style, lessons.response!)));
} else {
debugPrint('Android widget cache: No lessons to cache');
}
}
@@ -137,7 +140,6 @@ class WidgetCacheHelper {
theme = isLightMode.value ? 'light' : 'dark';
}
// Get today's and tomorrow's lessons
final now = timeNow();
final todayMidnight = DateTime(now.year, now.month, now.day);
final tomorrowMidnight = todayMidnight.add(Duration(days: 1));
@@ -145,19 +147,24 @@ class WidgetCacheHelper {
final todayResponse = await client.getTimeTable(
todayMidnight,
todayMidnight.add(Duration(hours: 23, minutes: 59)),
forceCache: false,
);
final tomorrowResponse = await client.getTimeTable(
tomorrowMidnight,
tomorrowMidnight.add(Duration(hours: 23, minutes: 59)),
forceCache: false,
);
final todayLessons = todayResponse.response ?? [];
final tomorrowLessons = tomorrowResponse.response ?? [];
// Get grades
final gradesResponse = await client.getGrades();
debugPrint('iOS widget refresh: ${todayLessons.length} today lessons, ${tomorrowLessons.length} tomorrow lessons');
final gradesResponse = await client.getGrades(forceCache: false);
final grades = gradesResponse.response ?? [];
debugPrint('iOS widget refresh: ${grades.length} grades fetched (cached: ${gradesResponse.cached})');
// Calculate subject averages
final Map<String, double> subjectAverages = {};
final Set<String> subjectUids = {};

View File

@@ -5,6 +5,7 @@ import 'package:firka/helpers/api/client/live_activity_backend_client.dart';
import 'package:firka/helpers/api/model/generic.dart';
import 'package:firka/helpers/api/model/timetable.dart';
import 'package:firka/helpers/db/models/app_settings_model.dart';
import 'package:firka/helpers/db/widget.dart';
import 'package:firka/helpers/live_activity_manager.dart';
import 'package:firka/helpers/settings.dart';
import 'package:firka/ui/phone/screens/live_activity/live_activity_consent_screen.dart';
@@ -443,6 +444,14 @@ class LiveActivityService {
}
}
try {
_logger.info('Background fetch: refreshing iOS widgets...');
await WidgetCacheHelper.refreshIOSWidgets(client, initData.settings);
_logger.info('Background fetch: iOS widgets refreshed successfully');
} catch (e) {
_logger.warning('Background fetch: failed to refresh iOS widgets: $e');
}
bool foundFirstSchoolDay = false;
for (int dayOffset = 1; dayOffset <= 5; dayOffset++) {
final candidateDay = endOfWeek.add(Duration(days: dayOffset));