JavaScript: Close WebSocket when server goes offline - javascript

I'm using WebSockets to communicate between an ESP8266 (server, using the arduinoWebSockets library) and JavaScript running on a web page (client).
I'd like to notify the user when the connection between the ESP and the browser is lost.
To check if the connection still works, the client sends a "p" message every second, and the server just echoes it. If it the client doesn't receive this echo within a second, it closes the connection (WS.close();).
The WS.onclose event sets a timeout to establish a new connection.
This approach works, but the problem is that when the internet connection is lost, or when the server resets, WS.close() doesn't work as expected.
It just spends 60 seconds in the WebSocket.CLOSING state, and then times out, calling the onclose event one minute too late.
Is there a way to immediately disconnect the WebSocket connection when the server is offline?
let WS;
let WStimeout;
let pingInterval;
startWS();
function startWS() {
console.log("Start WebSocket");
WS = new WebSocket('ws://' + location.hostname + ':81/');
WS.onopen = function () {
pingInterval = setInterval(ping, 1000);
};
WS.onerror = function (err) {
console.log('WebSocket Error ', err);
};
WS.onclose = function (ev) {
console.log("WebSocket Closed ", ev);
clearInterval(pingInterval);
// ... let user know that the connection is lost
WS = null; // delete the current WebSocket
setTimeout(startWS, 5000); // try connecting to WebSocket server again in 5 seconds
}
WS.onmessage = function (ev) {
clearTimeout(WStimeout); // server is still online, clear "ping" timeout
console.log(ev.data);
// ... handle incoming data
}
}
function ping() {
if (!WS || WS.readyState !== WebSocket.OPEN) {
console.log("Connection not open: " + WS.readyState);
return;
}
WS.send("p"); // send ping to server
WStimeout = setTimeout(function () { // if it doesn't respond within a second
if (WS && WS.readyState === WebSocket.OPEN) { // and if the connection is still open
console.error("Ping timed out: closing WebSocket connection ...");
WS.close(); // close the connection
}
}, 1000);
}
Output:
Start WebSocket
p
...
p
* Turned WiFi off and on again *
Ping timed out: closing WebSocket connection ...
Connection not open: 2
... (60 times in total, for 1 minute)
Connection not open: 2
WebSocket Closed
CloseEvent {isTrusted: true, wasClean: false, code: 1006, reason: "", type: "close", …}
Start WebSocket
p
...

Related

Websocket won't reconnect unless I close the browser tab and restart it

I have a webserver with websockets set up on an ESP8266. The application runs fine on both client and server sides, sending and receiving data. However, if the server side disconnects (power cycle or upload new code), the client (Chrome) won't reconnect to the websocket. I can reload/refresh the web page, and it claims (according to the console log) to be connecting to the websocket, but it does not. The only solution I have found that works is to close the tab, and then restart a new session.
My code is heavily based on this tutorial from Random Nerd Tutorials
var gateway = `ws://${window.location.hostname}/ws`;
var websocket;
function initWebSocket() {
console.log('Trying to open a WebSocket connection...');
websocket = new WebSocket(gateway);
websocket.onopen = onOpen;
websocket.onclose = onClose;
websocket.onmessage = onMessage; // <-- add this line
}
function onOpen(event) {
console.log('Connection opened');
}
function onClose(event) {
console.log('Connection closed');
setTimeout(initWebSocket, 2000);
}
Is there something that is missing from the code above to make it more reliable?
You probably need to use setInterval. Try this, you may have to tweek it a bit.
var gateway = `ws://${window.location.hostname}/ws`;
var websocket, sockTimer=null;
function initWebSocket() {
console.log('Trying to open a WebSocket connection...');
websocket = new WebSocket(gateway);
websocket.onopen = onOpen;
websocket.onerror = onError; //  new
websocket.onclose = onClose;
websocket.onmessage = onMessage; // <-- add this line
}
function onOpen(event) {
clearInterval(sockTimer) // <= better
console.log('Connection opened');
}
function onError() { // <= New
sockTimer = setInterval(init, 1000 * 60);
};
function onClose(event) {
console.log('Connection closed');
//setTimeout(initWebSocket, 2000);
sockTimer = setInterval(initWebSocket, 1000 * 60); // <=new
}

How can I do Ping/Pong between JavaScript and NodeJS WebSocket?

