How do I catch a WebSocket connection interruption? - javascript

In Firefox (at least), if you hit ESC, then it will close all open WebSockets connections.
I need to capture that disconnection and try to re-connect once it's available again.
Here's an example of the code I've tried to implement, but nothing I can figure out will catch the error and allow me to handle it gracefully.
Have a look at the code: http://jsfiddle.net/w5aAK/
var url = "ws://echo.websocket.org";
try {
socket = window['MozWebSocket'] ? new MozWebSocket(url) : new WebSocket(url);
socket.onopen = function(){
console.log('Socket is now open.');
};
socket.onerror = function (error) {
console.error('There was an un-identified Web Socket error');
};
socket.onmessage = function (message) {
console.info("Message: %o", message.data);
};
} catch (e) {
console.error('Sorry, the web socket at "%s" is un-available', url);
}
setTimeout(function(){
socket.send("Hello World");
}, 1000);
Turn on your console and watch the output.
Am I doing something wrong here, or is it just not possible because the connection is running outside of the scope of the JS script?
Any input would be helpful.
Thanks!

You can attach a handler to the socket.onclose event. It will be called when you hit ESC and the connection is interrupted.
See: http://jsfiddle.net/w5aAK/1/
One problem that you can't get around at the moment is the interrupted error being output to the console. There's no way of capturing that at the moment I'm afraid.

You can't catch it and it's not your fault. It's FireFox bug. Vote for it here:
https://bugzilla.mozilla.org/show_bug.cgi?id=712329
I personally tried all kind of solutions:
event handlers onunload onbeforeunload onclose try..catch some js error handling 3rd party services etc.
You can log to console your socket, it's closed before unload, but FF thinks different.. :(
Solution (not answer directly to the answer, but it works):
It's a bug, so you can't catch, but this info is not Solution. After all kind of crazy workarounds and tries to catch that bug, i finally found this working. If you use socket.io to work with WebScokets it can work with different transport technologies. xhr-polling works with Firefox.
if (/Firefox[\/\s](\d+\.\d+)/.test(navigator.userAgent)) { //test for Firefox/x.x or Firefox x.x (ignoring remaining digits);
socket = io.connect('//' + node_server + '/', {
transports: ['polling']
});
} else {
socket = io.connect('//' + node_server + '/');
}
What helped me - might help you too:
Web Socket support in Node.js/Socket.io for older browser
Define transport types on the client side
socket.io doens't work with transports: [ 'xhr-polling' ]

Related

Inspecting multiple WebSocket connections at the same time

I am using solutions provided in following topics to inspect WebSockets traffic (messages) on the web page, which I do not own (solely for learning purposes):
Inspecting WebSocket frames in an undetectable way
Listening to a WebSocket connection through prototypes
https://gist.github.com/maskit/2252422
Like this:
(function(){
var ws = window.WebSocket;
window.WebSocket = function (a, b, c) {
var that = c ? new ws(a, b, c) : b ? new ws(a, b) : new ws(a);
that.addEventListener('open', console.info.bind(console, 'socket open'));
that.addEventListener('close', console.info.bind(console, 'socket close'));
that.addEventListener('message', console.info.bind(console, 'socket msg'));
return that;
};
window.WebSocket.prototype=ws.prototype;
}());
The issue with the provided solutions is that they are listening on only 1 of 3 WebSocket connections ("wss://..."). I am able to see in the console the messages that I receive or send, but only for one connection.. Is there something I am missing? Is it possible that two other service are any different and prohibiting the use of prototype extension technique?
p.s. I will not provide an URL to the web resource that I am doing my tests on, in order to avoid possible bans or legal questions.
Okay, since it's been weeks and no answers, then I will post a solution which I ended up using.
I have built my own Chrome extension that listens to WebSocket connections and forwards all requests and responses to my own WebSocket server (which I happen to run in C#).
There are some limitations to this approach. You are not seeing the request header or who is sending the packets.. You are only able to see the payload and that is it. Also you are not able to modify the contents in any way or send your own requests (remember - you have no access to header metadata). Naturally, another limitation is that you have to be running Chrome (devtools APIs are used)..
Some instructions.
Here is how you attach debugger to listen to network packets:
chrome.debugger.attach({ tabId: tabId }, "1.2", function () {
chrome.debugger.sendCommand({ tabId: tabId }, "Network.enable");
chrome.debugger.onEvent.addListener(onTabDebuggerEvent);
});
Here is how you catch them:
function onTabDebuggerEvent(debuggeeId, message, params) {
var debugeeTabId = debuggeeId.tabId;
chrome.tabs.get(debugeeTabId, function (targetTab) {
var tabUrl = targetTab.url;
if (message == "Network.webSocketFrameSent") {
}
else if (message == "Network.webSocketFrameReceived") {
var payloadData = params.response.payloadData;
var request = {
source: tabUrl,
payload: params.response.payloadData
};
websocket.send(JSON.stringify(request));
}
});
}
Here is how you create a websocket client:
var websocket = new WebSocket("ws://127.0.0.1:13529");
setTimeout(() => {
if (websocket.readyState !== 1) {
console.log("Unable to connect to a WebsocketServer.");
websocket = null;
}
else {
console.log("WebsocketConnection started", websocket);
websocket.onclose = function (evt) {
console.log("WebSocket connection got closed!");
if (evt.code == 3001) {
console.log('ws closed');
} else {
console.log('ws connection error');
}
websocket = null;
};
websocket.onerror = function (evt) {
console.log('ws normal error: ' + evt.type);
websocket = null;
};
}
}, 3000);
Creating the server is outside the scope of this question. You can use one in Node.js, C# or Java, whatever is preferable for you..
This is certainly not the most convenient approach, but unlike java-script injection method - it works in all cases.
Edit: totally forgot to mention. There seems to be another way of solving this, BUT I have not dig into that topic therefore maybe this is false info in some way. It should be possible to catch packets on a network interface level, through packet sniffing utilities. Such as Wireshark or pcap. Maybe something I will investigate further in the future :)

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

