Failed to execute 'send' on 'WebSocket': Still in CONNECTING state - javascript

I am new in react js development and try to integrate WebSocket un my app.
but got an error when I send messages during connection.
my code is
const url = `${wsApi}/ws/chat/${localStorage.getItem("sID")}/${id}/`;
const ws = new WebSocket(url);
ws.onopen = (e) => {
console.log("connect");
};
ws.onmessage = (e) => {
const msgRes = JSON.parse(e.data);
setTextMessage(msgRes.type);
// if (msgRes.success === true) {
// setApiMessagesResponse(msgRes);
// }
console.log(msgRes);
};
// apiMessagesList.push(apiMessagesResponse);
// console.log("message response", apiMessagesResponse);
ws.onclose = (e) => {
console.log("disconnect");
};
ws.onerror = (e) => {
console.log("error");
};
const handleSend = () => {
console.log(message);
ws.send(message);
};
and got this error
Failed to execute 'send' on 'WebSocket': Still in CONNECTING state

Sounds like you're calling ws.send before the socket has completed the connection process. You need to wait for the open event/callback, or check the readyState per docs and queue the send after the readyState changes i.e after the open callback has fired.
Not suggesting you do this, but it might help:
const handleSend = () => {
if (ws.readyState === WebSocket.OPEN) {
ws.send()
} else {
// Queue a retry
setTimeout(() => { handleSend() }, 1000)
}
};
As Logan has mentioned my first example is lazy. I just wanted to get OP unblocked and I trusted readers were intelligent enough to understand how to take it from there. So, make sure to handle the available states appropriately, e.g if readyState is WebSocket.CONNECTING then register a listener:
const handleSend = () => {
if (ws.readyState === WebSocket.OPEN) {
ws.send()
} else if (ws.readyState == WebSocket.CONNECTING) {
// Wait for the open event, maybe do something with promises
// depending on your use case. I believe in you developer!
ws.addEventListener('open', () => handleSend())
} else {
// etc.
}
};

