next-auth
next-auth copied to clipboard
Allow pausing the session polling when the browser is offline
Description 📓
When the session polling is enabled (refetchInterval
option), the request to /session
is made regardless of the browser networks status. If the browser is offline (i.e. computer puts itself to sleep), the request fails, and the session is invalidated (null
). This behavior can cause UI issues and make other requests fail when the application stores access tokens in the session object.
Example (next-auth + SWR):
- The app is mounted, and SWR fetches data successfully.
- The host device goes offline =>
/session
request fails =>session
is invalidated (null). - The host device goes back online, triggering the window focus event.
- SWR tries to fetch the data but sends unauthenticated requests because the session is null, resulting in an error.
Suggested solution:
We could add a new flag similar to the SWR's refreshWhenOffline
. When disabled, all requests would be paused while the browser is offline, so the session would not be invalidated. This flag wouldn't introduce any breaking change, but it'd make the behavior configurable by the user.
The issue is related to #2215, but the stale bot didn't reopen the issue when I commented on it. As an alternative to this suggestion, we could stop the session from being invalidated, but I agree with the point made in https://github.com/nextauthjs/next-auth/issues/2215#issuecomment-867190636.
How to reproduce ☕️
- Run the example app with at least one configured provider (Auth0, for example).
- Ensure the session polling is enabled (
refetchInterval
). - Sign in with the previously configured auth provider.
- Use the dev tools to set the throttling to "Offline".
- Wait until the application tries to refetch the session.
- See the session gets invalidated.
Contributing 🙌🏽
Yes, I am willing to help implement this feature in a PR
So I don't have a definitive answer, but as far as I am aware, setInterval
(which refetchInterval
uses) stops when the computer goes to sleep and resumes when it's back: https://stackoverflow.com/a/6347336/5364135
Answer here also suggests that inactive tabs have lower priority for executing JS so I would assume the same as above: https://stackoverflow.com/a/5927432/5364135
On a second read, I fixated a bit much on the "sleeping" part, when in reality you are discussing online/offline states here.
I think you make a good point. There is not much value in polling when there is no internet access. I'm opening a PR to make this opt-in, we might even want this as the default behavior in the next major release. :+1:
Thank you for such a swift implementation! Let me know if you need help with anything 🙂
Yeah, you could try it out, here is an experimental published version https://github.com/nextauthjs/next-auth/pull/4940#issuecomment-1185042331
Okay, I tested it on my project. The session polling is indeed paused when the refreshWhenOffline
flag is set to false
. However, the session is still being refreshed on window focus causing it to be invalidated anyway.
I'm wondering whether the refreshWhenOffline
flag should override the refetchOnWindowFocus
flag or not. It's not exactly obvious which flag has the higher priority.
Sorry, I know this is not the original issue, but I wouldn't want this change to create confusion.
Hmm, yeah. Good point and sounds relevant, but these are separate flags IMO, not necessarily that one has priority over the other.
You can already disable refetching on window focus. You could set refetchOnWindowFocus={typeof navigator !== "undefined" && navigator.onLine}
(Extra check is added since navigator
would not be available when prerendering the page at build-time)
~Maybe refreshWhenOffline
-> refetchWhenOffline
naming would be better and could cover both at the same time. But refresh*
sounds like it should only change behavior when polling is enabled.~ I did call it that, so maybe we should just incorporate your suggestion. :sweat_smile: Although there is some ambiguity around refetch and refresh that might cause confusion. :thinking:
Not sure...
Let's call it refreshWhenOffline
and suggest refetchOnWindowFocus={typeof navigator !== "undefined" && navigator.onLine}
in addition in the docs to keep these separate?
(In the long run, we might want to revisit the efforts like https://github.com/nextauthjs/react-query to make the whole session state handling pluggable into other libs. At this point, we are recreating SWR and react-query
. :shrug:)
Let's call it refreshWhenOffline and suggest refetchOnWindowFocus={typeof navigator !== "undefined" && navigator.onLine} in addition in the docs to keep these separate?
So if I understand correctly, there would be two options affecting the polling configuration?
-
refreshWhenOffline
-
refetchInterval
I'm afraid having different prefixes for these two might backfire quickly. I like how SWR distinguishes between options affecting the polling configuration (refresh*
prefix) and one-time revalidation requests (revalidate*
prefix). But that brings us back to your point about recreating SWR.
It seems like the only way to avoid confusion is to make the refetchWhenOffline
also affect the revalidation on window focus. I can't figure out any other way which wouldn't introduce breaking changes 😞
(In the long run, we might want to revisit the efforts like https://github.com/nextauthjs/react-query to make the whole session state handling pluggable into other libs. At this point, we are recreating SWR and react-query. 🤷)
Not gonna lie, this sounds like it'd solve many issues. I don't think the built-in useSession
will ever be so robust as SWR or react-query
. If the only problem is you not having the bandwidth for maintaining another package, I can put together some up-to-date versions, at least for SWR and react-query
.
Partially the bandwidth/maintenance burden, but properly managing the session state would need to involve signIn
and signOut
as well, as those can mutate the state as well. Currently, it is a bit hacky, as these leave outside the React world:
https://github.com/nextauthjs/next-auth/blob/c56abbd74504687134732474425d5b99ae2a8ed3/packages/next-auth/src/react/index.tsx#L243
https://github.com/nextauthjs/next-auth/blob/c56abbd74504687134732474425d5b99ae2a8ed3/packages/next-auth/src/react/index.tsx#L291
Basically, we would need to recreate the whole next-auth/react
module for each library. Or a better solution is to abstract away the session management and signIn
/signOut
methods that aren't tied to React at all but could be plugged into it.
This might also make it easier to create clients for other frameworks as we expand (#3942) and will probably be the next logical step.
To keep this feature request small though we could recommend this today:
<SessionProvider
refetchInterval={60}
refetchOnWindowFocus={typeof navigator !== "undefined" && navigator.onLine}
refetchWhenOffline={false}
>
So this would poll the session every 60 seconds, but stop annulling the session when offline and would only refetch on window focus if you are online again.
Basically, we would need to recreate the whole next-auth/react module for each library. Or a better solution is to abstract away the session management and signIn/signOut methods that aren't tied to React at all but could be plugged into it.
Thanks for the explanation. I can see the complexity now.
So this would poll the session every 60 seconds, but stop annulling the session when offline and would only refetch on window focus if you are online again.
This seems to be an optimal solution for now. Thank you!
Edit: If anyone using SWR stumbles across this issue, I've created an SWR-powered alternative to @next-auth/react-query
:
https://github.com/jozefhruska/next-auth-swr
As an alternative to this suggestion, we could stop the session from being invalidated, but I agree with the point made in #2215 (comment).
I was looking for a way to stop the session from being invalidated in a somewhat similar case, because it's kind of a problem, so I stumbled upon this issue. Preventing the invalidation (perhaps with a callback that receives the error and the previous session, and returns a session or null) would solve the problem for me as well.
In my case, I'm getting an access token from the session. Requests with this access token go to an external API and the token lives long enough so that one failed session call will never result in an expired token. If the session wasn't invalidated when getSession fails, the user would not notice that there was an error. With the current implementation however, the user is logged out.
<SessionProvider refetchInterval={60} refetchOnWindowFocus={typeof navigator !== "undefined" && navigator.onLine} refetchWhenOffline={false} >
@ThangHuuVu @balazsorban44 hi guys! I tried this but it is not working. Session is still being refreshed even if user is offline
PS: the only thing which i did not configure was refetchInterval={60} as I don't want the network cost due to a call to session being made every 60secs (I have kept it as default i.e. 0)
@jozefhruska is this feature still working for you? I am using NextAuth - 4.10.1. If its working for you... can you help me out for what I might be doing wrong... help would be really appreciated as this feature would really help us in production.
Working with this feature I've found a couple things that can confound testing...
- Your browser might matter and how you are going offline might matter - if you are using chrome network tab, there are gotchas with dev tools and workbox. You can get close.
- If you are using next-pwa dev/production matters. Also, the cached/installed version of the app matters. Try a production build then run start and open an incognito window. This helped narrow a lot of issues down for me.
With those solved:
<SessionProvider
refetchInterval={60}
refetchOnWindowFocus={typeof navigator !== "undefined" && navigator.onLine}
refetchWhenOffline={false}
>
using typeof navigator !== "undefined" && navigator.onLine
was flaky for me (especially in dev). The initial value was getting cached (HMR). If you change to
<SessionProvider
refetchInterval={60}
refetchOnWindowFocus={false}
refetchWhenOffline={false}
>
It might reveal what was happening for you (at the cost of some functionality)
Also, if your page/component has useSession
in it... that might be triggering a refresh. Try commenting that out and see if it helps narrow the issue. With these changes the session was working correctly for me.