I'm using a simple chat-client for my react-native chat app which uses WebSocket.
Whenever there's an error on the server side and the chat shuts down, causing an error event on my app, sets the readyState to CLOSED meaning I need to restart and try to re-connect.
I have a list of listeners when the socket connects such as: 'chatMessage', 'error', 'userDisconnected', 'userConnected'.
But the problem is, whenever I recreate the client by doing:
initWebSocket() {
this._chatClient = null;
this._chatClient = new ChatClient({
url: `${Constants.chatUrl}:${Constants.chatPort}`,
log: false
});
}
To then call a connect() afterwards, I create a duplicated event listeners of all of them. So when I send/receive a message, it shows twice as if it was indeed sent twice. This is a look & feel thing, because on the server, it only shows the message was sent once. If I go back to the message list screen and then get inside the same chat, it will update and show only 1 message, not 2. So this is 100% a duplicated event listener being called.
How can I solve this specific issue?
Related
I have already tried all possible trouble shooting for this. The problem is occuring with a single event handler. the messsage is received from serverside. This i confirmed in the network tab, but the event handler is not invoked and no error is thrown.
objConnection.on("FileTransferAccepted", (res) => {
setTransferStatus(true);
});
This is received in the browser
I have also confirmed that the event handler is attached
Im stuck. cant figure out whats wrong with just this handler. tried renaming the handler still no change.
So I finally found the problem.
I created a connection object in one page and forgot to clean up and stop the connection before navigating to another page. Here I created another connection object and updated the connection id in the db so that im reachable. But even after the component is unmounted. the earlier connection was not destroyed. And the message was received it but that object had no event handler to handle this event. All I did was close previous connection on unmount and everything started working fine.
Summary:
Solution 1: In case you have created multiple connections in different pages or components. destroy these connections by calling the "stop()" method on unmount. Also dont forget to update your new connection id in db.
Solution 2: dont create multiple connections for dfferent pages, use the same object throughout the app.
I'm calling on a client, one-to-one, multiple times during a session and the streamCreated event gets called on the host. When I hang up, I unsubscribe and the client unpublishes. However, when I call on the client again, the streamCreated event gets called twice on the host's side. I call on the client 3, 4, 5, etc. more times and the streamCreated event fires the same number of times as I have called on the client. For example on the 7th time I call the client, streamCreated gets called 7 times! It seems like I'm not really destroying the streams although streamDestroyed gets called.
On the client side, I was desperate enough to try and unpublish with:
clientSession.unpublish(clientPublisher, handleError);
clientPublisher.stream.destroy();
clientPublisher.destroy();
clientPublisher = null;
On the host side, I've also tried to make sure the subscriber was destroyed:
clientSession.unsubscribe(clientSubscriber);
clientSubscriber.destroy();
clientSubscriber = null;
The problem with this is when I open a video monitor with multiple clients and have each client publish without audio. However, I can still hear the client I called on... like their original stream(s) still exists. What am I doing wrong?
Every time I called on the person, I was using:
clientSession.on('streamCreated', function (event) {
clientSubscriber = clientSession.subscribe(event.stream, vid, {
...
So, each time I called on a client, it created a new event handler. To correct the issue, I added the following code when I disconnected from the client.
clientSession.unsubscribe(clientSubscriber);
clientSession.off();
That killed the event handler and everything works properly now.
I have html page sent by node.js server and socket.io component that connects to that server like this:
var socket = io();
Also several socket events:
socket.on('server-message', function(type, content) {
...
});
socket.on('server-update', function(type, content) {
...
});
The problem is that in the moment server is stopped, i get client side errors:
https://example.com/socket.io/?EIO=3&transport=polling&t=LptmQyC net::ERR_CONNECTION_REFUSED
Once server is started again it crashes in a 30 seconds after.
It looks like i could use a detection if server is not available anymore and just destroy all socket related events, then reconnect by page refresh or some button.
Maybe someone could help me with this.
I dont believe that much can be done about the error, its internal socket.io error that is a result of unsuccessful server polling.
For better understanding, once connection is severed, socket.io goes into ajax polling and will try to reconnect as soon as possible (meaning as soon as server is up again).
Server crash after reconnect on the other hand can be addressed very easy. The code you presented is only functional in terms of how to connect, you are missing handlers for disconnect and optionally - reconnect.
I will show you how you can add to this code to manage disconnects (and prevent server crashes after bringing it back up).
First of all connect to server:
var socket = io();
Create a disconnect event right after (btw its also a code for server side disconnect event):
socket.on('disconnect', function() {
destroy_socket_events();
});
Create function that will destroy all your listeners:
var destroy_socket_events = function() {
socket.off('disconnect');
socket.off('server-message');
socket.off('server-update');
// add all other socket events that you have in here ...
};
Now what is going to happen is this:
connection is made
server is stopped
client triggers disconnect event
function destroys all of your socket listeners
server is back up
client reconnects (socket.io will do it because of the polling)
no listener is ever triggered at that point
So you can safely reinitialize all of your code and attach listeners again properly.
When user visit my webpage his is connected to socket on port 3000
var socket = io('http://localhost:3000');
Now I included this code so every page have it. How is there a way to pass second parameter here so even if user refresh page or go to a different page he still have some unique identifier ? My problem is that I got node.js app that respond to event.
socket.on('myevent', function (data) {
Some code is executed here.....
Well now I want to emit response back to the single user who sent it, but what if in meantime user decide to refresh or switch page ? That will give him new connection.
I've been having trouble establishing a WebRTC session and am trying to simplify the issue as much as possible. So I've written up a simple copy & paste example, where you just paste the offer/answer into webforms and click submit.
The HTML+JS, all in one file, can be found here: http://pastebin.com/Ktmb3mVf
I'm on a local network, and am therefore removing the ICE server initialisation process to make this example as bare-bones as possible.
Here are the steps I'm carrying out in the example:
Page 1
Page 1 (loads page), enters a channel name (e.g. test) and clicks create.
A new Host object is created, new PeerConnection() and createDataChannel are called.
createOffer is called, and the resulting offerSDP is pasted into the offer textarea.
Page 2
Copy offerSDP from Page 1 and paste into offer textarea on Page 2, click join.
New Guest object is created, PeerConnection and an ondatachannel handler is set.
setRemoteDescription is called for the Guest object, with the offerSDP data.
createAnswer is called and the result is pasted into the answer textarea box.
Page 1
The answerSDP is copied from Page 2 and pasted into the answer textarea of Page 1, submit answer is clicked.
Host.setRemoteDescription is called with the answerSDP data. This creates a SessionDescription, then peer.setRemoteDescription is called with the resulting data.
Those are the steps carried out in the example, but it seems I'm missing something critical. After the offerer's remoteDescription is set with the answerSDP, I try to send a test message on the dataChannel:
Chrome 40
"-- complete"
> host.dataChannel.send('hello world');
VM1387:2 Uncaught DOMException: Failed to execute 'send' on 'RTCDataChannel': RTCDataChannel.readyState is not 'open'
Firefox 35
"-- complete"
ICE failed, see about:webrtc for more details
> host.dataChannel.send('hello world');
InvalidStateError: An attempt was made to use an object that is not, or is no longer, usable
I also had a more complicated demo operating, with a WebSocket signalling server, and ICE candidates listed, but was getting the same error. So I hope this simplification can help to track down the issue.
Again, the single-file code link: http://pastebin.com/Ktmb3mVf
To enable webRTC clients to connect to each other, you need ICE. While STUN and TURN, which you don't need for such a test, are part of that, even without these helpers you still need to use ICE to tell the other end which IP/port/protocol to connect to.
There are two ways to do this: Google's "trickle ice", where the SDP (answer/offer) is passed on without any ICE candidates. These are then transported over a separate signaling layer and added as they are discovered. This speeds up the connection process, as ICE takes time and some late ICE candidates might not be needed.
The classic method is to wait until all ICE candidates have been gathered, and then generate the SDP with these already included.
I have modified your latest version to do that: http://pastebin.com/g2YVvrRd
You also need to wait for the datachannel/connection to become available before being able to use it, so I've moved the sending of the message to the channels onopen event.
The significant changes to the original code:
The interface callbacks were removed from Host.prototype.createOffer and Guest.prototype.createAnswer, instead we attach the provided callback function to the respective objects for later use.
self.cb = cb;
Both Host and Guest have an added ICE handler for the PeerConnection:
var self = this;
this.peer.onicecandidate = function (event) {
// This event is called for every discovered ICE candidate.
// If this was trickle ICE, you'd pass them on here.
// An event without an actual candidate signals the end of the
// ICE collection process, which is what we need for classic ICE.
if (!event.candidate) {
// We fetch the up to date description from the PeerConnection
// It now contains lines with the available ICE candidates
self.offer = self.peer.localDescription;
// Now we move on to the deferred callback function
self.cb(self.offer);
}
}
For the guest self.offer becomes self.answer
The interface handler $("#submitAnswer").click() does not send the message anymore, instead it is send when the datachannel is ready in the onopen event defined in setChannelEvents().
channel.onopen = function () {
console.log('** channel.onopen');
channel.send('hello world!');
};