WebRTC more than one peer connection - javascript

I searched almost everywhere and can't seem to find any resource on this scenario for some reason. Any help would greatly be appreciated.
The problem is this:
I have 4 users. Lets say: A,B,C and D. I want to match them according to url. For example, if A and B connects to &room=1 and C and D connects to &room=2 I want to establish the connection between A and B pairs and C and D pairs.
Currently my code only establishes connection between A and B, and if one of the C or D users initiates the call, the connection between A and B gets disconnected and C cannot find D.
In order to solve this problem I tried to create an object like this:
{
room: 1,
peer: RTCPeerConnection
}
and store it in an array and get the peer connection according to room id and establish the connection based on RTCPeerConnection that is stored according to roomId. But that didn't work.
How would I go around fixing this without establishing a socket server that handles this communication?
I currently establish connections this way if it helps:
navigator.mediaDevices
.getUserMedia({
audio: true,
video: true
})
.then(function (stream) {
localStream = stream;
localVideo.srcObject = localStream;
try {
conn = new RTCPeerConnection(servers);
} catch (err) {
console.log("Can't establish connection");
return;
}
localStream.getTracks().forEach(function (track) {
conn.addTrack(track, localStream);
});
conn.onaddstream = function (event) {
setTimeout(() => {
callButton.click();
}, 2000);
remoteVideo.srcObject = event.stream;
};
conn.onicecandidate = function (event) {
if (event.candidate) {
chat.server.iceCandidate(
JSON.stringify({ candidate: event.candidate })
);
}
};
})
.catch(function (err) {
console.log("Error", err);
});
And this is my failed solution:
var connections = JSON.parse(localStorage.getItem("connections")) || [];
var connection;
if (connections) {
connection = connections.find(function (conn) {
return conn.id = roomId;
})
}
if (!connection) {
conn = new RTCPeerConnection(servers);
var data = {
id: roomId,
peer: conn
}
localStorage.removeItem("connections");
localStorage.setItem("connections", JSON.stringify(connections));
} else {
conn = JSON.parse(connection.peer);
conn.__proto__ = new RTCPeerConnection();
}
This fails because of course you can't store proto of an object in localStorage (RTCPeerConnection). When I stringify/parse it peer attribute comes as an empty object. If I try a global variable, instead of localStorage, it always comes empty. I'd very appreciate any tip about this issue.

It's like your messing up with the localStorage variable, for testing purpose and avoid the signaling server implementation, I advice to use the serverless-webrtc or an updated by jameshfisher
With this you can create the rooms (separate the clients) and show you a sdp message to copy and paste to the other client (without localStorage) to make the peer connection.
This will be better to test between browsers firefox <-> chrome, safari, etc.

Related

how to reply and send message after receive message in node js using websocket

After receive message from client, I have to connect db(mysql) and save data and need to response the result to client and inform to other(admin) client.
So I need to get current socket client and special client(admin) from the socket list.
Is it possible to get current socket outside of wss connection block?
Thanks.
const WebSocketServer = require('ws');
// Creating a new websocket server
const wss = new WebSocketServer.Server({ port: 8080 });
const clients = new Map();
// Creating connection using websocket
wss.on("connection", ws => {
console.log("new client connected");
client_id = Date.now();
clients.set(client_id, ws);
// sending message
ws.on('message', function(message) {
//wss.broadcast(JSON.stringify(message));
console.log('Received: ' + message);
BuyCoin(message);
//console.log()
});
ws.on("close", () => {
console.log("the client has connected");
});
ws.onerror = function () {
console.log("Some Error occurred")
}
// ws.send('You successfully connected to the websocket.');
});
function BuyCoin(strValue){
const req_info = JSON.parse(strValue);
console.log(req_info.user_id)
console.log('betting!');
var sql = 'SELECT * from users where id = ? LIMIT 1'
connection.query(sql, req_info.user_id, (ws)=>{
return function(err, rows, fields) {
//console.log("ix="+ix);
ws.send(rows[0]);
};
});
}
}
You have several options:
#1: You can put the BuyCoin function logic inside the ws scope to make it a local function that is in scope of the ws variable for the current connection like this:
const WebSocketServer = require('ws');
// Creating a new websocket server
const wss = new WebSocketServer.Server({ port: 8080 });
const clients = new Map();
// Creating connection using websocket
wss.on("connection", ws => {
console.log("new client connected");
client_id = Date.now();
clients.set(client_id, ws);
// sending message
function BuyCoin(strValue) {
const req_info = JSON.parse(strValue);
console.log(req_info.user_id)
console.log('betting!');
var sql = 'SELECT * from users where id = ? LIMIT 1'
connection.query(sql, req_info.user_id, (ws) => {
return function(err, rows, fields) {
//console.log("ix="+ix);
ws.send(rows[0]);
};
});
}
ws.on('message', function(message) {
//wss.broadcast(JSON.stringify(message));
console.log('Received: ' + message);
BuyCoin(message);
//console.log()
});
ws.on("close", () => {
console.log("the client has connected");
});
ws.onerror = function() {
console.log("Some Error occurred")
}
// ws.send('You successfully connected to the websocket.');
});
#2: You can pass the ws value to your BuyCoin() function as an argument by just changing the function call from this:
BuyCoin(message);
to this:
BuyCoin(ws, message);
And, then changing your function declaration from this:
function BuyCoin(strValue) {...}
to this:
function BuyCoin(ws, strValue) {...}
Is it possible to get current socket outside of wss connection block?
No, there really is no such thing as the current socket. When using asynchronous code in nodejs, lots of different pieces of code can be "in-flight" at the same time so there is no global sense of the current socket. Instead, you have manage data specific to your current operation either by using scope, by passing as an argument or by setting as a properties on some other object that is passed as an argument. Since there is no natural object that BuyCoin() already has access to here that is specific to the user with the activity, then that leaves the first two options (using scope and passing as an argument).
FYI, this code looks a bit problematic because you're allowing the webSocket to send in the user_id that will be operated on without any visible authentication. That exposes you to rogue sockets that can pretend to be users that they aren't.
Also, it doesn't appear you have code that removes webSockets from the clients Map object when they disconnect so that Map object will just get larger and larger and contain lots of dead connections.
Another thing that needs fixing is that your connection.query() code is declaring a callback that does nothing but return another function and it tried to make up a value of ws that would never actually be passed. That function you create inside the callback is never called. Change from this:
connection.query(sql, req_info.user_id, (ws) => {
return function(err, rows, fields) {
//console.log("ix="+ix);
ws.send(rows[0]);
};
});
to this:
connection.query(sql, req_info.user_id, (err, rows, fields) => {
//console.log("ix="+ix);
ws.send(rows[0]);
});
And, combine that with one of the above two solutions to get access to the ws value.

