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.
Related
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.
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
I have recently developed a web app using PeerJS, and am trying to add reconnect functionality.
Basically, my app works by someone creating a server that clients then connect to. The server person can control what the hosts are doing but its basic two way communication.
If a client disconnects they simply reconnect and it works normally. However if the server user refreshes the page, or their computer crashes then they need to be able to re-establish control over the clients.
The start of this is by regaining the original connection ID and peer api ID, which is fine and easy as they are stored in a database and assigned a unique ID the server user can use to query them. Then to enable the client to reconnect I do this upon close:
// connection is closed by the host involuntarily...
conn.on('close', function() {
// if the clients connection closes set up a reconnect request loop - when the host takes back control
// the client will auto reconnect...
connected = false;
conn = null;
var reconnect_timer = setInterval(function () {
console.log('reconnecting...'); // make a fancy animation here...
conn = peer.connect(connectionid, {metadata: JSON.stringify({'type':'hello','username':username})});
// upon connection
conn.on('open', function() { // if this fails need to provide an error message... DO THIS SOON
// run the connect function...
connected = true;
connect(conn);
});
// didnt connect yet
conn.on('error', function(err) {
connected = false;
});
if(connected === true) {
clearInterval(reconnect_timer);
}
}, 1000);
});
This appears to work, as on the server end the client looks like they have reconnected - the connect function has fired etc. However messages cant be sent between, and the client console says:
Error: Connection is not open. You should listen for the `open` event before sending messages.(…)
Where the 'open' event is shown as having been listened to above...
I hope this is clear - any help is appreciated :)
So in the end to create an auto reconnect script, I simply dealt with the client end of things, ensuring the server was set to the same api_key (for cloudservers) and key:
peer = new Peer(return_array.host_id, {key: return_array.api_key});
and then having the client, upon connection closing:
// connection is closed by the host involuntarily...
conn.on('close', function() {
// if the clients connection closes set up a reconnect request loop - when the host takes back control
// the client will auto reconnect...
peer.destroy(); // destroy the link
connected = false; // set the connected flag to false
conn = null; // destroy the conn
peer = null; // destroy the peer
// set a variable which means function calls to launchPeer will not overlap
var run_next = true;
// periodically attempt to reconnect
reconnect_timer = setInterval(function() {
if(connected===false && run_next===true) {
run_next = false; // stop this bit rerunning before launchPeer has finished...
if(launchPeer(false)===true) {
clearInterval(reconnect_timer);
} else run_next == true;
}
}, 1000);
});
Where launch peer will attempt to launch a new peer. To ensure continuity the new id from the client replaces the old id from the client and everything is a smooth takeover. The hardest part in the end was having the "setInterval" only fire once which is achieved (badly...) through use of boolean flags.
Thanks to anybody who read and thought how they could help :)
I'm trying to send some data from Firefox to java desktop application .so my java class work as a server and Firefox script work as a client .when i test it using another java class which is client.java data successfully sent to server.java how ever when i use this firefox script to send data ,it actually connect to the server .but send("text"); doesn't work realtime .i mean sever shows received data when i close the socket socket.close(); . but i know there is no problem with server.java code.
this doesn't work
setTimeout(function(){socket.send("i'm firefox");},5000); // because socket isn't closed yet
this work
setTimeout(function(){socket.send("i'm firefox");},5000);
setTimeout(function(){socket.close();},6000);
but i really don't want to close the socket because i want to send lot of data one by one.
here is the complete code tested on scratchpad [-browser]
var tcpSocket = Cc["#mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
var socket = tcpSocket.open("127.0.0.1", 5000);
setTimeout(function(){socket.send("i'm firefox");},5000);
//setTimeout(function(){socket.close();},8000);// i'm firefox text retrieve by server when run this line / when close the socket.
i think java code isn't important.but here it is.
I'm asking why do i need to close the socket to send data ? and how can i send data without close the socket ?
update
i made a Gif to show my problem here you can see data not sending real time but when socket is closed all the data flushed .
It's actually working. Your data is being received, but you're currently waiting for a new line to print your received messages:
while ((inputLine = in.readLine()) != null) {
System.out.println("Server: " + inputLine);
out.println(inputLine);
if (inputLine.equals("Bye.")) {
break;
}
}
Which currently only happens when you close the socket. Just add a new line at the end of your messages and it will work as expected:
var tcpSocket = Cc["#mozilla.org/tcp-socket;1"].createInstance(Ci.nsIDOMTCPSocket);
var socket = tcpSocket.open("127.0.0.1", 5000);
function sendMessage(msg){
socket.send(msg + "\n");
}
setTimeout(function(){
sendMessage("hi mark");
},3000);
setTimeout(function(){
sendMessage("hi johnny");
},5000);
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).