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 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
4
package-lock.json
generated
@@ -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/*"
|
||||||
|
|||||||
@@ -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 T−5 fetch without this).
|
||||||
|
await configureNativeFetcherIfReady();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user