How to regularly check if a client has disconnected from WebSocket http Server

(I didn't really understood other solutions.)
I'm streaming a webcam video from a browser client (index.html) to a WebSocket server (app.js/node.js).
The server is receiving these frames encoded as base64 and generates images out.png inside a static/img folder.
I would like to delete this out.png image after the client stopped streaming.
const wsServer = new WebSocket.Server({ server: httpServer });
// array of connected websocket clients
let connectedClients = [];
wsServer.on('connection', (ws, req) => {
console.log('Connected');
// add new connected client
connectedClients.push(ws);
// listen for messages from the streamer, the clients will not send anything so we don't need to filter
ws.on('message', data => {
// send the base64 encoded frame to each connected ws
connectedClients.forEach((ws, i) => {
if (ws.readyState === ws.OPEN) { // check if it is still connected
// create static image of the base64 encoded frame
fs.writeFile(path.resolve(__dirname, './static/img/out.png'), data.replace(/^data:image\/png;base64,/, ""), 'base64', function(err) {
if (err) throw err;
});
} else { // if it's not connected remove from the array of connected ws
connectedClients.splice(i, 1);
}
});
});
});
I tried this approach which looks like it would make sense. But .on("close") is not actively checking if the streamer is still connected. How can I do that? So, the server should regularly check if the streamer stopped streaming and clean up the mess which he/she left behind :)
// If the client is not streaming or stopped streaming
wsServer.on('close', (ws, req) => {
console.log('Disconnected');
// delete last image if streamer does not send new messages
fs.unlink(path.resolve(__dirname, './static/img/out.png'), function (err) {
if (err) throw err;
});
});

PeerJS: Other Peer Detected but Connection Not Open

