feat(notifications): mint JWT pool for native fetcher; log API response
- Mint BACKGROUND_JWT_POOL_SIZE (90 + 10) distinct background JWTs with unique jti; pass jwtTokens from nativeFetcherConfig into configureNativeFetcher. - Android TimeSafariNativeFetcher: overload configure with jwtTokenPool; select bearer via epoch day mod pool size; fall back to primary jwtToken. - Log truncated plansLastUpdatedBetween response at DEBUG for prefetch debugging.
This commit is contained in:
@@ -5,6 +5,7 @@ import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
@@ -42,6 +43,8 @@ public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher
|
||||
private static final int CONNECT_TIMEOUT_MS = 10000;
|
||||
private static final int READ_TIMEOUT_MS = 15000;
|
||||
private static final int MAX_RETRIES = 3;
|
||||
/** Max chars of response body logged at DEBUG (avoids huge log lines). */
|
||||
private static final int MAX_RESPONSE_BODY_LOG_CHARS = 4096;
|
||||
private static final int RETRY_DELAY_MS = 1000;
|
||||
|
||||
// Must match plugin's SharedPreferences name and keys (DailyNotificationPlugin / TimeSafariIntegrationManager)
|
||||
@@ -56,6 +59,9 @@ public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher
|
||||
private volatile String apiBaseUrl;
|
||||
private volatile String activeDid;
|
||||
private volatile String jwtToken;
|
||||
/** Distinct JWTs from configureNativeFetcher `jwtTokens`; null = use jwtToken only. */
|
||||
@Nullable
|
||||
private List<String> jwtTokenPool;
|
||||
|
||||
public TimeSafariNativeFetcher(Context context) {
|
||||
this.appContext = context.getApplicationContext();
|
||||
@@ -64,11 +70,48 @@ public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher
|
||||
|
||||
@Override
|
||||
public void configure(String apiBaseUrl, String activeDid, String jwtToken) {
|
||||
configure(apiBaseUrl, activeDid, jwtToken, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(
|
||||
String apiBaseUrl,
|
||||
String activeDid,
|
||||
String jwtToken,
|
||||
@Nullable List<String> jwtTokenPool) {
|
||||
this.apiBaseUrl = apiBaseUrl;
|
||||
this.activeDid = activeDid;
|
||||
this.jwtToken = jwtToken;
|
||||
this.jwtTokenPool =
|
||||
jwtTokenPool != null && !jwtTokenPool.isEmpty()
|
||||
? new ArrayList<>(jwtTokenPool)
|
||||
: null;
|
||||
int starredCount = getStarredPlanIds().size();
|
||||
Log.i(TAG, "Configured with API: " + apiBaseUrl + ", starredPlanIds count=" + starredCount);
|
||||
Log.i(
|
||||
TAG,
|
||||
"Configured with API: "
|
||||
+ apiBaseUrl
|
||||
+ ", starredPlanIds count="
|
||||
+ starredCount
|
||||
+ (this.jwtTokenPool != null
|
||||
? ", jwtPoolSize=" + this.jwtTokenPool.size()
|
||||
: ""));
|
||||
}
|
||||
|
||||
/** One pool entry per UTC day (epoch day mod pool size); else primary jwtToken. */
|
||||
private String selectBearerTokenForRequest() {
|
||||
List<String> pool = jwtTokenPool;
|
||||
if (pool == null || pool.isEmpty()) {
|
||||
return jwtToken;
|
||||
}
|
||||
long epochDay = System.currentTimeMillis() / (24L * 60 * 60 * 1000);
|
||||
int idx = (int) (epochDay % pool.size());
|
||||
String t = pool.get(idx);
|
||||
if (t == null || t.isEmpty()) {
|
||||
return jwtToken;
|
||||
}
|
||||
Log.i(TAG, "Bearer from JWT pool: index=" + idx + " of " + pool.size());
|
||||
return t;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@@ -92,7 +135,8 @@ public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
try {
|
||||
Log.i(TAG, "fetchContent worker thread=" + Thread.currentThread().getName());
|
||||
if (apiBaseUrl == null || activeDid == null || jwtToken == null) {
|
||||
String bearer = selectBearerTokenForRequest();
|
||||
if (apiBaseUrl == null || activeDid == null || bearer == null || bearer.isEmpty()) {
|
||||
Log.e(TAG, "Not configured. Call configureNativeFetcher() from TypeScript first.");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
@@ -104,7 +148,7 @@ public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher
|
||||
connection.setReadTimeout(READ_TIMEOUT_MS);
|
||||
connection.setRequestMethod("POST");
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setRequestProperty("Authorization", "Bearer " + jwtToken);
|
||||
connection.setRequestProperty("Authorization", "Bearer " + bearer);
|
||||
connection.setDoOutput(true);
|
||||
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
@@ -143,6 +187,16 @@ public class TimeSafariNativeFetcher implements NativeNotificationContentFetcher
|
||||
}
|
||||
}
|
||||
String responseBody = response.toString();
|
||||
String snippet =
|
||||
responseBody.length() <= MAX_RESPONSE_BODY_LOG_CHARS
|
||||
? responseBody
|
||||
: responseBody.substring(0, MAX_RESPONSE_BODY_LOG_CHARS) + "…";
|
||||
Log.d(
|
||||
TAG,
|
||||
"plansLastUpdatedBetween response len="
|
||||
+ responseBody.length()
|
||||
+ " body="
|
||||
+ snippet);
|
||||
List<NotificationContent> contents = parseApiResponse(responseBody, context);
|
||||
if (!contents.isEmpty()) {
|
||||
updateLastAckedJwtIdFromResponse(responseBody);
|
||||
|
||||
Reference in New Issue
Block a user