3

I am currently trying to send a request when an user closes the page. I am using the onbeforeunload event.

The event triggers when the tab is closed or the page is refreshed. My event looks as follows:

window.onbeforeunload = function () {
    $.ajax({ //jQuery
        type: "POST",
        url: "offline.php",
        data: {
            logout: 'false'
        }
    });
}; 

offline.php (this is not the complete script):

...
unset($_SESSION["onpage"];
if ($_POST['logout'] == "false") {
    sleep(3);
    if (isset($_SESSION["onpage"]) || !empty($_SESSION["onpage"])) die();
}
...

When a user closes the page, script unsets a session that is set on the page of the chat. After three seconds, the script should check if the user has returned but checking the onpage session. However, the problem comes in when I hit the refresh button. On refresh, the page does not load because the three seconds have completed. This is causing my whole system to be ruined.

I have tried adding ignore_user_abort(true); but it did not solve my issue. Is there any other way of getting this to work?

Side Note: This is for a chat. When a user closes the page, it should notify the other users on the chat with a "The user has left" message. This should not be displayed on refresh. When a user comes back to the page, it should notify the other users that the user has returned with a "The user has entered" message.

Chat

Shawn31313
  • 5,978
  • 4
  • 38
  • 80
  • See http://stackoverflow.com/questions/1617412/how-can-i-stop-php-sleep-affecting-my-whole-php-code – guest271314 Mar 30 '16 at 02:50
  • 1
    You cannot depend on AJAX requests inside an `onbeforeunload`. They may or may not complete. – user229044 Apr 06 '16 at 02:51
  • 2
    Is there any requirement to use Ajax and not something like socket.io? With socket.io you can detect a disconnect on the server side. (I was editing as answer, but was it deleted -- this is more of a comment anyway) – noderman Apr 06 '16 at 02:59
  • For now AJAX it is. I am still learning and not sure exactly how sockets work. However, I am using server sent events but I doubt those have the same functionality. @noderman – Shawn31313 Apr 06 '16 at 03:15
  • All right -- on a not so side note, socket.io is practically born for chats: http://socket.io/demos/chat/ -- worth digging. – noderman Apr 06 '16 at 03:39
  • I agree with @noderman advice is the best and will handle all that issues for you, you have to implement it to build a reliable and high quality app. – Mohammed Gomma Apr 07 '16 at 11:39

4 Answers4

7

The Problem

I'm afraid what you're attempting to do isn't really possible, as there are many times in which onbeforeunload won't get called, due to browser implementation, user preference, your pet zebra knocking your computer off the table, etc.

From Mozilla's beforeunload documentation:

Note also that various mobile browsers ignore the result of the event (that is, they do not ask the user for confirmation). Firefox has a hidden preference in about:config to do the same. In essence this means the user always confirms that the document may be unloaded.

https://developer.mozilla.org/en-US/docs/Web/Events/beforeunload

The Solution

That doesn't mean handling users going offline is impossible, however.

Most chat web applications instead employ a heartbeat (a regular ping-like request), and use the distance between last ping to determine user disconnects. As an additional note, I would recommend allowing a wider window than three seconds to determine user disconnects, because there are many reasons a ping may not make it within a three second timeframe.

Implementation Strategy

You have a few options to implement a ping. Most commonly, people will use a window.setTimeout to invoke the ping, which will restart the window.setTimeout upon completion or failure, usually doubling the delay on successive failures so you aren't barraging a potentially-overloaded service.

setTimeout to Ping:

var i = 1000;

(function ping() {
  $.ajax({
    type: "POST",
    url: "ping.php"
  }).done(function() {
    i = 1000;
    window.setTimeout(ping, i);
  }).fail(function() {
    i = i * 2;

    if (i > 60000) {
      // We don't wait to wait longer than a minute
      i = 60000;
    }

    window.setTimeout(ping, i);
  });
}());

What about window.setInterval instead? Won't my code be shorter?

Please don't do that. My above note about "barraging an overloaded service"? It'll be plenty worse if you do that.

Community
  • 1
  • 1
CassOnMars
  • 6,153
  • 2
  • 32
  • 47
  • How could I go about creating this ping? – Shawn31313 Apr 06 '16 at 03:03
  • So say a user does disconnect, how will I tell the server that the user isn't connected anymore based on the ping if he is no longer on the page? I'm very confused about what ping.php would actually do. – Shawn31313 Apr 06 '16 at 03:18
  • You base a user's connected status on how long it has been since the last ping for a given user. ping.php would simply be a PHP script that sets that user's `lastPing` (or whatever you name it) variable to the current time. – CassOnMars Apr 06 '16 at 03:21
  • Additionally, if offline.php is terminating a session, you would simply perform the inverse on your other scripts, and terminate the session if their last ping time has been too long (arbitrarily defined by you, of course). – CassOnMars Apr 06 '16 at 03:23
  • But ping.php would continue to run only if the page is open. Once tab is closed, so would ping.php. However, I need someone to happen as the page is already closed – Shawn31313 Apr 06 '16 at 03:26
  • The purpose is to notify the other users, right? If so, then the client side for the users can detect that the user has left because the other user's lastPing time is too far out. – CassOnMars Apr 06 '16 at 03:27
  • Oh Yes thats right, that is the main reason for the feature, but what happens if there is only one user in the chat? – Shawn31313 Apr 06 '16 at 03:29
  • And they disconnected from the chat? Then nobody would be in the room to see a disconnected notification. – CassOnMars Apr 06 '16 at 03:30
  • I attached a picture of my chat in order for you to get a better idea of the type of notifications I want. Yes, they are for the other users to know that they have an active audience. But they are also meant to be implemented into the chat as a norm. I have seen this done before so I know its not impossible. – Shawn31313 Apr 06 '16 at 03:36
  • I see. In which case, assuming you have both a lastPing value and an online flag for a given user, you can use ping.php to set the user's lastPing as previously described, and upon _any_ user visiting the chatroom again, you can scan through your set of all users to find who has online == true, and an expired lastPing, and for those users, set their online flag to false, and add the chat message at the timestamp of lastPing. That way it works for empty and active chat rooms. – CassOnMars Apr 06 '16 at 03:40
  • I see what you mean! I will try this tomorrow! – Shawn31313 Apr 06 '16 at 04:06
  • You may use some nosql DB like Redis to avoid regular queries on the Db for the user status – Mohammed Gomma Apr 07 '16 at 11:41
1

You really shouldn't be counting on onbeforeunload or beforeunload since a browser shouldn't require itself to do something on your behalf right before a user closes your webpage. If it did, it would open up a whole avenue of attack for malicious code where you could never close a page (think going to a torrent website and having 2 pages pop up every time you click on any DOM element, only now every time a user tries to close the tab, a recursive loop starts and prevents them from ever leaving without killing the browser through task manager).

Heartbeats

Most chat clients use a heartbeat system where the client automatically pings the server every x seconds. This method works - but it's costly and inefficient. You're forced to open and close new connections on a constant basis just to tell the server that you are still engaged.

Web Sockets

The contemporary way of building something like a chat system is to use web sockets. These are persistent connections established by the server between your web browser and the server. On a traditional web page, the server always reacts to the what the client does. The client makes a request, the server responds; this process repeats itself over and over again. The server never tells the client what to do and then wait for the client response. With web sockets, instead of having this one way communication channel, we have a pipeline that allows for a continuously open connection with bidirectional communication.

Creating Web Sockets

This isn't a trivial task. There's a lot of boilerplate code out of the gate you would have to write to get some basic functionalities working. But the good news is that you don't have to do this. There are several 3rd party JavaScript SDKs / back-end services that encapsulate all of the ground level browser implementation logic for you and make available to you a simple, clear interface. Think of their relationship to web sockets as what jQuery was to plain JavaScript. They standardize everything for you so you don't have to worry about writing low level web socket implementation and syncing up cross browser compatibility.

One of my favorite web socket services is Firebase. Coincidentally, the demo project they showcase on their website is a real-time chat application built using the web socket support their service provides. Firebase can be set up to be an indepedent database source if you want it to be that. Or it can simply be used as a medium of communications between your traditional SQL database (which communicates with it through a RESTful API) and client devices (browsers, apps, etc...).

Lloyd Banks
  • 35,740
  • 58
  • 156
  • 248
0

I would like to point out that, as others have said, doing an AJAX request on the page unload event is highly unreliable.

However, you may be interested in Navigator.sendBeacon()

Relevant excerpt:

This method addresses the needs of analytics and diagnostics code that typically attempt to send data to a web server prior to the unloading of the document.

https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon

It is experimental, but once fully supported, should do what you want.

crunch
  • 675
  • 3
  • 8
0

AJAX can get cut off as the page leaves.

You need to use navigator.sendBeacon() instead.

https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon

window.addEventListener('unload', function () {

     navigator.sendBeacon("offline.php", {
         logout: 'false'
     });

}, false);

This is simple, reliable, and needs no server smarts like juggling heartbeats entails.

Of course, like anything good in webdev, IE compat is lacking...

dandavis
  • 16,370
  • 5
  • 40
  • 36