@@ -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
0 commit comments