I'm currently developing a NodeJS WebSocket server. To detect broken connections I've followed this guide here:
https://github.com/websockets/ws#how-to-detect-and-close-broken-connections
The server side works really good but the client makes problems because I can't find a ping function.
Does anyone has an idea how I can get the client part done without the library?
const WebSocket = require('ws');
function heartbeat() {
clearTimeout(this.pingTimeout);
// Use `WebSocket#terminate()`, which immediately destroys the connection,
// instead of `WebSocket#close()`, which waits for the close timer.
// Delay should be equal to the interval at which your server
// sends out pings plus a conservative assumption of the latency.
this.pingTimeout = setTimeout(() => {
this.terminate();
}, 30000 + 1000);
}
const client = new WebSocket('wss://echo.websocket.org/');
client.on('open', heartbeat);
client.on('ping', heartbeat);
client.on('close', function clear() {
clearTimeout(this.pingTimeout);
});
One main problem is that there is no ping method I think:
client.on('open') -> client.onopen available in JavaScript
client.on('close') -> client.onclose available in JavaScript
client.on('ping') -> How? Just how?
There is no Javascript API to send ping frames or receive pong frames. This is either supported by your browser, or not. There is also no API to enable, configure or detect whether the browser supports and is using ping/pong frames.
https://stackoverflow.com/a/10586583/7377682
Sad but true, in case of the ping frame, the API does not support it as mentioned in previous answers.
The most popular workaround is to listen to the close event and try to reconnect to the server using an interval.
This tutorial is easy to understand and contains most use-cases to begin with WS:
var ws = new WebSocket("ws://localhost:3000/ws");
let that = this; // cache the this
var connectInterval;
var check = () => {
const { ws } = this.state;
if (!ws || ws.readyState == WebSocket.CLOSED) this.connect(); //check if websocket instance is closed, if so call `connect` function.
};
// websocket onopen event listener
ws.onopen = () => {
console.log("connected websocket main component");
this.setState({ ws: ws });
that.timeout = 250; // reset timer to 250 on open of websocket connection
clearTimeout(connectInterval); // clear Interval on on open of websocket connection
};
// websocket onclose event listener
ws.onclose = e => {
console.log(
`Socket is closed. Reconnect will be attempted in ${Math.min(
10000 / 1000,
(that.timeout + that.timeout) / 1000
)} second.`,
e.reason
);
that.timeout = that.timeout + that.timeout; //increment retry interval
connectInterval = setTimeout(this.check, Math.min(10000, that.timeout)); //call check function after timeout
};
// websocket onerror event listener
ws.onerror = err => {
console.error(
"Socket encountered error: ",
err.message,
"Closing socket"
);
ws.close();
};
I think what you are look for on the client is onmessage:
client.onmessage = function (event) {
console.log(event.data);
}
All messages sent from the server can be listened to this way. See https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_client_applications

Check if WebSocket Server is running (on localhost)

When I try to initialize a websocket connection to the server running on localhost with
var webSocket = new WebSocket("ws://localhost:8025/myContextRoot");
in javascript, but the server hasn't completed starting up yet, I get the error
SCRIPT12029: WebSocket Error: Network Error 12029, A connection with the server could not be established
How can I prevent this? I.e. how do I check if the server has already started or how can I force the WebSocket client to wait for the server?
What about:
var webSocketFactory = {
connectionTries: 3,
connect: function(url) {
var ws = new WebSocket(url);
ws.addEventListener("error", e => {
// readyState === 3 is CLOSED
if (e.target.readyState === 3) {
this.connectionTries--;
if (this.connectionTries > 0) {
setTimeout(() => this.connect(url), 5000);
} else {
throw new Error("Maximum number of connection trials has been reached");
}
}
});
}
};
var webSocket = webSocketFactory.connect("ws://localhost:8025/myContextRoot");
When you get a connection error, you can do a limited number of trial-errors to try to re-connect. Or you can endlessly try to reach the server.
The accepted answer is perfectly fine. I just would like to extend it a little bit further with promises.
var wsFactory = { tryCount: 3,
connect : function(url){
var ctx = this,
ws = new WebSocket(url);
return new Promise(function(v,x){
ws.onerror = e => { console.log(`WS connection attempt ${4-ctx.tryCount} -> Unsuccessful`);
e.target.readyState === 3 && --ctx.tryCount;
if (ctx.tryCount > 0) setTimeout(() => v(ctx.connect(url)), 1000);
else x(new Error("3 unsuccessfull connection attempts"));
};
ws.onopen = e => { console.log(`WS connection Status: ${e.target.readyState}`);
v(ws);
};
ws.onmessage = m => console.log(m.data);
});
}
};
wsFactory.connect("ws://localhost:8025/myContextRoot")
.then(ws => ws.send("Hey..! This is my first socket message"))
.catch(console.log);
You can't prevent (or put on hold) the WebSocket from starting / establish a connection. WebSocket automatically establishes a connection with the server when its declared. What you can do is place all your code inside onopen event handler that you want to execute on successful connection. So it would be like...
var webSocket = new WebSocket("ws://localhost:8025/myContextRoot");
webSocket.onopen = function() {
// code you want to execute
};
check this article to know more about WebSocket.
Hence the protocol can't get queried by the server if it is not started, the only option is trial and error.
Or you could let the WebSocket server create a simple textfile with the timestamp of the startup in your web space directory where the javascript can retrieve it and than try to establish a connection. You can retrieve the textfile with XMLHttpRequest.

