User experience after session timeout

What I’m trying to do:

Achieve decent user experience when a user leaves an app for a while (say 1 hour), comes back to the page, enters input, and hits submit.

The default behavior is pretty bad – the user gets “Your session has timed out. Please refresh the page to continue.”, clicks Refresh, loses all input, and must reenter them.

A few possible solutions

  1. Remember all form state so that, after the refresh, the information is at least repopulated for the user.

Pro: Ok user experience

Con: More boilerplate code to save/restore input fields

  1. Detect a long delay between window focuses, and just refresh the page immediately.

Something like this

  def __init__(...):
    self.last_focus_time = time.time()
    # 10 min < ~30 min after which Anvil kills session
    self.window_focus_threshold = 10 * 60
    anvil.js.window.addEventListener("focus", self.window_focus)
    
  def window_focus(self, event):
    time_elapsed = time.time() - self.last_focus_time
    if time_elapsed > self.window_focus_threshold:
      anvil.js.window.removeEventListener("focus", self.window_focus)
      anvil.js.window.location.reload()
    self.last_focus_time = time.time()

Pros: No error message, no ability to enter input that will soon be lost

Con: Relies on JS rather than core Anvil behavior

My actual question

This seems like a very vanilla issue, so my question is

Are there any patterns that I’m missing that achieve better UX without resorting to using JS? Relying instead on core Anvil functionality?

Related posts

I found 3 somewhat related posts, but they focus on different issues, and no solutions are applicable here.

Is it possible to avoid session timeouts? always maintains focus and simply wants to avoid timeouts. However here we lose focus so ping/pong doesn’t work.

The other 2 posts just focus on a previous bug where the session timeouts were constant.

Thanks

Welcome to the forum!

I haven’t used it, but in the docs on Sessions there’s a section on recovering from an expired session that doesn’t involve refreshing the entire app: Anvil Docs | Sessions and Cookies

Hi @rob1 ,

I had the same issue with an earlier app I wrote. I simply dragged a timer component onto the form. Then I set the interval to 1740 seconds which equals 29 minutes. After that time has elapsed it runs the Tick event.

I just had the event trigger run a low intensity server call which refreshed the page to keep the session alive.

def timer_1_tick(self, **event_args):
“”“This method is called Every [interval] seconds. Does not trigger if [interval] is 0.”“”

I guess if you don’t want to refresh and lose any info already keyed in, you could call some code which does nothing just to keep the session alive, You will have to ensure the timer resets again.

Maybe this is useful?

Just tested on my new site… works well. Timer automatically resets after it triggers.

I use session recovery, and it works well.

  1. I made an app called ‘restore_session’ that contains a single module “server”:
from anvil import *
import anvil.users
import anvil.server


def call_s(server_func_name: str, **kwargs):
  server_call_func = anvil.server.call_s
  return _server_call(server_call_func, server_func_name, **kwargs)

def call(server_func_name: str, **kwargs):
  server_call_func = anvil.server.call
  return _server_call(server_call_func, server_func_name, **kwargs)

def _server_call(server_call_func: callable, server_func_name:str, **kwargs):
  try:
    result = server_call_func(server_func_name, **kwargs)
    return result
  except anvil.server.SessionExpiredError:
    anvil.server.reset_session()
    if anvil.users.get_user(allow_remembered=True):
      return _server_call(server_call_func, server_func_name, **kwargs)
    else:
      raise anvil.server.SessionExpiredError()
      
  1. I import this app as a dependency into all my other apps, and in my forms:
import anvil.server # Delete this (automatically added by Anvil)
import restore_session.server as server #import 

..

def call_server_func(self):
    server.call('my_server_func') # call it just like anvil.server, but now with session recovery.

I see from my error logs that sometimes my users still get a SessionExpiredError, but not often.

4 Likes

Thanks! This looks like it will work. Will try it soon. Cheers.