Need help on using takeUntil() and Observable.fromEvent() Methods - node.js

I'm following this tutorial to create Reactive TCP server in Nodejs
here's code that i've been working on
const Rx = require('rxjs')
const net = require('net')
const uuid = require('uuid');
module.exports = () => {
const sockets = new Map();
const ids = new Map();
const GetSocket = _id => sockets.get(_id);
const GetId = _socket => ids.get(_socket);
const SetSocket = _socket =>{
_socket.setEncoding('utf8');
const _id = uuid();
sockets.set(_id, _socket);
ids.set(_socket,_id);
return _id;
};
const server = net.createServer({ allowHalfOpen: true });
const socketStream = Rx.Observable.fromEvent(server, 'connection');
const RemoveSocket = socket = () => {
console.log("connection closed && removing socket from Map");
const id = ids.get(socket);
sockets.delete(id);
ids.delete(socket)
};
const socketObservable = socket => SetSocket(socket) &&
Rx.Observable
.of({
action: 'CONNECTION',
socket: GetId(socket)
})
.merge(
Rx.Observable
.fromEvent(socket,'data')
.map(d=>{
try {return JSON.parse(d);}
catch (e) {
console.log(e);
return d;
}
})
.map(msg=>{
return Object.assign({action:msg,socket:GetId(socket)})
})
)
.takeUntil(Rx.Observable.fromEvent(socket, 'close').map(d=>{
console.log("!!!! Should remove !!!");
RemoveSocket(socket);
}));
const Print = ()=>{
//ids.forEach(id=> console.log(GetSocket(id)));
console.log("total connected socket : " + ids.size);
};
const startServer = port => server.listen(port) &&
socketStream
.flatMap(socketObservable);
return {startServer, Print , stop: () => server.close()};
};
and here's my test result(just sending test msg and connect/reconnect to server )
{ action: 'CONNECTION',
socket: '8989b581-dc54-479b-a8c0-870cc8103c5b' }
total connected socket : 1
{ action: { test: 1 },
socket: '8989b581-dc54-479b-a8c0-870cc8103c5b' }
total connected socket : 1
{ action: { test: 2 },
socket: '8989b581-dc54-479b-a8c0-870cc8103c5b' }
total connected socket : 1
{ action: 'CONNECTION',
socket: 'b868104b-d1cf-41c9-950f-472f63bac27a' }
total connected socket : 2
{ action: { test: 1 },
socket: 'b868104b-d1cf-41c9-950f-472f63bac27a' }
total connected socket : 2
{ action: 'CONNECTION',
socket: 'b9a579fe-3715-4952-aaf7-d7f64a0bea99' }
total connected socket : 3
Everything working fine till detecting socket close event by TakeUntil()
I tried using takewhile() by simply adding counter like this TakeWhile(cnt < 5)
and socket stream completed as expected.
this is my first attempt to make something with Node.js and feel like i'm missing something.
can anyone help me to understand why takeUntil() is not working here?
Thank you :)

