fix(notifications): refresh native fetcher on resume and log API error bodies

Call configureNativeFetcherIfReady when the app becomes active so getHeaders can
supply a new JWT before the next background prefetch when users return from
background.

In TimeSafariNativeFetcher, read HttpURLConnection#getErrorStream for non-200
responses and log a capped body snippet (including on retryable errors) to
diagnose JWT_VERIFY_FAILED and other API failures.
This commit is contained in:
Jose Olarte III
2026-03-26 18:16:08 +08:00
parent 0ebad3b497
commit f4ee507918
3 changed files with 53 additions and 4 deletions

View File

@@ -16,6 +16,8 @@ import org.timesafari.dailynotification.NativeNotificationContentFetcher;
import org.timesafari.dailynotification.NotificationContent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
@@ -151,7 +153,15 @@ public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher
if (retryCount < MAX_RETRIES && (responseCode >= 500 || responseCode == 429)) {
int delayMs = RETRY_DELAY_MS * (1 << retryCount);
Log.w(TAG, "Retryable error " + responseCode + ", retrying in " + delayMs + "ms");
String errBody = readHttpErrorBodySnippet(connection);
Log.w(
TAG,
"Retryable error "
+ responseCode
+ (errBody.isEmpty() ? "" : " body: " + errBody)
+ ", retrying in "
+ delayMs
+ "ms");
try {
Thread.sleep(delayMs);
} catch (InterruptedException e) {
@@ -161,7 +171,12 @@ public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher
return fetchContentWithRetry(context, retryCount + 1).join();
}
Log.e(TAG, "API error " + responseCode);
String errBody = readHttpErrorBodySnippet(connection);
if (errBody.isEmpty()) {
Log.e(TAG, "API error " + responseCode);
} else {
Log.e(TAG, "API error " + responseCode + " body: " + errBody);
}
return Collections.emptyList();
} catch (Exception e) {
Log.e(TAG, "Fetch failed", e);
@@ -179,6 +194,37 @@ public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher
});
}
/**
* Reads error response body for logging (HttpURLConnection puts 4xx/5xx bodies on
* {@link HttpURLConnection#getErrorStream()}).
*/
private static String readHttpErrorBodySnippet(HttpURLConnection connection) {
InputStream stream = connection.getErrorStream();
if (stream == null) {
return "";
}
final int maxChars = 4096;
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
StringBuilder sb = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
if (sb.length() > 0) {
sb.append('\n');
}
if (sb.length() + line.length() > maxChars) {
sb.append(line, 0, Math.max(0, maxChars - sb.length()));
sb.append("");
break;
}
sb.append(line);
}
return sb.toString().trim();
} catch (IOException e) {
return "(read error body failed: " + e.getMessage() + ")";
}
}
private List<String> getStarredPlanIds() {
try {
String idsJson = prefs.getString(KEY_STARRED_PLAN_IDS, "[]");