I am using PeerJS to establish peer-to-peer connections. It seems that I have been able to establish a connection momentarily but I have been unable to send and receive data over the connection. Below is my code:
var peer;
var nbcc = [];
function updatePeerConnections(){
if (!id) return;
if (!peer) {
peer = new Peer(id,{key: '52hhtusf1t0rudi'});
peer.on('open', function(conn) {
console.log('new connection');
});
peer.on('connection', function(conn) {
conn.on('open', function(){
console.log('connected!');
conn.on('data', function(data){
let o = JSON.parse(data);
console.log('updating car ' + o.i);
updateCarMarker(o.id,o.lat,o.lng);
});
});
conn.on('error', function(err){
console.log(err);
});
console.log(conn.open+': remote peer detected: '+conn.peer);
conn.id = conn.peer;
nbcc[conn.peer] = conn;
});
peer.on('error', function(err){
console.log(err.type);
});
updateConnections();
} else {
updateConnections();
}
}
function updateConnections(){
for (cm of Object.values(carMarkers)){
if (cm.id!=id && !Object.keys(nbcc).includes(cm.id)){
console.log('connecting to '+cm.id)
nbcc[cm.id] = peer.connect(cm.id);
nbcc[cm.id].id = cm.id;
nbcc[cm.id].on('error', function(err){
console.log(err);
});
nbcc[cm.id].on('open', function(){
console.log('connected!');
nbcc[cm.id].on('data', function(data){
let o = JSON.parse(data);
console.log('updating car ' + o.i);
updateCarMarker(o.id,o.lat,o.lng);
});
});
}
}
}
On the browser console, it printed 'new connection' and then 'false: remote peer detected: 879874543958', where the id is the remote peer (another tab in the browser). It never printed 'connected!' nor any error message. What is wrong with the code?
I found the problem!
It is with this line:
peer = new Peer(id,{key: '52hhtusf1t0rudi'});
We must not set the optional parameter 'id' ourselves. Instead, we should receive it in peer.on('connection',function(id){...})
Duh. Why allow that parameter to be set when it musn't be set. That is confusing.
If this works in localhost but doesn't work over the internet then you are probably missing a STUN and a TURN server.
By default peer js uses the google global stun server but you will have to get a turn server for this to work in production.

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.

Creating and using a data channel between two peers with webRTC

I am trying to setup a peer to peer file sharing system using WebRTC. I'm able to open a data channel on each side, but I can't send messages from one user to another. Moreover, if one peer closes the channel, the other, the onclose event is only triggered for this user.
What's the proper way to setup and use a data channel with webRTC?
Could you tell me what's wrong or missing in my code?
//create RTC peer objet.
var RTCPeerConnection = webkitRTCPeerConnection;
var RTCIceCandidate = window.RTCIceCandidate;
var RTCSessionDescription = window.RTCSessionDescription;
var iceServers = {
iceServers: [{
url: 'stun:stun.l.google.com:19302'
}]
};
var p2p_connection = new RTCPeerConnection({
iceServers: [
{ 'url': (IS_CHROME ? 'stun:stun.l.google.com:19302' : 'stun:23.21.150.121') }
]
});
// send offer (only executes in one browser)
function initiateConnection() {
p2p_connection.createOffer(function (description) {
p2p_connection.setLocalDescription(description);
server_socket.emit('p2p request', description,my_username);
});
};
// receive offer and send answer
server_socket.on('p2p request', function(description,sender){
console.log('received p2p request');
p2p_connection.setRemoteDescription(new RTCSessionDescription(description));
p2p_connection.createAnswer(function (description) {
p2p_connection.setLocalDescription(description);
server_socket.emit('p2p reply', description,sender);
});
});
// receive answer
server_socket.on('p2p reply', function(description,sender){
console.log('received p2p reply');
p2p_connection.setRemoteDescription(new RTCSessionDescription(description));
});
// ICE candidates
p2p_connection.onicecandidate = onicecandidate; // sent event listener
// locally generated
function onicecandidate(event) {
if (!p2p_connection || !event || !event.candidate) return;
var candidate = event.candidate;
server_socket.emit('add candidate',candidate,my_username);
}
// sent by other peer
server_socket.on('add candidate', function(candidate,my_username){
p2p_connection.addIceCandidate(new RTCIceCandidate({
sdpMLineIndex: candidate.sdpMLineIndex,
candidate: candidate.candidate
}));
});
// data channel
var dataChannel = p2p_connection.createDataChannel('label');
dataChannel.onmessage = function (event) {
var data = event.data;
console.log("I got data channel message: ", data);
};
dataChannel.onopen = function (event) {
console.log("Data channel ready");
dataChannel.send("Hello World!");
};
dataChannel.onclose = function (event) {
console.log("Data channel closed.");
};
dataChannel.onerror = function (event) {
console.log("Data channel error!");
}
Update:
Found the solution there: http://www.html5rocks.com/en/tutorials/webrtc/basics/
p2p_connection.ondatachannel = function (event) {
receiveChannel = event.channel;
receiveChannel.onmessage = function(event){
console.log(event.data);
};
};
You might consider using the simple-peer library to avoid dealing with these complexities in the future. The WebRTC API calls are confusing and the ordering is sometimes hard to get right.
simple-peer supports video/voice streams, data channel (text and binary data), and you can even use the data channel as a node.js-style duplex stream. It also supports advanced options like disabling trickle ICE candidates (so each client only needs to send one offer/answer message instead of many repeated ice candidate messages). It's un-opinionated and works with any backend.
https://github.com/feross/simple-peer
Abstractions!
https://github.com/feross/simple-peer (noted above by #Feross)
https://github.com/rtc-io/rtc-mesh
https://github.com/dominictarr/scuttlebutt
https://github.com/mafintosh/peervision
https://github.com/muaz-khan/DataChannel
Gigantic list of related projects...
https://github.com/kgryte/awesome-peer-to-peer

Categories