So my confusion was from understanding 'close' and 'end' events
'end' event gets triggered when the client disconnected or server calls socket.end(..) when server receives FIN packet
and 'close' event gets called after socket.destroy()
if anyone wants to see all socket events in action, I recommend watching this video
#Brannon, Thank you for pointing out the right event usage and thank you, everyone, for helping me out with this!!
also just in case, anyone wants working TCP server code.
dependency : rxjs 5.5.0
const Rx = require('rxjs');
const net = require('net');
const uuid = require('uuid');
module.exports = () => {
const sockets = new Map();
const ids = new Map();
const GetSocket = _id => sockets.get(_id);
const GetId = _socket => ids.get(_socket);
const SetSocket = _socket =>{
_socket.setEncoding('utf8');
const _id = uuid();
sockets.set(_id, _socket);
ids.set(_socket,_id);
return _id;
};
const server = net.createServer({ allowHalfOpen: true });
const socketStream = Rx.Observable.fromEvent(server, 'connection');
const RemoveSocket = socket => {
const id = ids.get(socket);
sockets.delete(id);
ids.delete(socket)
console.log("[server.js] socket closed..");
};
const socketObservable = socket => SetSocket(socket) &&
Rx.Observable
.of({
action: 'CONNECTION',
socket: GetId(socket)
})
.merge(
Rx.Observable
.fromEvent(socket,'data')
.map(d=>{
try {return JSON.parse(d);}
catch (e) {
console.log(e);
return d;
}
})
.map(msg=>{
return Object.assign({action:msg,socket:GetId(socket)})
})
)
.takeUntil(Rx.Observable.fromEvent(socket, 'end')
.map(()=>RemoveSocket(socket)));
const Print = ()=>{
//ids.forEach(id=> console.log(GetSocket(id)));
//ids.clear();
console.log("total connected socket : " + ids.size);
};
const startServer = port => server.listen(port) &&
socketStream
.flatMap(socketObservable);
console.log("[server.js] Starts Started" );
return {startServer, Print , stop: () => server.close()};
};

Related

Socket.IO is creating 2 socket IDS every time a new user joins a room instead of one

I am creating a collaborative react app, in that every time a new user is joining the room the socket io is generating 2 id's for every user, I have followed the documentation code, in the same way, I am not sure why is this happening, below is the snippet of the server-side code (server.js).
const cors = require('cors');
const axios = require('axios');
const {Server} = require('socket.io');
const http = require('http');
const ACTIONS = require('../src/Actions');
const app = express(); // Create an instance of express
const server = http.createServer(app) // Create an instance of http server
const io = new Server(server); // Create an instance of socket.io server
// Storing a client list
const clients = new Map();
// Switching on the server socket to listen for connections
io.on('connection', (socket) => {
const clientSocketId = socket.id;
console.log(clientSocketId+' connected');
socket.on(ACTIONS.JOIN,({roomId,username})=>{
console.log(roomId,username)
clients.set(socket.id,{
roomId,
username,
socketId: socket.id,
})
socket.join(roomId);
const clientlist = Array.from(clients.values())
clientlist.forEach(client=>{
io.to(client.socketId).emit(ACTIONS.JOINED,{
clientlist,
username,
socketId: socket.id,
})
})
})
// The server is listening to two events Code Change and Code Sync
// Code Change is emitted when the user changes the code
// Code Sync is called when the user joins the room to sync the previously typed code
socket.on(ACTIONS.CODE_CHANGE, ({ roomId, code }) => {
socket.in(roomId).emit(ACTIONS.CODE_CHANGE, { code });
});
socket.on(ACTIONS.SYNC_CODE, ({ socketId, code }) => {
io.to(socketId).emit(ACTIONS.CODE_CHANGE, { code });
});
// Disconnecting the current socket
socket.on('disconnecting',()=>{
console.log(clientSocketId+' disconnected')
// Getting the list of all the present rooms
const rooms = Object.keys(socket.rooms);
rooms.forEach(roomId=>{
socket.in(roomId).emit(ACTIONS.DISCONNECTED,{
socketId: socket.id,
username: clients.get(socket.id).username,
})
})
clients.delete(socket.id);
socket.leave();
})
})
const PORT = process.env.SERVER_PORT || 5000;
server.listen(PORT,()=>{console.log('Listening on '+PORT)});
And below is how I have initialized the socket on the client-side
export const initSocket = async () => {
const options = {
transports: ['websocket'],
reconnection: true,
reconnectionAttempts: 'Infinity',
forceNew: true,
reconnectionDelay: 1000,
reconnectionDelayMax: 5000,
timeout: 10000,
autoConnect: true,
secure: true,
}
const socket = io(process.env.REACT_APP_SERVER_URL,options)
return socket
}
And in my Dashboard.js I have called the init function in UseEffect
React.useEffect(()=>{
// As the user joins the room we initialize the client socket which connects to the server
const init = async () => {
socketRef.current = await initSocket();
// Handling connection errors
socketRef.current.on('connect_error',(err)=>handleError(err))
socketRef.current.on('connect_failed',(err)=>handleError(err))
const handleError = (err)=>{
console.log(err)
toast({
title: 'Error connecting to the server',
status: 'error',
duration: 9000,
isClosable: true,
})
reactNavigater('/')
}
socketRef.current.emit(ACTIONS.JOIN,{
roomId: roomId,
username: location.state?.username,
});
// Listening for joined event when a even user joins
socketRef.current.on(ACTIONS.JOINED,({clientlist,username,socketId})=>{
if(username !== location.state?.username){
toast({
title: `${username} has joined the room`,
status: 'success',
duration: 9000,
isClosable: true,
})
}
setClientlist(clientlist)
socketRef.current.emit(ACTIONS.SYNC_CODE, {
socketId: socketRef.current.id,
code: codeRef.current,
});
})
// Listening for disconnected event when a even user disconnects
socketRef.current.on(ACTIONS.DISCONNECTED,({socketId,username})=>{
toast({
title: `${username} has disconnected`,
status: 'warning',
duration: 9000,
isClosable: true,
})
// Filter the clientlist to remove the disconnected client
setClientlist(Clientlist.filter(client=>client.socketId !== socketId))
}
)
}
init()
// Here we have multiple listeners, so we have to remove them when the component unmounts
return ()=>{
if(socketRef.current){
socketRef.current.disconnect()
socketRef.current.off(ACTIONS.JOINED)
socketRef.current.off(ACTIONS.DISCONNECTED)
}
}
},[])
Any help would be appreciated
If you have strict mode on, what's by default, then useEffect is called twice (from React 18). And your connection is created twice. And as every connection generates new Id, you get two id's.
https://stackoverflow.com/a/72238236/8522881
My React Component is rendering twice because of Strict Mode
You can just wrap your socket.io instance with a useRef so it will keep the same value between re-renders, like:
import React from 'react'
function MyComponent() {
const socket = React.useRef(null)
React.useEffect(() => {
if(!socket.current) {
socket.current = io('ws://my-url/');
}
}, [])
// now you can use socket.current and ensure you aways will have only one instance
return (
<p>hello</p>
)
}

