Web: detect restored Tab vs. interactive page reload?

I have a Web app which has an automatic login feature (using Cookies). The app has a lot of logging, so I can tell when users are logging in, logging out, have timed out due to inactivity, etc.

At the moment, I see a lot of logins (using the cookie) in which there appears to be “nobody home” – e.g. the user is not actually using the web app.

I think that this is due to the the browser restoring or reloading a session from before, e.g. the user has the site open in a browser tab, puts their comptuer to sleep, then wakes their computer. The browser restores the tab - and this looks to my webApp as if the user has reloaded the page, but in fact the tab is in the background and the user is not on the site at all.

Question: is there a way in a Xojo Web 2 app to detect this? I suppose I could do it with some sort of UI/UX, putting up a messageDialog saying "Are you there? Click OK to proceed’.

But I’m wondering if there is another way, perhaps detecting browser headers or something?

Also, my memory is that Xojo Web was going to implement some sort of “session restoration” but that this feature was never released. Or was it?

I’m thinking perhaps I could use Session.Activated and .Deactivated events, but there’s some sort of documentation issue:

Deactivated

The window in which the app is running has been minimized, has lost the focus or the tab in which the app is running is no longer frontmost.

If Visible is true, the page is still visible to the user. The ability to detect this is dependent upon the browser supporting this capability.

(the Deactivated Event signature in the documentation is wrong)
https://tracker.xojo.com/xojoinc/xojo/-/issues/74011

Doing some testing, and it seems like .Deactivated and .Activated events are firing, but not in a way that’s very useful.

Normal page load event order:

Session.Opening
WebPage.Opening
Session.Activated

Hidden tab restoration event order:

Session.Opening
WebPage.Opening

Problems:

  1. .Activated doesn’t fire until well after the Session and first WebPage open
  2. On a tab restoration, .Activated doesn’t fire at all (neither does .Deactivated)
  3. .Activated doesn’t have a (visible as Boolean) parameter, so that event alone doesn’t tell you if the page is hidden or not.

I created a Feature Request for this:
https://tracker.xojo.com/xojoinc/xojo/-/issues/74012

1 Like

I don’t see what’s wrong with that. If the browser window was sent to the background but the app is still frontmost, the Visible parameter is true. If it is hidden behind another window or tab, Visible is false. This was supported behavior in at least Safari when the feature was implemented.

In terms of event order, there’s not much that can be done about that. IIRC Session.Open and WebPage.Open fire on the server before anything is delivered to the browser. The Activated event fires in response to an event fired by the browser once the initial app assets have been delivered to the browser.

Document.visibilityState may be what I want, see Document: visibilityState property - Web APIs | MDN since it can be read at any time and doesn’t require an event to fire.

I added a comment to the FR to consider exposing that as a WebSession property.

Maybe two events would be better, once the state has changed? Requesting such property can only be done async or sync and slowing down the entire web process?

That’s the design of Session.Activated and Session.Deactivated. The Activated event has no Visible parameter because the page can’t be activated without making it visible. I have found through testing that the web app does not get an activated event when the browser but not the tab is brought to the front. You also get a Deactivated event with the Visible parameter as false if the browser window is moved behind something, but the browser is still the active app. These events are effectively tracking the visibility state.

These events are behaving exactly as I would expect them to. I really don’t understand what Mike is trying to do or why, but I suspect there’s a better solution for the actual goal.

It is important to remember that Web and Desktop are different beasts, and building a web app to act like a desktop app is a difficult and often futile objective.

2 Likes

My goals:

  1. Statisics: detect when a user is actually logging into the system, vs. the browser automatically restoring a background tab. I need this in order to keep track of statistics such as “currently logged in users” and “user X last logged in N days ago”.
  2. Efficiency: avoid wasting resources - by detecting when a user is not actually there, and serving up a placeholder webPage instead of a complicated one requiring a lot of data/database transactions.

Problems:

  • Since Session.Activated fires after Session.Opening, you can’t really use any logic inside the Session.Opening event to determine which kind of user (“live” vs. “ghost”).
  • In the ‘hidden tab restoration’ scenario, neither .Activated nor .Deactivated fires at all, which means you have to guess the status.

I may be able to work around these issues by doing something like this:

Session.Opening
  me.liveUser=false // assume this is a background tab until proven otherwise
  setup a Timer to call Session.TimerCallback later 
  [... do nothing else important ...]

Session.Activated
   liveUser=true
Session.Deactivated
   liveUser=false

Session.TimerCallback
  if liveUser then
      WebPageComplicated.Show
      user.lastLoginTime = DateTime.Now
  else
     WebPageSimple.Show

I’m not sure how robust this aproach will be, and it’s a lot of extra logic for something which could be simpler.

I have opinions on your complaints, but nothing that can solve what you are considering an issue. I do not consider Activated or Deactivated to be malfunctioning with all of the tests I have performed today. You might consider redesigning your application structure if an inactive tab is really consuming a noticeable amount of database time.

In an effort not to create a negative atmosphere that’s all I can add to this discussion.

1 Like

IIRC, that’s the property that is monitored by the framework.

@Tim_Parnell I’m appreciating your thoughts & ideas, please don’t worry on my behalf!

Some of this is just hypothetical musing. The CPU usage and bandwidth use of the webApp is pretty low, as I typically have under 100 users and Xojo web 2 is pretty frugal.

I think the problem is due to my design in which

  • users are given (if they agree) a long-lasting cookie upon login, and if they ever reload the page, they are logged back in automatically via the cookie
  • I use Session.UserTimedOut to kick off absent users - however, I do this by calling Session.Terminate. While this works to disconnect the user, it leaves the browser tab sitting on the same URL as the webApp.
  • then, sometime later, the user wakes up their device, the browser restores tabs, and (because of my cookie login logic) they are logged back in, even if they are not actually using the WebApp, the tab is invisible or hidden, etc.

Maybe I could fix this:

  • what if before calling Session.Terminate, i set Session.HashTag to “#TimedOut
  • then, if the browser restores the tab, in Session.Opening I could check the hashTag, and if it is “timedOut” send them to a placeholder webPage which requires a click to log back in.
  • In addition, if Session.Activated fires, this would imply “actual user, tab is visible” and then I could trigger my auto-cookie-login logic.

I think this might solve most of the issues: keeping inactive users basically offline and out of my statistics, while not hassling actual users and forcing them to go through extra steps.

Why not just send them to the placeholder page directly on timeout instead of requiring another hit on your app?

Does this solve the problem?
ActivationTabChange

1 Like

Thanks @Artur_Zaremba - that looks like a good solution using the WebSDK - the downside is that it requires that you add a control to each WebPage you want to track. I’m still looking for something that can be at a higher level (the WebSession) requiring less code.

I haven’t had a chance to work on this in a few days, but I will report back if I figure out any clever solutions.

Also, I’m not sure how the WebSession.UserTimedOut event works with a backgrounded Tab. If it’s a server-side timer, then I would expect it to work even if the browser has paused the WebPage. But if it’s client-side, then maybe it never fires? That may add some complexity…