I guess you can only send data with ws only if it's already open, and you do not check when it's open or not.
Basically you ask for an openning but you send a message before the server said it was open (it's not instant and you do not know how many time it can take ;) )
I think you should add a variable somithing like let open = false;
and rewrite the onopen
ws.onopen = (e) => {
open = true;
console.log("connect");
};
and then in your logic you can only send a message if open is equal to true
don't forget the error handling ;)

Related

Request video during PeerJs ongoing live connection (stream)

I am new to PeerJs and recently starting developing an app for my school during this Covid pandemic.
I have been able to deploy code to NodeJs server with express and was able to establish connection between 2 users.
But the problem arises when video is turned off from the beginning of stream for both users and a user wants to initiate a video call.
What I need is, to send some kind of notification to user 2 that user 1 is requesting for video. So that user 2 will turn on video.
My existing code is:
var url = new URL(window.location.href);
var disableStreamInBeginning = url.searchParams.get("disableStreamInBeginning"); // To disable video in the beginning
var passwordProtectedRoom = url.searchParams.get("passwordProtectedRoom");
var muteAllInBeginning = url.searchParams.get("muteAllInBeginning");
const socket = io('/')
const localVideoDiv = document.getElementById('local-video-div')
const oneOnOneSelf = document.getElementById('local-video')
const oneOnOneRemote = document.getElementById('remote-video')
if(typeof disableStreamInBeginning !== 'undefined' && disableStreamInBeginning == 'true'){
var disbaleSelfStream = true
} else {
var disbaleSelfStream = false
}
if(typeof passwordProtectedRoom !== 'undefined' && passwordProtectedRoom == 'true'){
var passwordProtected = true
} else {
var passwordProtected = false
}
if(typeof muteAllInBeginning !== 'undefined' && muteAllInBeginning == 'true'){
var muteAll = true
} else {
var muteAll = false
}
var systemStream
oneOnOneSelf.style.opacity = 0
oneOnOneRemote.style.opacity = 0
const myPeer = new Peer(undefined, {
host: '/',
port: '443',
path: '/myapp',
secure: true
})
const ownVideoView = document.createElement('video')
const peers = {}
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(ownStream => {
systemStream = ownStream
addVideoStream(ownStream, oneOnOneSelf)
myPeer.on('call', call => {
call.answer(ownStream)
call.on('stream', remoteStream => {
addVideoStream(remoteStream, oneOnOneRemote)
})
})
socket.on('user-connected', userId => {
//connectToNewUser(userId, stream)
setTimeout(connectToNewUser, 1000, userId, ownStream)
})
})
socket.on('user-disconnected', userId => {
if (peers[userId]) peers[userId].close()
})
myPeer.on('open', id => {
//Android.onPeerConnected();
socket.emit('join-room', ROOM_ID, id)
})
function connectToNewUser(userId, stream) {
const call = myPeer.call(userId, stream)
call.on('stream', remoteStream => {
//console.log('Testing')
addVideoStream(remoteStream, oneOnOneRemote)
})
call.on('close', () => {
oneOnOneRemote.remove()
})
peers[userId] = call
}
function addVideoStream(stream, videoView) {
videoView.srcObject = stream
videoView.addEventListener('loadedmetadata', () => {
if(disbaleSelfStream){
audioVideo(true)
} else {
localVideoDiv.style.opacity = 0
videoView.style.opacity = 1
videoView.play()
}
})
}
function audioVideo(bool) {
if(bool == true){
localVideoDiv.style.opacity = 1
oneOnOneSelf.style.opacity = 0
systemStream.getVideoTracks()[0].enabled = false
} else {
if(disbaleSelfStream){
console.log('Waiting For Another User To Accept') // Here is need to inform user 2 to tun on video call
} else {
localVideoDiv.style.opacity = 0
oneOnOneSelf.style.opacity = 1
systemStream.getVideoTracks()[0].enabled = true
}
}
}
function muteUnmute(bool) {
if(bool == true){
systemStream.getAudioTracks()[0].enabled = true
} else {
systemStream.getAudioTracks()[0].enabled = false
}
}
function remoteVideoClick(){
alert('Hi');
}
Please help.
You can send messages back and forth directly using peer itself
const dataConnection = peer.connect(id) will connect you to the remote peer, it returns a dataConnection class instance that you can later use with the send method of that class.
Just remember that you also want to setup listener on the other side to listen for this events, like "open" to know when the data channel is open:
dataConnection.on('open', and dataConnection.on('data...
You have a bug in your code above, I know you didn't ask about it, it is hard to see and not always will manifest. The problem will occur when your originator sends a call before the destination has had time to receive the promise back with its local video/audio stream. The solution is to invert the order of the calls and to start by setting up the event handler for peer.on("call", ... rather than by starting by waiting for a promise to return when we ask for the video stream. The failure mode will depend on how long does it take for your destination client to signal it wants and call to the originator plus how long it takes for the originator to respond versus how long it takes for the stream promise to return on the destination client. You can see a complete working example, where messages are also sent back and forth here.
// Function to obtain stream and then await until after it is obtained to go into video chat call and answer code. Critical to start the event listener ahead of everything to ensure not to miss an incoming call.
peer.on("call", async (call) => {
let stream = null;
console.log('*** "call" event received, calling call.answer(strem)');
// Obtain the stream object
try {
stream = await navigator.mediaDevices.getUserMedia(
{
audio: true,
video: true,
});
// Set up event listener for a peer media call -- peer.call, returns a mediaConnection that I name call
// Answer the call by sending this clients video stream --myVideo-- to calling remote user
call.answer(stream);
// Create new DOM element to place the remote user video when it comes
const video = document.createElement('video');
// Set up event listener for a stream coming from the remote user in response to this client answering its call
call.on("stream", (userVideoStream) => {
console.log('***"stream" event received, calling addVideoStream(UserVideoStream)');
// Add remote user video stream to this client's active videos in the DOM
addVideoStream(video, userVideoStream);
});
} catch (err) {
/* handle the error */
console.log('*** ERROR returning the stream: ' + err);
};
});

Synchronous closing websocket in browser

I have a web app using WebSocket to connect the server and performing an action. After the action finished, the connection is going to be closed automatically.
But the user can restart the action by pressing a button, which close connection then create a new connection.
Example code when user restart the action:
if (this.connection) {
this.connection.close()
// this.connection = null
}
if (!this.connection) {
this.connection = new WebSocket(serverSocketURL)
// Other logic codes here
this.connection.onclose = () => {
this.connection = null
}
}
The problem is close() method is async so the second block code run before the connection is closed.
How to synchronous closing WebSocket connection?
Should I use setTimeout to wait a small time after call close() method?
Perhaps this will do what you want
When the connection is "re-connected" by the user, a second close listener is added to make a new connection - as this listener is added after the one that sets this.connection = null, it will be called after that is run, so there's no chance of a race condition
const makeConnection = () => {
this.connection = new WebSocket(serverSocketURL);
// Other logic codes here
this.connection.addEventListener('close', () => {
this.connection = null
});
};
if (this.connection) {
this.connection.addEventListener('close', makeConnection);
this.connection.close();
} else {
makeConnection();
}
or - using onclose instead of addEventListener('close',
const makeConnection = () => {
this.connection = new WebSocket(serverSocketURL);
// Other logic codes here
this.connection.onclose = () => {
this.connection = null
};
};
if (this.connection) {
this.connection.onclose = makeConnection;
this.connection.close();
} else {
makeConnection();
}

RTCPeerConnection connectionState never moves from "new" to "checking"/"connected"

I have taken over a WebRTC project from someone and though I'm starting to wrap my head around the concepts, I continue to be stumped by a specific problem: getting the WebRTC connection to move from new to checking/completed, etc...
Here is the extent of the output from chrome://webrtc-internals:
Our code calls connect():
connect(mediaStream, interviewUid, applicantUid) {
return new Promise((resolve, reject) => {
this.connectRtcPeerConnection()
.then(() => {
this.connectionState = Socket.CONNECTION_STATES.connected;
this.rtcPeer.addStream(mediaStream);
return this.rtcPeer.createOffer({ offerToReceiveAudio: 1, offerToReceiveVideo: 1 });
}).then((offer) => {
console.log('offer created', offer);
return this.rtcPeer.setLocalDescription(offer);
}).then(() => {
const message = {
id: SENDABLE_MESSAGES.connect,
sdpOffer: this.rtcPeer.localDescription,
interviewUid,
applicantUid,
};
this.sendMessageToServer(message);
resolve();
})
.catch((error) => {
console.error(error);
reject();
});
});
}
which in turns calls connectRtcPeerConnection():
connectRtcPeerConnection(
) {
return new Promise((resolve, reject) => {
if (this.rtcPeer) {
resolve();
}
console.log('started connecting');
const rtcPeerOptions = {
iceServers: [TRUNCATED],
};
console.log('rtcPeerOptions', rtcPeerOptions);
this.rtcPeer = new RTCPeerConnection(rtcPeerOptions);
console.log('rtcPeer object: ', this.rtcPeer);
this.rtcPeer.onerror = reject;
this.rtcPeer.onicecandidate = (candidate) => { this.handleIceCandidateEvent(candidate); };
this.rtcPeer.oniceconnectionstatechange = () => {
this.handleIceConnectionStateChangeEvent();
};
this.rtcPeer.onaddstream = () => { console.log('handleAddStreamEvent'); };
this.rtcPeer.onremovestream = () => { console.log('handleRemoveStreamEvent'); };
this.rtcPeer.onicegatheringstatechange = () => { console.log('handleIceGatheringStateChangeEvent'); };
this.rtcPeer.onsignalingstatechange = () => { console.log('handleSignalingStateChangeEvent'); };
this.rtcPeer.onnegotiationneeded = () => { console.log('handleNegotiationNeededEvent'); };
resolve();
});
}
This chunk of code never gets executed:
this.rtcPeer.oniceconnectionstatechange = () => {
this.handleIceConnectionStateChangeEvent();
};
I've followed every conditional and code path and don't currently see what the issue might be. Has anyone encountered this and is able to shed some light on potential things to look at/consider?
Thanks!
When I was implementing Kurento library for iOS, tried something like this:
Generated SDPOffer
Set LocalDescription at our end
WebRTC started generating IceCandidate
Sent Ice Candidate through WebSocket
At this point, other party sent SDPAnswer.
Processed SDPAnswer at our end.
Set RemoteDescription at our end.
Server started sending IceCandidate gathered at their end.
Added these IceCandidate in array at our end.
Here received change in connection state to "Checking"
Received RemoteStream at this point.
Here received change in connection state to "Connected"
Hope it helps!
well, you never call setRemoteDescription or add a remote ice candidate via addIceCandidate. Without that there is no-one to talk to

Local WebRTC connection stuck on "checking" when offline

I've been trying to get a hermedic development environment such that things work locally e.g. on one computer while on the train with no internet. I've created this minimal "Hello World" page which tries to create a WebRTC connection between two things in the same page (the "creator" and "joiner"). This way the signalling server is stubbed out and the steps can be shown in one synchronous log. However I'm not not getting the callbacks that I expect when my computer is offline.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Offline WebRTC</title>
<style>
html, body {padding:0;margin:0;height:100%}
body {box-sizing:border-box;padding:50px 0 0px;color:#ccc;background-color:#303030;}
h1 {position:fixed;margin:0;line-height:50px;padding:0 15px;top:0;left:0;font-size:18px;width:100%;box-sizing:border-box}
</style>
</head>
<body>
<h1>Why does this WebRTC work online but not offline?</h1>
<pre id="log"></pre>
<script type="text/javascript">
//
// Gobals
//
// This is the interface through which the Creator and Joiner communicate.
// Usually this would involve piping through the server via websockets.
const signallingServer = {
giveOfferToJoiner: null, // initialized in the "create" section
giveAnswerToCreator: null, // initialized in the "join" section
};
let logCounter = 0;
function logWithIndent(message, indent) {
const prefix = ''.padStart(indent, ' ') + (''+logCounter).padStart(4, '0') + ' ';
logCounter += 1;
document.getElementById('log').textContent += prefix + message + '\n';
const panes = [
document.getElementById('join-pane'),
document.getElementById('create-pane'),
];
}
//
// Join (right column)
//
(() => {
const log = (message) => logWithIndent(message, 50);
const pc = new RTCPeerConnection(null);
const sdpConstraints = { optional: [{RtpDataChannels: true}] };
signallingServer.giveOfferToJoiner = (offerString) => {
log('Received offer');
const offerDesc = new RTCSessionDescription(JSON.parse(offerString));
pc.setRemoteDescription(offerDesc);
pc.createAnswer(
(answerDesc) => {
log('Setting peer connection description')
pc.setLocalDescription(answerDesc);
},
() => { log("ERROR: Couldn't create answer"); },
sdpConstraints
);
};
pc.ondatachannel = (e) => {
const dataChannel = e.channel;
const sendMessage = (message) => {
log(`Sending message: ${message}`);
dataChannel.send(message);
};
dataChannel.onopen = () => { log("Data channel open!"); };
dataChannel.onmessage = (e) => {
const message = e.data
log("Received message: " + message);
sendMessage('PONG: ' + message)
}
};
pc.onicecandidate = (e) => {
if (e.candidate) {
log('waiting for null candidate for answer');
return;
}
const answer = JSON.stringify(pc.localDescription);
log('Answer created. Sending to creator');
signallingServer.giveAnswerToCreator(answer);
log('waiting for connection...')
};
pc.oniceconnectionstatechange = (e) => {
const state = pc.iceConnectionState;
log(`iceConnectionState changed to "${state}"`)
if (state == "connected") {
log('TODO: send message');
}
};
log(`Waiting for offer`);
})();
//
// Create (left)
//
(() => {
const log = (message) => logWithIndent(message, 0);
const pc = new RTCPeerConnection(null);
let dataChannel = null;
const sendMessage = (message) => {
log(`Sending message: ${message}`);
dataChannel.send(message);
};
signallingServer.giveAnswerToCreator = (answerString) => {
var answerDesc = new RTCSessionDescription(JSON.parse(answerString));
log('Setting peer connection description')
pc.setRemoteDescription(answerDesc);
};
pc.oniceconnectionstatechange = (e) => {
const state = pc.iceConnectionState;
log(`iceConnectionState changed to "${state}"`)
};
pc.onicecandidate = (e) => {
if (e.candidate) {
log(`Waiting for null candidate for offer`);
return;
}
const offer = JSON.stringify(pc.localDescription);
log(`Offer created. Sending to joiner`);
signallingServer.giveOfferToJoiner(offer);
log(`waiting for answer...`);
}
function createOffer() {
dataChannel = pc.createDataChannel("chat");
dataChannel.onopen = () => { log("Data channel open!"); sendMessage('Hello World!')};
dataChannel.onmessage = (e) => { log("Received message: " + e.data); }
log('Creating offer...');
pc.createOffer().then((e) => {
log('setting local description');
pc.setLocalDescription(e);
});
};
createOffer();
})();
</script>
</body>
</html>
To reproduce:
While connected to the internet, open this .html file locally (should have a file://... URL, no need for a server)
Observe that it is working properly (should get toPONG: Hello World!)
Disconnect your computer from the internet
Refresh the page
Observe that it does not proceed after iceConnectionState changed to "checking"
Additional info:
Disconnecting my computer from the internet has a different effect on this compared to the "offline" checkbox in the network tab of the chrome devtools. Checking this checkbox has no effect on whether a connection can be established.
So my main question is: How can I open a local WebRTC connection when my computer is offline?
Additional questions: I assume that the browser is trying to talk to someone in the background as part of the check or connect step. Who is it trying to talk to? Why are these requests not showing up in the network tab of the devtools?
WebRTC gathers candidates from your local network interfaces as part of the ICE process.
From looking at the SDP (either in the debugger or on chrome://webrtc-interals), when offline there no interface (other than the loopback interface which is ignored) to gather candidates from, there is no candidate in onicecandidate and you just send an offer without any candidates.
Going into 'checking' ICE connection state seems like a bug, https://w3c.github.io/webrtc-pc/#rtcicetransportstate requires a remote candidate for that.

How can I check if port is busy in NodeJS?

How can I check if port is busy for localhost?
Is there any standard algorithm? I am thinking at making a http request to that url and check if response status code is not 404.
You could attempt to start a server, either TCP or HTTP, it doesn't matter. Then you could try to start listening on a port, and if it fails, check if the error code is EADDRINUSE.
var net = require('net');
var server = net.createServer();
server.once('error', function(err) {
if (err.code === 'EADDRINUSE') {
// port is currently in use
}
});
server.once('listening', function() {
// close the server if listening doesn't fail
server.close();
});
server.listen(/* put the port to check here */);
With the single-use event handlers, you could wrap this into an asynchronous check function.
Check out the amazing tcp-port-used node module!
//Check if a port is open
tcpPortUsed.check(port [, host])
//Wait until a port is no longer being used
tcpPortUsed.waitUntilFree(port [, retryTimeMs] [, timeOutMs])
//Wait until a port is accepting connections
tcpPortUsed.waitUntilUsed(port [, retryTimeMs] [, timeOutMs])
//and a few others!
I've used these to great effect with my gulp watch tasks for detecting when my Express server has been safely terminated and when it has spun up again.
This will accurately report whether a port is bound or not (regardless of SO_REUSEADDR and SO_REUSEPORT, as mentioned by #StevenVachon).
The portscanner NPM module will find free and used ports for you within ranges and is more useful if you're trying to find an open port to bind.
Thank to Steven Vachon link, I made a simple example:
const net = require("net");
const Socket = net.Socket;
const getNextPort = async (port) =>
{
return new Promise((resolve, reject) =>
{
const socket = new Socket();
const timeout = () =>
{
resolve(port);
socket.destroy();
};
const next = () =>
{
socket.destroy();
resolve(getNextPort(++port));
};
setTimeout(timeout, 10);
socket.on("timeout", timeout);
socket.on("connect", () => next());
socket.on("error", error =>
{
if (error.code !== "ECONNREFUSED")
reject(error);
else
resolve(port);
});
socket.connect(port, "0.0.0.0");
});
};
getNextPort(8080).then(port => {
console.log("port", port);
});
this is what im doing, i hope it help someone
const isPortOpen = async (port: number): Promise<boolean> => {
return new Promise((resolve, reject) => {
let s = net.createServer();
s.once('error', (err) => {
s.close();
if (err["code"] == "EADDRINUSE") {
resolve(false);
} else {
resolve(false); // or throw error!!
// reject(err);
}
});
s.once('listening', () => {
resolve(true);
s.close();
});
s.listen(port);
});
}
const getNextOpenPort = async(startFrom: number = 2222) => {
let openPort: number = null;
while (startFrom < 65535 || !!openPort) {
if (await isPortOpen(startFrom)) {
openPort = startFrom;
break;
}
startFrom++;
}
return openPort;
};
you can use isPortOpen if you just need to check if a port is busy or not.
and the getNextOpenPort finds next open port after startFrom. for example :
let startSearchingFrom = 1024;
let port = await getNextOpenPort(startSearchingFrom);
console.log(port);

Categories

Resources