|
|
@@ -145,6 +145,25 @@ public class VivoAuthService {
|
|
|
return coldStore.listTokens();
|
|
|
}
|
|
|
|
|
|
+ public void refreshTokensExpiringWithin(Duration threshold) {
|
|
|
+ long thresholdMs = normalizeThresholdMs(threshold);
|
|
|
+ List<VivoTokenRecord> tokens = listAuthorizedSecondaryAccounts();
|
|
|
+ if (tokens == null || tokens.isEmpty()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ for (VivoTokenRecord token : tokens) {
|
|
|
+ if (token == null || token.getAccountId() == null || token.getAccountId().isBlank()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ refreshTokenIfExpiringWithin(token.getAccountId().trim(), thresholdMs);
|
|
|
+ } catch (Exception e) {
|
|
|
+ log.error("[Vivo][Token] secondaryAccountId={} proactive refresh failed: {}",
|
|
|
+ token.getAccountId(), e.getMessage(), e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
public void cacheAdvertiserMappings(List<VivoAdvertiserAccountRecord> records) {
|
|
|
if (records == null || records.isEmpty()) {
|
|
|
return;
|
|
|
@@ -252,6 +271,28 @@ public class VivoAuthService {
|
|
|
return record;
|
|
|
}
|
|
|
|
|
|
+ private void refreshTokenIfExpiringWithin(String secondaryAccountId, long thresholdMs) {
|
|
|
+ synchronized (locks.computeIfAbsent(secondaryAccountId, ignored -> new Object())) {
|
|
|
+ VivoTokenRecord record = loadToken(secondaryAccountId);
|
|
|
+ if (record == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ long now = System.currentTimeMillis();
|
|
|
+ if (!expiresWithin(record.getTokenExpireAt(), now, thresholdMs)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ if (isUsable(record.getRefreshToken(), record.getRefreshTokenExpireAt(), now)) {
|
|
|
+ long remainingMs = record.getTokenExpireAt() == null ? -1L : record.getTokenExpireAt() - now;
|
|
|
+ log.info("[Vivo][Token] secondaryAccountId={} token expires soon, proactive refresh | remainingMs={} | thresholdMs={}",
|
|
|
+ secondaryAccountId, remainingMs, thresholdMs);
|
|
|
+ refresh(secondaryAccountId, record.getRefreshToken());
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ log.warn("[Vivo][Token] secondaryAccountId={} token expires within threshold but refresh_token is unavailable | tokenExpireAt={} | refreshTokenExpireAt={}",
|
|
|
+ secondaryAccountId, record.getTokenExpireAt(), record.getRefreshTokenExpireAt());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
private VivoTokenRecord buildTokenRecord(AgentAccountProfile profile,
|
|
|
TokenPayload payload,
|
|
|
String secondaryAccountId,
|
|
|
@@ -477,6 +518,17 @@ public class VivoAuthService {
|
|
|
return token != null && !token.isBlank() && expireAt != null && expireAt - now > REFRESH_AHEAD_MS;
|
|
|
}
|
|
|
|
|
|
+ private static boolean expiresWithin(Long expireAt, long now, long thresholdMs) {
|
|
|
+ return expireAt != null && expireAt - now <= thresholdMs;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static long normalizeThresholdMs(Duration threshold) {
|
|
|
+ if (threshold == null || threshold.isNegative() || threshold.isZero()) {
|
|
|
+ return Duration.ofDays(1).toMillis();
|
|
|
+ }
|
|
|
+ return threshold.toMillis();
|
|
|
+ }
|
|
|
+
|
|
|
private static String toQuery(Map<String, String> params) {
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
boolean first = true;
|