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 org.timesafari.dailynotification.NotificationContent;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
@@ -151,7 +153,15 @@ public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher
if (retryCount < MAX_RETRIES && (responseCode >= 500 || responseCode == 429)) { if (retryCount < MAX_RETRIES && (responseCode >= 500 || responseCode == 429)) {
int delayMs = RETRY_DELAY_MS * (1 << retryCount); 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 { try {
Thread.sleep(delayMs); Thread.sleep(delayMs);
} catch (InterruptedException e) { } catch (InterruptedException e) {
@@ -161,7 +171,12 @@ public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher
return fetchContentWithRetry(context, retryCount + 1).join(); 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(); return Collections.emptyList();
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Fetch failed", 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() { private List<String> getStarredPlanIds() {
try { try {
String idsJson = prefs.getString(KEY_STARRED_PLAN_IDS, "[]"); String idsJson = prefs.getString(KEY_STARRED_PLAN_IDS, "[]");

4
package-lock.json generated
View File

@@ -8685,8 +8685,8 @@
} }
}, },
"node_modules/@timesafari/daily-notification-plugin": { "node_modules/@timesafari/daily-notification-plugin": {
"version": "2.1.3", "version": "2.1.5",
"resolved": "git+https://gitea.anomalistdesign.com/trent_larson/daily-notification-plugin.git#4dd1aea00224e4edc79a1fa3aa5767985a771c25", "resolved": "git+https://gitea.anomalistdesign.com/trent_larson/daily-notification-plugin.git#469167a55fbebb91b3e61d6c8b3aec6fc873a13c",
"license": "MIT", "license": "MIT",
"workspaces": [ "workspaces": [
"packages/*" "packages/*"

View File

@@ -459,6 +459,9 @@ if (
if (isActive) { if (isActive) {
logger.debug("[Main] 📱 App became active, checking for shared image"); logger.debug("[Main] 📱 App became active, checking for shared image");
await checkForSharedImageAndNavigate(); await checkForSharedImageAndNavigate();
// Refresh JWT for background New Activity prefetch (WorkManager cannot run JS;
// short-lived tokens would expire between configure and T5 fetch without this).
await configureNativeFetcherIfReady();
} }
}); });
} }