how to send message "page will close" from browser to server? - javascript

I want to add a script (JavaScript) to each html-document, that sends two messages to the server:
page did open
page will close (this message contains how long the page was open)
The open-message should be sent when the document is loading (or when it finished loading). This is the easy part.
The close-message should de sent when the document is unloaded from the browser's viewport. (User clicks on a link that has not target="_blank"; User closes the browser tab/window; User reloads the page; User quits the browser; anything else that makes the page disappear)
I tried it this way:
//================================================================================
//Global Variables
//================================================================================
gl = {}; //container for global variables (1 scalar and 1 function)
gl.start = Math.floor(Date.now()); //timestamp of page-loading
//================================================================================
//function: Send message to server
//================================================================================
gl.sendData = function (action) {
var message = {
'href' : window.location.href,
'height' : $(window).height(),
'width' : $(window).width(),
'action' : action, //can be 'open' or 'close' (i.e. 'load' and 'unload')
'rand' : Math.random() //random number to outwit cache
};
if (action == 'close') {
//how long was the page open? [milliseconds]
message.duration = Math.floor(Date.now()) - gl.start;
};
window.alert(action); //debugging: show if gl.sendData is executed
$.ajax({
'url' : 'http://' + window.location.hostname + '/path/to/script.pl',
'data' : message,
'success' : function(){}, //not interested in server's answer
'error' : function(){} //errors will be ignored
});
};
//================================================================================
//Things to do as soon as page load is complete
//================================================================================
$(document).ready(function(){
//send message "page did open"
gl.sendData('open');
//add event-handler to send the message "page will close" when the page is closing
$(window).on("unload", function() {
gl.sendData('close');
});
});
Sending a message when the page did open is working perfectly fine. But the browser doesn't send the close-message.
I found out this facts:
"unload" seems to be the correct event. The alert-message pops up when the page is closing.
"beforeunload" doesn't work because it is not fired (Safari on Mac, when clicking on a link that navigates to another page)
the ajax-request is sent when the page is loading, so it seems to be ok.
the ajax-request doesn't send data to the server when the page is closing.
my Question:
Is there a way to send a massage to the server in the moment when a document is unloaded from the browser?
I want to send the duration of the page beeing displayed to the server. Is there another way to do this?

