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:
@@ -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();
|
||||
}
|
||||
|
||||
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, "[]");
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -8685,8 +8685,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@timesafari/daily-notification-plugin": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "git+https://gitea.anomalistdesign.com/trent_larson/daily-notification-plugin.git#4dd1aea00224e4edc79a1fa3aa5767985a771c25",
|
||||
"version": "2.1.5",
|
||||
"resolved": "git+https://gitea.anomalistdesign.com/trent_larson/daily-notification-plugin.git#469167a55fbebb91b3e61d6c8b3aec6fc873a13c",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
|
||||
@@ -459,6 +459,9 @@ if (
|
||||
if (isActive) {
|
||||
logger.debug("[Main] 📱 App became active, checking for shared image");
|
||||
await checkForSharedImageAndNavigate();
|
||||
// Refresh JWT for background New Activity prefetch (WorkManager cannot run JS;
|
||||
// short-lived tokens would expire between configure and T−5 fetch without this).
|
||||
await configureNativeFetcherIfReady();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user