Node websockets/ws - WSS: How to tell if I closed the connection, or they did?

The setup
I am using the websockets/ws library to listen to a WSS feed. It works well, it's lightweight enough and it seems to be one of the fastest around (which is important).
I'm trying to differentiate between me programmatically closing the connection, and them closing it for whatever reason.
According to the docs, I can send a code (int) and a reason (string), both of which are sent to the on close event. But by all accounts, this functionality no longer exists.
Tests
Most codes throw a First argument must be a valid error code number error
Leaving it blank sends code = 1005 and an empty reason to the event
If I enter a code of 1005, I get the invalid error
If I enter a code of 1000, the event receives code = 1006 and still an empty reason (regardless of what I put)
^ tests are simple enough...
var WebSocket = require('ws');
var ws = new WebSocket(url);
ws.on('close', function(code, reason) {
console.log(code);
console.log(reason);
});
var code = 1234,
reason = 'whatever reason';
setTimeout(function() {
ws.close(code, reason);
}, 5000);
But...
I need to be able to tell if I've closed the connection, or if it was closed for another reason (connection lost, they closed it because of time limits, etc). Depending on the reason it was closed, I sometimes need to immediately reopen the connection, but only sometimes.
I can do something like...
_initWS(url) {
var ws = new WebSocket(url);
ws.on('open', function() {...});
ws.on('close', function(code, reason) {
ws = null; // Don't know if needed
ws = _initWS(url); // Reopen the connection, for ever and ever...
});
ws.on('message', funciton(msg) {...});
return ws;
}
var someFeed = _initWS(someURL);
... but since the code and reason are all but meaningless, this automatically restarts the connection regardless of why it was closed. This is great if the connection was lost or timed-out, but not so great if I want to close it programmatically...
someFeed.close(); // Connection immediately reopens, always and forever
Question(s)
How can I differentiate between different closes? I don't want to change the library code, because then I can't use npm install when I move my own code around. Can I override the close method within my own code?
Or is there an equally lightweight and lightning-fast library that has the functionality I'm looking for? I just need to be able to reliably send a unique code and/or reason, so I know when I'm trying to close it manually. I know that asking for recommendations is taboo, but there are too many libraries to test each one, and no benchmarks that I can find.
From the suggestion from #Bergi
_initWS(url) {
var ws = new WebSocket(url);
ws.didIClose = false; // This is the flag to set if I close it manually
ws.forceClose = function() {
ws.didIClose = true;
ws.close();
};
ws.on('open', function() {...});
ws.on('close', function(code, reason) {
if(!ws.didIClose) { // If I didn't close it, reopen
ws = null;
ws = _initWS(url);
}
});
ws.on('message', funciton(msg) {...});
return ws;
}
var someFeed = _initWS(someURL);
And then to use it...
someFeed.didIClose = true;
someFeed.close(); // Won't reopen the connection
Or, a bit cleaner...
someFeed.forceClose();
Edit #1
Modified solution to include the forceClose() method - requiring only one clean line of code to programmatically close it (instead of two).

Immediate Disconnection using websocket in FireFox Addon