Correct way to use socket.on in react

I am working on a small project which will be used for crawling. I am having problem while working with sockets.
I am emitting some events from the server in a loop.
for(i=0;i<blogs.length;i++){
socket.emit('crawling',blogs[i]);
const startTime = Date.now();
const post = await crawlPost(blogs[i]);
const endTime = Date.now();
socket.emit('crawled',blogs[i]);
socket.emit('timeTaken',{
time: endTime-startTime,
blog : blogs[i],
});
}
I want to listen for these events one by one in react component, but I am unable to do so, code I have written for listening to the events.
const App = () => {
const [crawlingBlog, setCrawlingBlog] = useState([]);
const ENDPOINT = 'localhost:4000';
socket = io(ENDPOINT);
useEffect(() => {
socket.on('crawling' , (blog) => {
setCrawlingBlog(crawlingBlog.concat(blog))
})
},[])
}
did you wrap your socket at the server like that?
module.exports = connectSockets
function connectSockets(io) {
io.on('connection', socket => {
for(i=0;i<blogs.length;i++){
socket.emit('crawling',blogs[i]);
const startTime = Date.now();
const post = await crawlPost(blogs[i]);
const endTime = Date.now();
socket.emit('crawled',blogs[i]);
socket.emit('timeTaken',{
time: endTime-startTime,
blog : blogs[i],
});
}
})
}

Error to emit socket: internal / bootstrap / pre_execution.js: 308

