Skip to content

Commit 6d3a0b7

Browse files
Fix for auto retry on 401 error
Now we are recreating the token and starting the auto retry
1 parent affca7b commit 6d3a0b7

3 files changed

Lines changed: 52 additions & 33 deletions

File tree

iterableapi/src/main/java/com/iterable/iterableapi/IterableAuthManager.java

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ interface AuthTokenReadyListener {
5555
private volatile boolean isInForeground = true; // Assume foreground initially
5656

5757
private volatile AuthState authState = AuthState.UNKNOWN;
58+
private final Object timerLock = new Object();
5859
private final ArrayList<AuthTokenReadyListener> authTokenReadyListeners = new ArrayList<>();
5960

6061
private final ExecutorService executor = Executors.newSingleThreadExecutor();
@@ -95,6 +96,21 @@ void setAuthTokenInvalid() {
9596
setAuthState(AuthState.INVALID);
9697
}
9798

99+
/**
100+
* Handles a server-side JWT rejection (401). Invalidates the current token,
101+
* clears any pending refresh, and schedules a new token request using the retry policy.
102+
* When the new token arrives, AuthTokenReadyListeners are notified via the
103+
* INVALID → UNKNOWN state transition.
104+
*/
105+
void handleAuthTokenRejection() {
106+
setAuthState(AuthState.INVALID);
107+
setIsLastAuthTokenValid(false);
108+
clearRefreshTimer();
109+
resetFailedAuth();
110+
long retryInterval = getNextRetryInterval();
111+
scheduleAuthTokenRefresh(retryInterval, false, null);
112+
}
113+
98114
AuthState getAuthState() {
99115
return authState;
100116
}
@@ -292,29 +308,31 @@ long getNextRetryInterval() {
292308
}
293309

294310
void scheduleAuthTokenRefresh(long timeDuration, boolean isScheduledRefresh, final IterableHelper.SuccessHandler successCallback) {
295-
if ((pauseAuthRetry && !isScheduledRefresh) || isTimerScheduled) {
296-
// we only stop schedule token refresh if it is called from retry (in case of failure). The normal auth token refresh schedule would work
297-
return;
298-
}
299-
if (timer == null) {
300-
timer = new Timer(true);
301-
}
311+
synchronized (timerLock) {
312+
if ((pauseAuthRetry && !isScheduledRefresh) || isTimerScheduled) {
313+
// we only stop schedule token refresh if it is called from retry (in case of failure). The normal auth token refresh schedule would work
314+
return;
315+
}
316+
if (timer == null) {
317+
timer = new Timer(true);
318+
}
302319

303-
try {
304-
timer.schedule(new TimerTask() {
305-
@Override
306-
public void run() {
307-
if (api.getEmail() != null || api.getUserId() != null) {
308-
api.getAuthManager().requestNewAuthToken(false, successCallback, isScheduledRefresh);
309-
} else {
310-
IterableLogger.w(TAG, "Email or userId is not available. Skipping token refresh");
320+
try {
321+
timer.schedule(new TimerTask() {
322+
@Override
323+
public void run() {
324+
if (api.getEmail() != null || api.getUserId() != null) {
325+
api.getAuthManager().requestNewAuthToken(false, successCallback, isScheduledRefresh);
326+
} else {
327+
IterableLogger.w(TAG, "Email or userId is not available. Skipping token refresh");
328+
}
329+
isTimerScheduled = false;
311330
}
312-
isTimerScheduled = false;
313-
}
314-
}, timeDuration);
315-
isTimerScheduled = true;
316-
} catch (Exception e) {
317-
IterableLogger.e(TAG, "timer exception: " + timer, e);
331+
}, timeDuration);
332+
isTimerScheduled = true;
333+
} catch (Exception e) {
334+
IterableLogger.e(TAG, "timer exception: " + timer, e);
335+
}
318336
}
319337
}
320338

@@ -363,10 +381,12 @@ private void checkAndHandleAuthRefresh() {
363381
}
364382

365383
void clearRefreshTimer() {
366-
if (timer != null) {
367-
timer.cancel();
368-
timer = null;
369-
isTimerScheduled = false;
384+
synchronized (timerLock) {
385+
if (timer != null) {
386+
timer.cancel();
387+
timer = null;
388+
isTimerScheduled = false;
389+
}
370390
}
371391
}
372392

iterableapi/src/main/java/com/iterable/iterableapi/IterableRequestTask.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -262,18 +262,17 @@ static IterableApiResponse executeApiRequest(IterableApiRequest iterableApiReque
262262
}
263263

264264
/**
265-
* When autoRetry is enabled and this is an offline task, skip the inline retry.
266-
* The task stays in the DB and IterableTaskRunner will retry it once a valid JWT
267-
* is obtained via the AuthTokenReadyListener callback.
265+
* When autoRetry is enabled and this is an offline task, do nothing here.
266+
* IterableTaskRunner.processTask() is the sole owner of 401 handling for offline tasks:
267+
* it calls setAuthTokenInvalid() which invalidates the token and schedules a refresh.
268+
* When the new token arrives, onAuthTokenReady() resumes the queue.
268269
* For online requests or when autoRetry is disabled, use the existing inline retry.
269270
*/
270271
private static void handleJwtAuthRetry(IterableApiRequest iterableApiRequest) {
271272
boolean autoRetry = IterableApi.getInstance().isAutoRetryOnJwtFailure();
272273
if (autoRetry && iterableApiRequest.getProcessorType() == IterableApiRequest.ProcessorType.OFFLINE) {
273-
IterableAuthManager authManager = IterableApi.getInstance().getAuthManager();
274-
authManager.setIsLastAuthTokenValid(false);
275-
long retryInterval = authManager.getNextRetryInterval();
276-
authManager.scheduleAuthTokenRefresh(retryInterval, false, null);
274+
IterableLogger.d(TAG, "Offline task 401 - deferring retry to IterableTaskRunner");
275+
return;
277276
} else {
278277
requestNewAuthTokenAndRetry(iterableApiRequest);
279278
}

iterableapi/src/main/java/com/iterable/iterableapi/IterableTaskRunner.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ private boolean processTask(@NonNull IterableTask task, boolean autoRetry) {
197197
// retain the task and pause processing until a valid JWT is obtained.
198198
if (autoRetry && isJwtFailure(response)) {
199199
IterableLogger.d(TAG, "JWT auth failure on task " + task.id + ". Retaining task and pausing processing.");
200-
IterableApi.getInstance().getAuthManager().setAuthTokenInvalid();
200+
IterableApi.getInstance().getAuthManager().handleAuthTokenRejection();
201201
isPausedForAuth = true;
202202
callTaskCompletedListeners(task.id, TaskResult.RETRY, response);
203203
return false;

0 commit comments

Comments
 (0)