WebRTC: Connection swap displays black video

I'm trying to make a random system of connections. I have a button that initiates a connection and also looks for new peer for a new automatic call. But it is intermittent, sometimes it works perfect, sometimes I do not know anymore.
Backend - server.js
/** successful connection */
wss.on('connection', function (client) {
console.log("A new WebSocket client was connected.");
/** incomming message */
client.on('message', function (message) {
/** broadcast message to all clients */
var obj = JSON.parse(message);
if("callState" in obj) {
// New client, add it to the id/client object
// client.set('call_state') = 1
console.log("Recebeu mensagem!!!");
}else if("sdp" in obj || "ice" in obj) {
wss.broadcast(message, client);
}else{
console.log("Recebeu: "+message);
}
});
});
// broadcasting the message to all WebSocket clients.
wss.broadcast = function (data, exclude) {
console.log("Broadcasting message to all " + this.clients.length + " WebSocket clients.");
for(var i in this.clients) {
client = this.clients[i];
// don't send the message to the sender...
if (client === exclude) continue;
if (client.readyState === client.OPEN) client.send(data);
else console.error('Error: the client state is ' + client.readyState);
}
};
Frontend - webrtc.js
/** button START */
function start(isCaller) {
peerConnection = new RTCPeerConnection(peerConnectionConfig);
peerConnection.onicecandidate = gotIceCandidate;
peerConnection.addStream(localStream);
if ('ontrack' in peerConnection) {
// WebRTC Spec, Firefox
peerConnection.ontrack = ontrack
} else {
// Chrome, etc. This can be removed once all browsers support `ontrack`
peerConnection.onaddstream = gotRemoteStream
}
if(isCaller) {
peerConnection.createOffer().then(createdDescription).catch(errorHandler);
}
}
function gotMessageFromServer(message) {
if(!peerConnection) start(false);
var signal = JSON.parse(message.data);
// Ignore messages from ourself
if(signal.uuid == uuid) return;
if(signal.sdp) {
peerConnection.setRemoteDescription(new RTCSessionDescription(signal.sdp)).then(function() {
// Only create answers in response to offers
if(signal.sdp.type == 'offer') {
peerConnection.createAnswer().then(createdDescription).catch(errorHandler);
}
}).catch(errorHandler);
} else if(signal.ice) {
peerConnection.addIceCandidate(new RTCIceCandidate(signal.ice)).catch(errorHandler);
}
}
In the server log shows that every time I press the "Start" button it log 1 SDP message and 14 ICE messages
EDIT: Including error
When I call the "start" button for the first time, everything works. However, in the following calls sometimes only the audio feature remains and at other times without a new connection.
I was able to reproduce an error after clicking "Start" several times:
DOMException [InvalidStateError: "Cannot set remote answer in state
stable" code: 11 nsresult: 0x8053000b]
I have a local server running on: https://luisdemarchi.aplicativo.info:8091

node.js hanging tcp connecting attempt

When massive tcp connecting attempts from node.js javascript client to server while server is restarting, some connecting attempts are hanging. I wrote a simple script to reproduce it:
var net = require('net');
function Conn() {
var conn = new net.Socket({allowHalfOpen: false});
conn.setTimeout(1000);
conn.on('error', function (connectionException) {
console.log('TCP Repeater error: ' + connectionException);
this.connected = false;
});
conn.on('connect', function () {
console.log("connected");
this.connected = true;
});
conn.on('close', function () {
console.log("connection closed");
this.connected = false;
});
conn.on('timeout', function () {
console.log("connection timeout");
this.connected = false;
});
conn.connect(9997, "localhost");
}
for (var i=0;i<400;i++) {
new Conn();
}
Run this script against a starting tcp server. Some attempts are getting errors as server has been started, some attempts are connected after server is started. However, some attempts won't trigger any events and just hanging.
Is there anyway I can kill those hanging attempts? It looks connection timeout won't help as it's for inactivity of established connections. Is there any way to set connecting timeout like setSoTimeout in java?
Thanks

Categories