internal/bootstrap/pre_execution.js:308
get() {
^
RangeError: Maximum call stack size exceeded
at get (internal/bootstrap/pre_execution.js:308:8)
at hasBinary (/home/spirit/Documents/ProjectsJS/ProjectBack/node_modules/has-binary2/index.js:44:3)
Hello I am trying to issue my socket but I have this error, and I can not receive my socket in the back end below all the code I used
io.on('connection', function (socket) {
setInterval(() => Queue.searching() , 1000);
sessionMap.set(socket.id,socket);
//ADD PLAYER TO QUEUE
socket.on('addPlayer-Queue', (result) => {
const player = {
id:result.id,
socketid: socket.id,
name: result.name,
mmr: result.mmr,
socket: socket
}
const nPlayer = new Player(player);
Queue.addPlayer(nPlayer);
/*
console.log(queue);
console.log(sessionMap.all());*/
//socket.emit('match', matches)
});
});
//
searching = () => {
const firstPlayer = this.getRandomPlayer();
const secondPlayer = this.players.find(
playerTwo =>
playerTwo.mmr < this.calculateLessThanPercentage(firstPlayer) &&
playerTwo.mmr > this.calculateGreaterThanPercentage(firstPlayer) &&
playerTwo.id != firstPlayer.id
);
if(secondPlayer){
const matchedPlayers = [firstPlayer, secondPlayer];
this.removePlayers(matchedPlayers);
Matches.configurePlayersForNewMatch(matchedPlayers);
}
}
//
getMatchConfigurationFor = players => {
console.log(sessionMap.all())
if(players){
const match = new Match(players);
const result = {
idMatch: match.id,
playerOne: match.players[0],
playerTwo:match.players[1]
}
return result;
}
}
configurePlayersForNewMatch = (matchedPlayers) => {
matchedPlayers.forEach(player =>
sessionMap.get(player.socketId)
.broadcast.to(player.socketId)
.emit('match',
this.getMatchConfigurationFor(matchedPlayers)));
}
so what I admit the problem is in this code snippet:
matchedPlayers.forEach(player =>
sessionMap.get(player.socketId)
.broadcast.to(player.socketId)
.emit('match',
this.getMatchConfigurationFor(matchedPlayers)));
The session map is a mapping I made of my sockets saving key (socket id) and socket to send by emit later. on console log about my session.map this is console . log on my socket player https://pastebin.com/XHDXH9ih

reconnecting to nodejs websocket on failure

This is my first practice after reading some tutorials and videos. Basically, I need message to be sent from the server (nodejs) to the client (Angular 6). At first tho, when client app is booted, it sends user id to the server for authentication. The server then will send data based on that user.
Now my problem is on first load and a few calls, the connection does work. But then on refresh or so, the connection drops. My client does console out "retrying" but it never succeeds. It works only if I manually restart the server and reload the client so a new connection could be established.
How can I maintain a fairly stable connection throughout the lifetime of a client? At times the readyState stays at 3 on the server i.e. connecting, which I am confused with because the client does try to reconnect...just fails.
My Server is simple. index.js (Tried to put it up on stackblitz but failed...would appreciate if someone can figure out the dependency file: nodejs websocket server)
const express = require('express');
const bodyParser = require('body-parser');
const pg = require ('pg');
var ws = require('./ws')
var app = express()
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.listen(3000, function () {
console.log('We are running on port 3000!')
})
ws.js:
const winston = require('winston');
const logger = winston.createLogger({
transports: [
new winston.transports.File({ filename: 'error.log'})
]
});
var WebSocketServer = require("ws").Server,
wss = new WebSocketServer({
port: 40511
});
let data = {
'packet': ['amy1', 'amy2', 'amy3']
}
const mx = 2;
const mn = 0;
wss.on("connection", function(ws) {
ws.on("message", function(user) {
// client has called now. If the connection
// fails, the client does try to connection again and again -- no limit but it simply doesn't seem to have effect. When connecting, it simply sends user name
console.log("received: %s", user);
setInterval(function(){
var random = Math.floor(Math.random() * (mx - mn + 1) + mn);
if (ws.readyState == ws.OPEN){
ws.send(data['packet'][random]);
}
}, 3000);
});
});
My front end is: service.ts
import { Observable} from 'rxjs';
export class WebSocketService {
socket: WebSocket;
constructor() { }
initConnection(): void {
if(!this.socket){
this.socket = new WebSocket('ws://localhost:40511');
// when connection is open, send user id
this.socket.onopen = () => this.socket.send(2);
}
}
manageState() : Observable<any>{
const vm = this;
return new Observable(observer => {
this.socket.onerror = (e) => {
// close it
this.socket.close();
observer.next('web socket communication closed due to error')
};
this.socket.onclose = (e) => {
//socket closed for any reason
setTimeout(function() {
console.log('try to connect')
vm.initConnection();
observer.next('still trying')
}, 1000);
}
});
}
onMessage(): Observable<any> {
// when message arrives:
return new Observable(observer => {
this.socket.onmessage = (e) => {
console.log(e.data);
observer.next(e.data)
};
});
}
}
component.ts:
// initialize the connection
this.service.initConnection();
this.service.onMessage().subscribe(
data => {
// we have got data
console.log('data came ', data)
},
err => {
console.log("error websocking service ", err);
}
);
// track state of the communication, letting the service to reconnect if connection is dropped
this.service.manageState().subscribe(data => {
console.log(data);
});

WebRTC Video sharing app doesn't work

I have been trying to share a video stream between two clients over WebRTC for about a week now and I have no idea how to proceed any further. I'm frustrated and could really use some help from the more experienced. Please help me get this running.
I am using Websockets and NodeJS. I will post all of my code below:
Server Code ( on NodeJS )
"use strict";
/** Requires **/
var webSocketServer = require('websocket').server,
expr = require("express"),
xpress = expr(),
server = require('http').createServer(xpress);
// Configure express
xpress.configure(function() {
xpress.use(expr.static(__dirname + "/public"));
xpress.set("view options", {layout: false});
});
// Handle GET requests to root directory
xpress.get('/', function(req, res) {
res.sendfile(__dirname + '/public/index.html');
});
// WebSocket Server
var wsServer = new webSocketServer({
httpServer: server
});
// Set up the http server
server.listen(8000, function(err) {
if(!err) { console.log("Listening on port 8000"); }
});
var clients = [ ];
/** On connection established */
wsServer.on('request', function(request) {
// Accept connection - you should check 'request.origin' to make sure that client is connecting from your website
var connection = request.accept(null, request.origin);
var self = this;
// We need to know client index to remove them on 'close' event
var index = clients.push(connection) - 1;
// Event Listener for when Clients send a message to the Server
connection.on('message', function(message) {
var parsedMessage = JSON.parse(message.utf8Data);
if ( parsedMessage.kind == 'senderDescription' ) {
wsServer.broadcastUTF(JSON.stringify({ kind:'callersDescription', callerData: parsedMessage }));
}
});
});
Index.html loads and immediately runs VideoChatApp.js
function VideoChatApp() {
this.connection = null;
this.runConnection();
}
_p = VideoChatApp.prototype;
/** Initialize the connection and sets up the event listeners **/
_p.runConnection = function(){
// To allow event listeners to have access to the correct scope
var self = this;
// if user is running mozilla then use it's built-in WebSocket
window.WebSocket = window.WebSocket || window.MozWebSocket;
// if browser doesn't support WebSocket, just show some notification and exit
if (!window.WebSocket) { return; }
/** Where to make the connection **/
var host = location.origin.replace(/^http/, 'ws');
console.log(host);
this.connection = new WebSocket(host);
/** Once the connection is established **/
this.connection.onopen = function () {
console.log("Web Socket Connection Established");
self.onConnectionEstablished();
};
/** If there was a problem with the connection */
this.connection.onerror = function (error) {
console.log("ERROR with the connection *sadface*");
};
}; // end runConnection
_p.onConnectionEstablished = function() {
// My connection to the nodejs server
var websocketConnection = this.connection;
// Some local variables for use later
var mediaConstraints = {
optional: [],
mandatory: {
OfferToReceiveVideo: true
}
};
var offerer, answerer;
this.theLocalStream = null;
var amITheCaller = false;
var localVideoTag = document.getElementById('localVideoTag');
var remoteVideoTag = document.getElementById('remoteVideoTag');
window.RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
window.RTCSessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription;
window.RTCIceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate;
navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia;
window.URL = window.webkitURL || window.URL;
window.iceServers = {
iceServers: [{
url: 'stun:23.21.150.121'
}]
};
var callButton = document.getElementById("callButton");
callButton.onclick = callClicked;
function callClicked() {
amITheCaller = true;
setUpOffer();
}
offerer = new RTCPeerConnection(window.iceServers);
answerer = new RTCPeerConnection(window.iceServers);
/** Start Here - Set up my local stream **/
getUserMedia(function (stream) {
hookUpLocalStream(stream);
});
function getUserMedia(callback) {
navigator.getUserMedia({
video: true
}, callback, onerror);
function onerror(e) {
console.error(e);
}
}
function hookUpLocalStream(localStream) {
this.theLocalStream = localStream;
callButton.disabled = false;
localVideoTag.src = URL.createObjectURL(localStream);
localVideoTag.play();
};
/* When you click call, then we come here. Here I want to set up the offer and send it. */
function setUpOffer() {
var stream = theLocalStream;
offerer.addStream(stream);
offerer.onaddstream = function (event) {
console.log("onaddstream callback was called");
};
offerer.onicecandidate = function (event) {
if (!event || !event.candidate) return;
answerer.addIceCandidate(event.candidate);
};
offerer.createOffer(function (offer) {
offerer.setLocalDescription(offer);
console.log("------------------- What I am sending: -------------------------");
console.log(offer);
console.log(stream);
console.log("-----------------------------------------------------------------\n");
var jsonMsg = JSON.stringify( {kind:'senderDescription', streamInfo: offer, theStream: stream} );
websocketConnection.send( jsonMsg );
//answererPeer(offer, stream);
}, onSdpError, mediaConstraints);
}
/* Respond to a call */
function answererPeer(offer, stream) {
answerer.addStream(stream);
answerer.onaddstream = function (event) {
remoteVideoTag.src = URL.createObjectURL(event.stream);
remoteVideoTag.play();
};
answerer.onicecandidate = function (event) {
if (!event || !event.candidate) return;
offerer.addIceCandidate(event.candidate);
};
answerer.setRemoteDescription(offer, onSdpSucces, onSdpError);
answerer.createAnswer(function (answer) {
answerer.setLocalDescription(answer);
offerer.setRemoteDescription(answer, onSdpSucces, onSdpError);
}, onSdpError, mediaConstraints);
}
function onSdpError(e) {
console.error('onSdpError', e);
}
function onSdpSucces() {
console.log('onSdpSucces');
}
websocketConnection.onmessage = function (messageFromServer) {
console.log(" ------------------------ Message from server: -------------------- ");
var parsedMessage = JSON.parse(messageFromServer.data);
if(parsedMessage.callerData.kind = "senderDescription") {
console.log("Received a senderDescription");
console.log(parsedMessage.callerData.streamInfo);
console.log(parsedMessage.callerData.theStream);
console.log("-------------------------------------------------------------------\n");
answererPeer(parsedMessage.callerData.streamInfo, parsedMessage.callerData.theStream);
}
};
};// end onConnectionEstablished()
Finally, here are my errors:
I am not sure if this is still interesting for you, but I have some very good experience with WebRTC using PeerJS as a wrapper around it. It takes care of all the stuff you don't want to do (http://peerjs.com/). There is the client library as well as a very nice signaling server for nodejs (https://github.com/peers/peerjs-server). You can easy extend this server in your own node server.
This may not explain why your approach failed, but gets WebRTC running easily.
You can start with code that is already working and completely open. Check out easyrtc.com we have a client api, signalling server and working code. And if you have problems with that code ask us for help on Google Groups for easyrtc.

Resources