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
Related
After checking similar questions on stackoverflow I did not find anything much helpful for what I want to do in my project. Reading and researching I successfully made the application work having multiple connections to my Ratchet PHP websocket server, but I noticed every time the user reloaded a page or opened a link in a new tab, the client websocket got disconnected and then reconnected again.
So, I wonder how to get only one persistent connection to a WebSocket Server, for multiple users, in a web application using a Sharedworker.
What I have in the client side is this:
<script>
$(document).ready(function($) {
let socket = new WebSocket("ws://realtime:8090");
socket.onopen = function(e) {
console.log("Browser client connected to websocket server");
socket.send("Greetings from the browser!");
};
socket.onmessage = function(event) {
console.log('Data received from server: ' + event.data);
};
socket.onclose = function(event) {
if (event.wasClean) {
console.log(`Connection closed cleanly, code=${event.code} reason=${event.reason}`);
}
else {
// e.g. server process killed or network down
// event.code is usually 1006 in this case
console.log('Connection closed unexpectedly.');
}
};
socket.onerror = function(error) {
alert(error.message);
};
});
</script>
Ok after reading, researching and trying different things and code samples, I came to this solution:
The client side (browser) should have the connection to a Sharedworker.
The sharedworker is a separated javascript file containing the core of the sharedworker and whatever other JS code that needs to be executed within it.
I first tested the sharedworker to work fine with the browser tabs, counting the number of opened tabs per user and sharing messages to one user, and then to a group of users.
Once the communication between the browser and the Sharedworker passed those tests I added the websocket code to the body of the Sharedworker JS file.
In the end, the client side (browser) looks like this:
<script>
$(document).ready(function($) {
var currentUser = "{{ Auth::user()->name }}";
let worker = new SharedWorker('worker.js');
worker.port.start();
worker.port.postMessage({
action: 'connect',
username: currentUser
});
worker.port.onmessage = function(message) {
console.log(message.data);
};
});
</script>
The Sharedworker looks like this:
// All this code is executed only once, until the onconnect() function.
//---------------------------------------------------------------------
// The array AllPorts contains objects with the format {user:<string>, port:<MessagePort>}
let AllPorts = [];
var socket = new WebSocket("ws://ssa:8090");
// Called when the WebSocket Server accepts the connection.
socket.onopen = function(e) {
//
};
// Event handler fired when the WebSocket Server sends a message to this client.
socket.onmessage = function(e) {
var message = JSON.parse(e.data);
// This loop sends a message to each tab opened by the given user.
for (var i = 0; i < AllPorts.length; i++) {
if (AllPorts[i].user == message.to) {
AllPorts[i].port.postMessage(message.msg);
}
}
};
socket.onclose = function(event) {
if (event.wasClean) {
console.log('Connection closed normally');
}
else {
console.log('Connection closed unexpectedly.');
}
};
socket.onerror = function(error) {
console.log(error.message);
};
// This event handler is fired every time a new tab is opened on the web browser.
onconnect = function(ev) {
let port = ev.ports[0];
port.onmessage = function(e) {
console.log(e.data.action);
let currentUser = e.data.username;
let userIsConnected = false;
switch (e.data.action) {
case "connect":
for (var i = 0; i < AllPorts.length; i++) {
if (AllPorts[i].user == currentUser) {
userIsConnected = true;
}
}
// Add new connected tab to AllPorts array.
AllPorts.push({user: currentUser, port: port});
if (!userIsConnected) {
// New users are added to the list of the WebSocket Server.
setTimeout(() => {
socket.send(JSON.stringify({action: 'connect', username: currentUser}));
}, 600);
}
break;
case "close":
console.log(AllPorts);
var index;
// This is also executed when the user reloads the Tab.
for (var i = 0; i < AllPorts.length; i++) {
if (AllPorts[i].port == port) {
index = i;
currentUser = AllPorts[i].user;
}
}
AllPorts.splice(index, 1);
userIsConnected = false;
// Check for any connected tab.
for (var i = 0; i < AllPorts.length; i++) {
if (AllPorts[i].user == currentUser) {
userIsConnected = true;
}
}
if (!userIsConnected) {
// User doen't have more tabs opened. Remove user from WebSocket Server.
socket.send(JSON.stringify({action: 'disconnect', username: currentUser}));
}
break;
case "notify":
// Check if given user is connected.
for (var i = 0; i < AllPorts.length; i++) {
if (AllPorts[i].user == currentUser) {
userIsConnected = true;
}
}
if (userIsConnected) {
socket.send(JSON.stringify({action: 'notify', to: currentUser, message: e.data.message}));
}
} // switch
} // port.onmessage
} // onconnect
I'm trying to write a simple wrapper class around the Paho MQTT JavaScript client. (The idea is to put some extra validation around MQTT messaging, to ensure messages are processed in the correct order.)
I'm not very comfortable with JavaScript classes, and I'm getting in a mess trying to work out what's wrong with this...
class Hermes {
constructor(uri, topic, callback) {
var clientId = "clientID_" + parseInt(Math.random() * 1000);
this.client = new Paho.MQTT.Client(uri, clientId);
this.topic = topic;
this.callback = callback;
this.client.onMessageArrived = this._onMessageArrived;
this.client.onConnectionLost = this._onConnectionLost;
this.client.connect({
onSuccess: this._onConnect,
onFailure: this._onFailure
});
}
_onConnect() {
// Once a connection has been made, make a subscription and send a message.
console.log("_onConnect: " + this.client.clientId)
this.client.subscribe(this.topic);
}
// called when connection fails
_onFailure(responseObject) {
console.log("_onFailure: "+responseObject.errorMessage);
}
// called when a message arrives
_onMessageArrived(message) {
console.log("_onMessageArrived: "+message.payloadString)
// TODO: validate message and pass to callback
}
// called when client loses connection
_onConnectionLost(responseObject) {
if (responseObject.errorCode !== 0) {
console.log("onConnectionLost: "+responseObject.errorMessage);
}
}
}
function handleMessage(message) {
// TODO: handle message
}
var hermes = new Hermes("ws://mqtt.example.com:9001/mqtt", "test", handleMessage);
Expected result:
_onConnect: clientID_xxx should be logged in the console when the client successfully connects.
Actual result:
onConnectionLost: AMQJS0005E Internal error. Error Message: undefined is not an object (evaluating 'this.client.clientId'), Stack trace: _onConnect#file:///Users/richardguy/Desktop/hermes.js:16:45
The MQTT broker is running on a VPS and I can publish/subscribe messages successfully using the Paho Javascript library outside of a class, like so...
uri = "ws://mqtt.example.com:9001/mqtt"
var clientId = "clientID_" + parseInt(Math.random() * 1000);
client = new Paho.MQTT.Client(uri, clientId);
client.onConnectionLost = onConnectionLost;
client.onMessageArrived = onMessageArrived;
client.connect({
onSuccess: onConnect,
onFailure: onFailure
});
function onConnect() {
// Once a connection has been made, make a subscription and send a message.
console.log("_onConnect: " + client.clientId)
client.subscribe("test");
}
// called when connection fails
function onFailure(responseObject) {
console.log("_onFailure: "+responseObject.errorMessage);
}
// called when a message arrives
function onMessageArrived(message) {
console.log("_onMessageArrived: "+message.payloadString)
// TODO: validate message and pass to callback
}
// called when client loses connection
function onConnectionLost(responseObject) {
if (responseObject.errorCode !== 0) {
console.log("onConnectionLost: "+responseObject.errorMessage);
}
}
Is this just a mistake in the class definition, or something to do with the Paho MQTT library??
Solution:
I needed to pass an object (in this case the instance of the Hermes class) to use as the context for the onSuccess callback rather than using this (which isn't what I thought it was, as usual...), using invocationContext in the connection options.
class Hermes {
constructor(uri, topic, callback) {
var clientId = "clientID_" + parseInt(Math.random() * 1000);
this.client = new Paho.MQTT.Client(uri, clientId);
this.topic = topic;
this.callback = callback;
this.client.onMessageArrived = this._onMessageArrived;
this.client.onConnectionLost = this._onConnectionLost;
this.client.connect({
onSuccess: this._onConnect,
onFailure: this._onFailure,
invocationContext: this
});
}
_onConnect(responseObject) {
// Once a connection has been made, make a subscription and send a message.
let self = responseObject.invocationContext;
self.client.subscribe(self.topic);
}
// called when connection fails
_onFailure(responseObject) {
console.log("_onFailure: "+responseObject.errorMessage);
}
// called when a message arrives
_onMessageArrived(message) {
console.log("_onMessageArrived: "+message.payloadString)
// TODO: validate message and pass to callback
}
// called when client loses connection
_onConnectionLost(responseObject) {
if (responseObject.errorCode !== 0) {
console.log("onConnectionLost: "+responseObject.errorMessage);
}
}
}
function handleMessage(message) {
}
var hermes = new Hermes("ws://mqtt.example.com:8080/mqtt", "test", handleMessage);
Your problem is that this is not what you think it is.
The callbacks are all made from the clients network handler, so this is actually a reference to the handler.
You can pass an object to use as the context for the onSuccess and onFailure callbacks in the connection options using invocationContext, but not for the other callbacks.
I'm trying to publish a message on a MQTT Broker on a raspberry trough paho.
I've built an "app" with visual studio 2015 (on windows 10) and I'm using the ripple simulator to test it but I always get this error:
AMQJS0011E Invalid state not connected.
I also tried to export the files and to open them as regular webpages with firefox on a linux system and I get the same kind of error so I don't think is something windows related.
The function that gets triggered with a button is playCanzone()
function playCanzone() {
console.log("play premuto");
mqttHost = '192.168.9.184';
topic = 'testTopic';
client = new Paho.MQTT.Client(mqttHost, 8080, "myclientid_" + parseInt(Math.random() * 100, 10));
onConnect();//publish('mEssaggio', 'testtopic/bar', 2);
}
// set callback handlers
client.onConnectionLost = onConnectionLost;
client.onMessageArrived = onMessageArrived;
// connect the client
client.connect({ onSuccess: onConnect });
// called when the client connects
function onConnect() {
// Once a connection has been made, make a subscription and send a message.
console.log("onConnect");
client.subscribe(topic);
message = new Paho.MQTT.Message("Hello");
message.destinationName = topic;
client.send(message);
}
// called when the client loses its connection
function onConnectionLost(responseObject) {
if (responseObject.errorCode !== 0) {
console.log("onConnectionLost:" + responseObject.errorMessage);
}
}
// called when a message arrives
function onMessageArrived(message) {
console.log("onMessageArrived:" + message.payloadString);
}
Your trying to send things before the connection is open.
This should behave better and ensure everything happens in order
var client; topic;
function playCanzone() {
console.log("play premuto");
var mqttHost = '192.168.9.184';
topic = 'testTopic';
client = new Paho.MQTT.Client(mqttHost, 8080, "myclientid_" + parseInt(Math.random() * 100, 10));
// set callback handlers
client.onConnectionLost = onConnectionLost;
client.onMessageArrived = onMessageArrived;
// connect the client
client.connect({ onSuccess: onConnect });
}
// called when the client connects
function onConnect() {
// Once a connection has been made, make a subscription and send a message.
console.log("onConnect");
client.subscribe(topic);
var message = new Paho.MQTT.Message("Hello");
message.destinationName = topic;
client.send(message);
}
// called when the client loses its connection
function onConnectionLost(responseObject) {
if (responseObject.errorCode !== 0) {
console.log("onConnectionLost:" + responseObject.errorMessage);
}
}
// called when a message arrives
function onMessageArrived(message) {
console.log("onMessageArrived:" + message.payloadString);
}
I've got a Java web application deployed on a local GlassFish 4.1 server that implements WebSockets to inter-operate with the web client. I'm able to successfully execute client-to-server communication over the socket, but server-to-client communication doesn't work for some reason.
The Java code that sends messages to the client:
try
{
String msg = ServerClientInteropManager.toResponseJSON(response);
parentSession.getBasicRemote().sendText(msg);
FLAIRLogger.get().info("Sent response to client. Message: " + msg);
}
catch (IOException ex) {
FLAIRLogger.get().error("Couldn't send message to session " + parentSession.getid() + ". Exception - " + ex.getMessage());
}
The Javascript code:
pipeline_internal_onMessage = function(event)
{
var msg = JSON.parse(event.data);
console.log("Received message from server. Data: " + event.data);
};
function pipeline_init()
{
if (PIPELINE !== null || PIPELINE_CONNECTED === true)
{
console.log("Pipline already initialized");
return false;
}
else
{
var pipelineURI = "ws://" + document.location.host + document.location.pathname + "webranker";
console.log("Attempting to establish connection with WebSocket # " + pipelineURI);
if ('WebSocket' in window)
PIPELINE = new WebSocket(pipelineURI);
else if ('MozWebSocket' in window)
PIPELINE = new MozWebSocket(pipelineURI);
else
{
console.log("FATAL: No WebSockets support");
alert("This browser does not support WebSockets. Please upgrade to a newer version or switch to a browser that supports WebSockets.");
return false;
}
// the other event listeners get added here
PIPELINE.onMessage = pipeline_internal_onMessage;
PIPELINE_CONNECTED = true;
window.onbeforeunload = function() {
pipeline_deinit();
};
console.log("Pipeline initialized");
return true;
}
}
The onMessage function is never fired, even when the server successfully calls the sendText() method. Using the AsyncRemote yields the same results. The onError listeners on both ends don't report anything either. This is my first time working with sockets so I might be missing something elementary.
replace
PIPELINE.onMessage = pipeline_internal_onMessage
with
PIPELINE.onmessage = pipeline_internal_onMessage
Please refer here for more.
I am developing chat based on websockets and webrtc. I would like to send messages to all connected users except sender but I cannot find suitable solution. To be more specific, I would like to send notifications to other connected users that new user has joined to the chat.
I am trying to give a unique ID to every connected user, but the first assigned ID is re-writed by every new user and I cannot diferentiate users.
Server:
// list of users
var CLIENTS=[];
var id;
// web server is using 8081 port
var webSocketServer = new WebSocketServer.Server({ port: 8081 });
// check if connection is established
webSocketServer.on('connection', function(ws) {
id = Math.random();
CLIENTS[id] = ws;
CLIENTS.push(ws);
ws.on('message', function(message) {
console.log('received: %s', message);
var received = JSON.parse(message);
if(received.type == "login"){
ws.send(message); // send message to itself
/* *********************************************************** */
/* *** Here I trying to check if message comes from sender *** */
sendNotes(JSON.stringify({
user: received.name,
type: "notes"
}), ws, id);
/* *********************************************************** */
}else if(received.type == "message"){
sendAll(message); // broadcast messages to everyone including sender
}
});
ws.on('close', function() {
console.log('user ' + CLIENTS[ws] + ' left chat');
delete CLIENTS[ws];
});
});
function sendNotes(message, ws, id) {
console.log('sendNotes : ', id);
if (CLIENTS[id] !== ws) {
console.log('IF : ', message);
for (var i = 0; i < CLIENTS.length; i++) {
CLIENTS[i].send(message);
}
}else{
console.log('ELSE : ', message);
}
}
function sendAll(message) {
for (var i=0; i < CLIENTS.length; i++) {
CLIENTS[i].send(message); // broadcast messages to everyone including sender
}
}
Client:
loginButton.addEventListener("click", function(){
name = usernameInput.value;
if(name.length > 0){
socket.send(JSON.stringify({
type: "login",
name: name
}));
}
});
function sendData() {
var data = dataChannelSend.value;
var userName = document.getElementById('greetingUser').innerHTML;
socket.send(JSON.stringify({
username : userName, // fetch user name from browser, after login
type : "message",
message : data
}));
}
socket.onmessage = function(message) {
var envelope = JSON.parse(message.data);
switch(envelope.type) {
case "login":
onLogin(envelope);
break;
case "message":
showMessage(envelope);
break;
}
};
I would highly appreciate If you could give me any hint. Thanks
Here is a very simple way of sending to everyone connected except the sender.
Create a broadcast function on your webSocketServer instance that will
take two params.
...
var webSocketServer = new WebSocketServer.Server({ port: 8081 });
...
/*
* method: broadcast
* #data: the data you wanna send
* #sender: which client/ws/socket is sending
*/
webSocketServer.broadcast = function(data, sender) {
webSocketServer.clients.forEach(function(client) {
if (client !== sender) {
client.send(data)
}
})
}
...
// On your message callback.
ws.on('message', function(message) {
...
// Note that we're passing the (ws) here
webSocketServer.broadcast(message, ws);
})
That's it, the broadcast method will send to each connected client
except the one who is sending.
Ok, so we are now storing the CLIENTS in a way that allows us to uniquely identify each client that is connecting, and store arbitrary information about them for later retrieval.
The code below will send the "notes" message to all clients, and THEN add the newly connecting client to the "all clients" list.
SERVER.JS:
var http = require('http'),
Static = require('node-static'),
WebSocketServer = new require('ws'),
// list of users
/*
We are now storing client data like this:
CLIENTS = {
uniqueRandomClientID: {
socket: {}, // The socket that this client is connected on
clientDetails: { // Any details you might wish to store about this client
username: "",
etc: "etc"
}
}
};
So now to get at the socket for a client, it'll be: CLIENTS[uniqueRandomClientID].socket.
Or to show a client's username, it'll be: CLIENTS[uniqueRandomClientID].clientDetails.username.
You might want to write a 'getClientByUsername' function that iterates the CLIENTS array and returns the client with that username.
*/
CLIENTS = {},
// web server is using 8081 port
webSocketServer = new WebSocketServer.Server({ port: 8081 });
// check if connection is established
webSocketServer.on('connection', function(ws) {
console.log('connection is established');
// Now using a randomly generated ID to reference a client. Probably should be better than Math.random :D
var wsID = Math.floor(Math.random() * 1000);
ws.on('message', function(message) {
console.log('received: %s', message);
var received = JSON.parse(message);
if(received.type == "login"){
// If a client with this login name doesnt exist already, its a new client
if(!CLIENTS[wsID]) {
doBroadcast(
{
"newuser": received.name,
type: "notes"
}
);
// Now add this new client to the list
CLIENTS[wsID] = {
socket: ws,
clientDetails: {
username: received.name
}
};
}
} else if(received.type == "message") {
doBroadcast(message); // broadcast messages to everyone including sender
}
});
ws.on('close', function(_event) {
if(CLIENTS[wsID]) {
console.log('user ' + CLIENTS[wsID].clientDetails.username + ' left chat');
delete CLIENTS[wsID];
}
});
/*
* Added this to 'catch' errors rather than just red dump to console. I've never actually done anything with this myself (I *like* red text in my console), but I know this handler should be here :P
*/
ws.on('error', function(_error) {
console.log("error!");
console.log(_error);
});
/*
* Send an object to a client
*
* #param WebSocketClient _to - The client you want to send to (generally an index in the CLIENTS array, i.e CLIENTS["bobsusername123"]
* #param Object _message - A stringifyable JSON object. Complex ones can screw things up, but your basic key/value pairs are usually fine to send.
*/
function doSend(_to, _message) {
_to.send(JSON.stringify(_message));
};
// Added broadcast function to replace sendAll
// Notice how it JSON stringifies the data before sending
/*
* Broadcast a message to all clients
*
* #param Object _message - A stringifyable JSON object. Complex ones can screw things up, but your basic key/value pairs are usually fine to send.
*/
function doBroadcast(_message) {
for(var client in CLIENTS) {
if(!CLIENTS.hasOwnProperty(client)) continue;
doSend(CLIENTS[client].socket, _message);
}
};
});
var fileServer = new Static.Server('.');
http.createServer(function (req, res) {
fileServer.server(req, res);
}).listen(8080, function(){
console.log("Server is listening 8080 port.");
});
console.log("Server is running on 8080 and 8081 ports");
MY CLIENT.JS (for your reference):
var loginButton = document.getElementById("loginbutton"),
usernameInput = document.getElementById("usernameInput");
var SocketClient = function(_uri, _callbacks) {
this.uri = _uri;
this.callbacks = _callbacks;
};
SocketClient.prototype = {
send: function(_message) {
this.socket.send(_message);
},
connect: function() {
try {
this.socket = new WebSocket("ws://" + this.uri);
} catch(e) { return false; }
for(var callback in this.callbacks) {
if(!this.callbacks.hasOwnProperty(callback)) continue;
this.socket["on" + callback] = this.callbacks[callback];
}
return true;
}
};
var socketClient = new SocketClient(
"127.0.0.1:8081",
{
open: function() {
console.log("connected.");
},
message: function(_message) {
console.log("received data:");
console.log(_message);
},
close: function() {
console.log("closed.");
},
error: function(_error) {
console.log("error: ");
console.log(_error);
}
}
);
socketClient.connect();
loginButton.addEventListener("click", function(){
name = usernameInput.value;
if(name.length > 0){
socketClient.send(JSON.stringify({
type: "login",
name: name
}));
}
});
AND THE CLIENT.HTML TO GO WITH IT:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
</head>
<body>
<input type="text" id="usernameInput"/>
<button type="button" id="loginbutton">Login</button>
<script src="client.js"></script>
</body>
</html>
Ive tested this with NWJS v0.12.3 running the server and Firefox on the client.
This should work
const WebSocket = require('ws');
// Websocket variables
const wss = new WebSocket.Server({
port: 3000
});
console.log('Websocket active on port 3000...');
// New WebSocket Connection
wss.on('connection', function connection(ws) {
console.log('new connection')
// On Message Received
ws.on('message', function incoming(message) {
console.log(message)
// Send To Everyone Except Sender
wss.clients.forEach(function(client) {
if (client !== ws) client.send(message);
});
});
});