I've been working on browser extensions that interact with a local application running a WebSocket server.
Safari and Chrome Extensions were very easy to implement, and after some headache getting a feel for FF development, I thought I would be able to implement WebSockets as I had in the other browsers. However I have had some issues.
I understand that I can't directly create a WebSocket in the "main" js file, and so attempted to use workarounds I found on the internet:
https://github.com/canuckistani/Jetpack-Websocket-Example uses a page-worker as a sort of proxy between main and the WebSocket code. When I implement this code, my WebSocket connection immediately errors w/ {"isTrusted":true} as the only information.
I also tried to use a hiddenframe as it appears this is how 1Password deals with websocket communication in their FF Addon, but this also results in the same immediate error.
When I simply open a websocket connection to my server in my normal FF instance, it connects perfectly, but so far, I haven't gotten anything to work from addon.
making pageWorker with:
var pw = pageWorker.Page({
contentUrl: self.data.url('com.html'),
contentScriptFile: self.data.url('com.js')
})
com.html:
<!DOCTYPE HTML>
<html>
<body>
</body>
</html>
com.js:
document.onready = launchCom();
// Could this need to be on ready?
function launchCom() {
console.log("[com.js] launchCom Called");
var wsAvailable = false;
if ("WebSocket" in window) {
console.log("[com.js] Detected Websocket in Window, attempting to open...");
// WebSocket is supported.
ws = new WebSocket('ws://localhost:9001');
wsAvailable = true;
} else {
console.log("[com.js] Websocket is not supported, upgrade your browser!");
}
}
ws.onmessage = function(event) {
console.log(event.data);
}
ws.onopen = function(evt) {
console.log("[com.js] ws opened. evt: " + evt);
}
ws.onerror = function(evt) {
console.log("[com.js] ws error: " + JSON.stringify(evt));
}
Running this results in:
console.log: xxx: [com.js] launchCom Called
console.log: xxx: [com.js] Detected Websocket in Window, attempting to open...
console.log: xxx: [com.js] ws error: {"isTrusted":true}
console.log: xxx: [com.js] ws closed. evt: {"isTrusted":true}
Any help would be greatly appreciated!
I've solved the problem:
I'm using https://github.com/zwopple/PocketSocket in my OS X application as my server, and there appears to be an issue with PocketSocket and FF.
After changing PocketSocket's PSWebSocketDriver.m line 87 code from
[[headers[#"Connection"] lowercaseString] isEqualToString:#"upgrade"]
to
[[headers[#"Connection"] lowercaseString] containsString:#"upgrade"]
per https://github.com/zwopple/PocketSocket/issues/34,
I was able to open a WebSocket connection from FF addon using the original code, but the server errored on messages.
Setting network.websocket.extensions.permessage-deflate to false in about:config allowed messages to be sent so I added
require("sdk/preferences/service").set("network.websocket.extensions.permessage-deflate", false);
to my main.js and everthing is working!
The tiny change to PocketSocket's code hasn't had any effects on the server interacting with other WebSocket clients.
I also got stuck in similar situation as websocket can't be implemented directly in main.js. I also did the same as you did , may be server is refusing connection. Snippet from my code look like below :
main.js
var wsWorker = require('sdk/page-worker').Page({
contentURL: "./firefoxScript/webSocket.html",
contentScriptFile : ["./firefoxScript/webSocket.js"]
});
webSocket.html
<html>
<head>
<title></title>
</head>
<body>
</body>
</html>
webSocket.js
var ws = new WebSocket('ws://localhost:9451');
ws.onopen = function() {
console.log('Connection open...');
};
ws.onclose = function() {
console.log('Connection closed...');
};
ws.onmessage = function(event) {
console.log('Message recieved...');
};
ws.onerror = function(event) {
console.log('Connection Error...');
};
It's perfectly working fine for me.

try catch doesn't work with sse

This is the code, P.S: the service httpd in s10 is stopped
try {
var source = new EventSource("http://s10/server.php");
console.log(source);
} catch (e) {
console.log("ADSfasfasfasdfasdfas" + e)
}
this is the console:
why the heck the try catch is not catching the error ??
ofcourse I have onerror event and onclose event:
source.addEventListener('error', function(e) {
if (source.readyState == 2) {
connectionClosed();//to change some style
console.log("Disconnected");
}
}, false);
source.onerror = function(e) {
if (source.readyState != 0) {
connectionClosed();//to change some style
console.log("Disconnected");
}
};
source.onclose = function() {
connectionClosed();//to change some style
console.log('Connection closed');
}
When I run this in the Console on an arbitrary URL that isn't set up to handle SourceEvents, the Console successfully logs a SourceEvent instance. This is similar to what you're describing.
So my guess here is that there's no error to catch. The SourceEvent instance is successfully constructed, even though there's no actual connection. If you want to detect if the connection is working, use the EventSource.readyState property.
In my screenshot you can see that readyState = 0, which means that the connection is "connecting", but in reality it's never going to finish connecting because the server on the other side isn't set up to handle SourceEvents.
https://developer.mozilla.org/en-US/docs/Web/API/EventSource/readyState
I'm testing the same thing, the onerror doesn't provide really useful info. For example, if you shut down your server or if you send an empty response while your client is connected, you can't detect programmatically on the client side the specific cause, I can see that just inspecting the dev console.

Categories