There is no 100% reliable way to send data to a server when the page is closing that works in all browsers.
With the advent and general availability of webSockets in browsers, there are some who are using a webSocket connection from client to server as a means of tracking this. When the page is opened, it makes a webSocket (or socket.io) connection to the server. This connection is kept open for the duration of the page. When the user leaves the page or closes the browser, the webSocket will get closed by the browser. The server can then see that the webSocket has been closed and can mark that as the time that the page was closed.
One other possibility that is less efficient than the webSocket connection is for the open webpage to regularly poll the server via Ajax (say every 30 seconds or so to announce that the page is still open). When the server sees the polling stop, it assumes that page has been closed.
Both of these techniques require regular connectivity with the server in order to do this tracking (e.g. can't be used to track offline usage).

Related

Browser service worker - Unchecked runtime.lastError: listener indicated an asynchronous response by returning true but the message channel closed

I am creating a push notification system for browsers.
What works...
If the corresponding website is loaded in the browser and if the browser is in the BACKGROUND the service worker correctly receives the information and pops up the message in the system tray. On clicking a button in that message, it brings the browser window back to focus and performs the action I want.
What doesn't work...
If the browser is closed OR the browser is open but the website is not loaded in any of the tabs, the service worker correctly receives the information and pops up the message in the system tray. However on clicking a button in that message, nothing happens :(
I tried to debug by closing the browser, sending the message, on receipt of message in system tray I manually loaded up the browser to the website and view console debug. When I then click the button in system try message nothing happened but I received the following message in the browser:
service worker message Unchecked runtime.lastError: A listener indicated an asynchronous response by returning true, but the message channel closed before a response was received
Could anyone give me any pointers. I've enclosed below the portion of service worker code that is called when a button on the system tray message is clicked...
//If a browser window is in background, and the domain of this project is loaded in to it,
//we force that window back in to the foreground (and re-display the message in the browser again via the postMessage function)
//However this isn't working if browser is closed at point of system tray message receipt...
function returnWindowToForeground(event,_action) {
event.waitUntil(
clients.matchAll(
{ type: 'window', includeUncontrolled: true }
)
.then(
(clientList) => {
var windowWithDomain = clientList.some((windowClient) => {
var found = -1;
let url = windowClient.url;
if( websiteurl == url.slice(0, websiteurl.length) ) {
found = i;
windowClient.focus(); //Go back to website in browser
windowClient.postMessage([currpayload,_action]); //Post message to browser window
}
return found;
});
//If not found, spawn a new browser tab, load url and post message
if (windowWithDomain== -1 && clients.openWindow)
{
clients.openWindow(websiteurl)
.then((windowClient) => (windowClient) ? windowClient.postMessage([currpayload,_action]) : null;
}
)
);
}
Thanks for your help, sorry it's a bit long.

Send SocketIO message on unload or upon confirmation onbeforeunload

I have a server application that performs firmware update on remote devices via radio.
Sometimes the update may continue like forever (if there is disturbance in the radio network). In that case the user may wish to interrupt the update by refreshing or leaving the page. In this case I have to:
Alert the user that he is about to interrupt the update (which is not recommended), and if the user is sure about his decision (and confirm):
Send a socketIO event to the server to inform that the update should be canceled.
I searched through the internet and came across to different solutions. The best of them are assigning handlers to the global object events onbeforeunload and onunload.
Using onbeforeunload I don't have choice. Can't send the SocketIO message only if the user confirm, and do nothing if the user decide to wait the update to finish. I can send the SocketIO message to the server, but what if the user decide to wait? The harm is already done.
Using onunload - it seems that doesn't work for me. I see that the socket event is being send by the browser, but before handled by the server the connection is closed.
Is there any way keep the SocketIO connection or delay the browser refresh so the server can handle the event?
I think that this is problem in the server because it runs on CPU with very limited resources, even CPU speed. It is ARM A7 architecture.
There is a way:
Server: Create a user id with:
var custom_id = 0;
io.engine.generateId = (req) => {
let id = custom_id++;
return id++; // very basic but unique id
}
Server: Create a listener to an attempt to close event and store the user id attempt:
var userAttempt = 0;
socket.on('attempt to close', function(data){
userAttempt = socket.id;
console.log(data)
})
Server: Modify disconnect event to check which id user is disconnected:
socket.on('disconnect', function(){
if(userAttempt === socket.id){
console.log("user attempt and close");
//the update should be canceled !!!!
}
console.log('user disconnected');
});
Client: Create the event emitter attempt to close inside onbeforeunload, this event is always going to be fired if user attempt to close or reload the page.
window.onbeforeunload = function (e) {
e.returnValue = " ";
var socket = io();
socket.emit('attempt to close', "user attempt to close");
};
If user try to close the tab, we fire the event attempt to close, we check if user close or not the tab checking the disconnect event. If user disconnected is the same as user attempted to close, the update should be cancelled.
What I did is place the socket event in
window.addEventListener('unload', function (e) {});
window.addEventListener('beforeunload', function (e) {
e.preventDefault();
e.returnValue = ' ';
});
window.addEventListener('unload', function (e) {
socket.emit('some event', {data to be sent});
});
it worked fine to accomplish the task
Although this doesn't fully answer the question, here are some observations I've made using Chrome Version 95.0.4638.54 (Official Build) (64-bit) and socket.io.
The window.beforeunload or window.unload events cause the socket to disconnect before you can send a message via socket.io that the window/tab is closing.
It's probably better to manage this on the server where you can detect the disconnection:(https://socket.io/docs/v4/server-api/)
socket.on("disconnect", (reason) => {
// reason = disconnect, server shutting down, ping timeout,
//transport close, transport error
console.log('disconnect:'+reason+ ' '+socket.id);
//then do your stuff
});
If you're managing the connections between user ids and socket ids on the server it's posssible to track who is doing what etc. I suspect that the answers given may have worked 3 yrs ago but Chrome has changed a lot and I always got a socket disconnect before I could use socket.emit.
Hopefully this will help.
Update 6-11-21
The latest version (v4.1.0 =>) has by default 'closeOnBeforeunload' as true
This will cause the socket to close before you can send a message.
You can of course set this to false.
(quote from docs):
"Whether to (silently) close the connection when the beforeunload event is emitted in the browser.
With closeOnBeforeunload set to false, a disconnect event will be emitted by the Socket instance when the user reloads the page on Firefox (but not on Chrome or Safari).
With closeOnBeforeunload set to true, all browsers will have the same behavior (no disconnect event when reloading the page). But this might cause issues if you use the beforeunload event in your application.
"
see: https://socket.io/docs/v4/client-options/#closeonbeforeunload

window.location.state does not work as expected

I have one situation,where i need to pass some json from one window to new window in the same domain.I have first window lets say it window1 and second window,let say it window2.
I have following code in window1:
var params = [
'height=750',
'width=720',
'scrollbars=yes',
'left=0',
'top=0',
'fullscreen=no', // only works in IE, but here for completeness
'location=no'
].join(',');
var port = location.port;
var url = "http://" + hostName + ':' + port + "/isecalreport" + location.search;
var newWindow = window.open(url,'photocal_report',params);
while(true){
if(newWindow! == undefined) {
newWindow.location.state={payloadFromIseCalWeekly : payloadFromIseCalWeekly,instrumentIdObj : instrumentIdObj};
break;
}
}
Code in window2:
var payloadFromIseCalWeekly = location.state.payloadFromIseCalWeekly ? location.state.payloadFromIseCalWeekly : {};
I want to make use of the json set in window.location.state.
So the problem is ,It works fine in chrome ,mozilla,but fails in IE 11(when debugger is not open.)
When i open debugger in IE11 ,it works fine.
I debugged it and found out that after the instruction which is opening the new window ,the next instruction get run and it doesnot find the new window object.
Its strange as it works when developer console is open.
It would be good if i can get insights about how to resolve the issue.
My aim is to open a new window ,to which i need to pass some data and using that data i want to do an API call.
With few exceptions, you cannot tell one window, tab or frame to talk to another directly. This is to prevent malicious scripts in one of these contexts from hijacking another across pages.
To cope with this, you have two options, you can use postMessage() or simply pass your data via the URL that you open in the new window. There are technically more options if you're on the same domain, but I recommend against going down that rabbit hole.
Sending data via the URL is a one-way affair. You can send query string variables in the URL that the new window can read, but it can't send anything back to the window that created it.
postMessage(), on the other hand, can be used to communicate between multiple contexts and across domains. It is considered secure because it requires that all participants be listening for messages, rather than allowing direct code access.
Your various pages can listen for messages with a simple event listener.
// listen for incoming messages on this page
window.addEventListener('message', function(e) {
// this is the handler function
// do we trust where this was sent from?
if (event.origin !== "http://example.com") {
// if so, print the resulting event object
console.log('message received', e);
}
}, false);
You can then send a message from another page to your window.
// the * is the targetOrigin, read the docs!
newWindow.postMessage("some message data", "*");

Closing HTML5 Web Sockets

HTML5 Web Sockets behave strangely if users are refreshing the page, or navigating
away from it. During reloading of the page, the socket connections between webserver and browser seem to stay open, and are closed if the page reloads in the browser, in both Firefox and Chrome. This means it works only every second time to establish a connection between browser and server, because the socket is closed by the browser on reload. The error message in the Firebug console from Firefox is "The connection to ws://.../websocket" was interrupted while the page was loading". So apparently the websocket connection is still open when the page is reloaded, which means the connection is closed during page load instead of opened every second page load. Log files read for instance like this (created with the Websocket-Rails gem)
==> first page load
[ConnectionManager] Connection opened: #<Connection::fef69428febee72f4830>
[Channel] #<Connection::fef69428febee72f4830> subscribed to channel xyz
==> second page load
[Channel] #<Connection::dfc4b33090b95826e08e> unsubscribed from channel xyz
[ConnectionManager] Connection closed: #<Connection::dfc4b33090b95826e08e>
Is there are way to close all open sockets and connections in the onbeforeunload Javascript event, something
like (in Coffeescript)..
window.onbeforeunload = () ->
close_all_sockets()
It is a bit tricky, but you can do it by creating a new WebSocket, and sending the identification of the user who should be disconnected through its protocol parameter.
For instance :
window.onbeforeunload = function () {
new WebSocket(myWebSocket.url, myUserName + "*" + myPassword);
}
The server must close the appropriate connection when it receives a new connection request with this non standard protocol.
This is what I've done in the handshake code of my server (C#) :
switch (handshakeKey)
{
// ..........
case "Sec-WebSocket-Protocol":
string[] infos = handshakeValue.Split('*');
if (infos.Length == 2)
{
Guest aguest = server.FindGuest(infos[0]);
if (aguest != null && aguest.password == infos[1]) server.RemoveGuest(aguest);
// The removeGuest function closes its socket }
TCPClient.Close(); // close the temporary connection
break;
// ........
}
Hope this helps.

Post method in $window.unload never called on change url, closing the tab, but ran on closing the browser

My unlock method never gets called on changing URL. Closing the browser executes the method. Closing the tab does not execute the method... I draw this conclusion since no post is received at the server-side (from examining the console).
/* Callback function that unlocks the current time report when leaving the angular app */
$window.onbeforeunload = function (event) {
event.preventDefault();
if ($scope.reportData != undefined && $scope.reportData.superId != undefined && !archive) {
$http.post(Settings.timereportBaseURLhttp + 'monthlyreport/' + $routeParams.office + '/current/' + $routeParams.tmsstep + '/' + $scope.reportData.superId + '/unlock');
}
};
First of all, unless $window refers to window, try removing the $. Secondly, event.preventDefault() won't prevent the browser from unloading the window. In most browsers, it will just ask the user if he wants to leave the page or not. If the user chooses not to stay on the page, and the AJAX call hasn't finished yet, the browser can close it immediately, and thus, you won't receive any POST request server-side.
To achieve what I think you're trying to do, I'd ping the server every once in a while and unlock the report if the server hasn't received a ping within a reasonable amount of time.

Categories