Socket.io with express, specific room - node.js

I want to send event to only an specific client when an express rute is triggered.
I keep online users in io.onlineUsers:
socket.on("log-in", (userWithRoles) => {
const userSocket = {
id: userWithRoles.id,
roles: userWithRoles.roles,
socketId: socket.id,
};
if (io.onlineUsers === undefined) {
io.onlineUsers = [
{ ...userSocket, socketIds: [userSocket.socketId], room: socket.id },
];
} else {
if (
io.onlineUsers.map((uSocket) => uSocket.id).includes(userSocket.id)
) {
const existingUserSocket = io.onlineUsers.find(
(us) => us.id === userSocket.id
);
existingUserSocket.socketIds.push(userSocket.socketId);
socket.join(existingUserSocket.room);
return;
}
io.onlineUsers.push({
...userSocket,
socketIds: [userSocket.socketId],
room: socket.id,
});
}
});
I attach io via:
httpsServer.listen(process.env.PORT || 5000, () => {
const io = createIo(httpsServer);
app.io = io;
console.log(
`Server running on port ${
process.env.PORT || 5000
}. Go to https://localhost:5000/.`
);
});
I am looking for room/socketId in a router via:
const getAllCookRoles = async (req, res) => {
const cookRoles = await CookRoleUser.find()
.populate({ path: "user", select: "username name" })
.populate({ path: "cooks" });
console.log(req.app.io.onlineUsers);
const cookRoom = req.app.io.onlineUsers?.find((ou) =>
ou.roles.map((ouRole) => ouRole.name).includes(COOK)
)?.room;
console.log(cookRoom);
if (cookRoom) {
console.log(cookRoom);
req.app.io.sockets.to(cookRoom).emit("page-visit");
}
res.json(cookRoles);
};
And the client listener is:
useEffect(() => {
const socket = io("https://localhost:5000");
// receive a message from the server
socket.on("new-order", (socketData) => {
console.log("neworder");
console.log(socketData);
});
socket.on("page-visit", () => {
console.log("page-visit");
})
}, []);
When i do req.io.emit("page-visit") everyithing works like a charm.
Contrarly, besides fiding and logging the cook room, when i do req.app.io.sockets.to(cookRoom).emit("page-visit"); nothing happens. With .sockets part and w/out.
How could I send event to specific room, not everyone, from express route?

Related

Why is socket.io not emitting to everyone within a room?

We are creating a multiplayer quiz app that uses socket.io and React. We have got a create room and join room working, but when the host in the lobby starts the game only they progress to the question page, and no one else in the lobby goes anywhere.
This is our code in app.js
import io from "socket.io-client";
const URL = "http://localhost:3001"
const {
difficulty,
category,
socket,
setSocket,
setAllPlayers,
setUserData
} = useContext(QuizContext)
useEffect(() => {
const newSocket = io(URL)
newSocket.on("update_room", (users) => {
setAllPlayers(users);
setUserData(prev => {
return {...prev, room: users[0]?.room}
});
});
}, [])
useEffect(() => {
if(socket) {
socket.on("game_started", () => {
console.log(2);
navigate("/quiz")
});
}
}, [socket]);
This is the code in WaitingRoom.js
<button onClick={startQuiz}>Start</button
const startQuiz = () => {
socket.emit("start_quiz", { room: userData.room, data: userData.data });
};
This is the code in our backend on socketEvents/index.js
socket.on("start_quiz", ({ room, data }) => {
users = users.map((user) => {
return { ...user, data };
});
io.to(room).emit("game_started", users);
});
We've looked at all the socket.io documentation and can't figure out why it's not working.

my video chat app (react.js + node.js) interface not showing video output while users are connected throgh different wifi connection

I was trying to build an react.js app with node.js integrated in backend video chatting app . after deploying(frontend in Netlify and backend in Heroku) the app I tested the app connecting both same and different wifi . My app is working fine when both the devices are connected in same wifi but it don't show any video output while connected in different wifi/data connection .I am sharing all the code , please ask me anything if you want to know anything
code used is backend :
const app = require("express")();
const server = require("http").createServer(app);
const cors = require("cors");
const io = require("socket.io")(server, {
cors: {
origin: "*",
methods: [ "GET", "POST" ]
}
});
app.use(cors());
const PORT = process.env.PORT || 5000;
app.get('/', (req, res) => {
res.send('Running the chat server');
});
io.on("connection", (socket) => {
socket.emit("me", socket.id);
socket.on("disconnect", () => {
socket.broadcast.emit("callEnded")
});
socket.on("callUser", ({ userToCall, signalData, from, name }) => {
io.to(userToCall).emit("callUser", { signal: signalData, from, name });
});
socket.on("answerCall", (data) => {
io.to(data.to).emit("callAccepted", data.signal)
});
});
server.listen(PORT, () => {
console.log(`Server is running for chat app on port ${PORT}`)
});
code used in frontend(all the Js code was written in one single page) :
import React, { createContext, useState, useRef, useEffect } from 'react';
import { io } from 'socket.io-client';
import Peer from 'simple-peer';
const SocketContext = createContext();
const socket = io('https://peaceful-caverns-09507.herokuapp.com/');
const ContextProvider = ({ children }) => {
const [callAccepted, setCallAccepted] = useState(false);
const [callEnded, setCallEnded] = useState(false);
const [stream, setStream] = useState();
const [name, setName] = useState('');
const [call, setCall] = useState({});
const [me, setMe] = useState('');
const myVideo = useRef();
const userVideo = useRef();
const connectionRef = useRef();
useEffect(() => {
navigator.mediaDevices.getUserMedia({ video: true, audio: true })
.then((currentStream) => {
setStream(currentStream);
myVideo.current.srcObject = currentStream;
});
socket.on('me', (id) => setMe(id));
socket.on('callUser', ({ from, name: callerName, signal }) => {
setCall({ isReceivingCall: true, from, name: callerName, signal });
});
}, []);
const answerCall = () => {
setCallAccepted(true);
const peer = new Peer({ initiator: false, trickle: false, stream });
peer.on('signal', (data) => {
socket.emit('answerCall', { signal: data, to: call.from });
});
peer.on('stream', (currentStream) => {
userVideo.current.srcObject = currentStream;
});
peer.signal(call.signal);
connectionRef.current = peer;
};
const callUser = (id) => {
const peer = new Peer({ initiator: true, trickle: false, stream });
peer.on('signal', (data) => {
socket.emit('callUser', { userToCall: id, signalData: data, from: me, name });
});
peer.on('stream', (currentStream) => {
userVideo.current.srcObject = currentStream;
});
socket.on('callAccepted', (signal) => {
setCallAccepted(true);
peer.signal(signal);
});
connectionRef.current = peer;
};
const leaveCall = () => {
setCallEnded(true);
connectionRef.current.destroy();
window.location.reload();
};
return (
<SocketContext.Provider value={{
call,callAccepted,myVideo,userVideo,stream,name,setName,callEnded,me,callUser,leaveCall,answerCall}}>
{children}
</SocketContext.Provider>
);
};
export { ContextProvider, SocketContext };

How to use socketio inside controllers?

I have a application created with Reactjs,Redux,Nodejs,MongoDB. I have created socketio in backend side.
server.js
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const app = express();
const routes = require('./routes/api/routes');
var server = require('http').Server(app);
const io = require('./socket').init(server);
app.use(express.json());
require('dotenv').config();
mongoose.connect(process.env.MONGO_DB).then(console.log('connected'));
const corsOptions = {
credentials: true, //access-control-allow-credentials:true
optionSuccessStatus: 200,
};
app.use(cors(corsOptions));
app.use(routes);
if (
process.env.NODE_ENV === 'production' ||
process.env.NODE_ENV === 'staging'
) {
// Set static folder
app.use(express.static('client/build'));
app.get('*', (req, res) => {
res.sendFile(path.resolve(__dirname, 'client', 'build', 'index.html'));
});
}
io.on('connection', (socket) => {
console.log('New client connected');
socket.on('disconnect', () => {
console.log('Client disconnected');
});
});
server.listen(process.env.PORT || 5000, () => {
console.log('socket.io server started on port 5000');
});
socket.js
let io;
module.exports = {
init: (httpServer) => {
return (io = require('socket.io')(httpServer, {
cors: {
origin: 'http://localhost:3000',
methods: ['GET', 'POST'],
},
}));
},
getIO: () => {
if (!io) {
throw new Error('Socket.io is not initialized');
}
return io;
},
};
In controller.js I am creating new item to inside mongodb and also using socketio. I am emitting new Item that I created. It looks like that
controller.js
const createTeam = async (req, res) => {
const userId = req.user.id;
const newItem = new Item({
name: req.body.name,
owner: userId,
});
await newItem.save((err, data) => {
if (err) {
console.log(err);
return res.status(500).json({
message: 'Server Error',
});
}
});
await newItem.populate({
path: 'owner',
model: User,
select: 'name',
});
await User.findByIdAndUpdate(
userId,
{ $push: { teams: newItem } },
{ new: true }
);
io.getIO().emit('team', {
action: 'creating',
team: newItem,
});
res.json(newItem);
};
In frontend side I am listening the socketio server with socket.io-client. In my App.js I can see data that come from backend side when I console.log(data). My app working perfect until this stage. I can take the data from socketio, I can see the new client that connected. But when I send the data with dispatch, I app start to add infinite new items to database. Take a look at my App.js
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import { AppNavbar } from './components/AppNavbar';
import { TeamList } from './components/TeamList';
import { loadUser } from './Store/Actions/AuthActions';
import { useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { io } from 'socket.io-client';
import { addItems } from './Store/Actions/itemActions';
function App() {
const dispatch = useDispatch();
useEffect(() => {
dispatch(loadUser());
const socket = io('http://localhost:5000');
socket.on('team', (data) => {
if (data.action === 'creating') {
dispatch(addItems(data.team));
}
// console.log(data);
});
}, []);
return (
<div className="App">
<AppNavbar />
<TeamList />
</div>
);
}
export default App;
My itemAction in redux side is also like that
export const addItems = (input) => (dispatch, getState) => {
axios
.post('/api/items/createTeam', input, tokenConfig(getState))
.then((res) =>
dispatch({
type: ADD_ITEM,
payload: res.data,
})
)
.catch((err) =>
dispatch(
returnErrors(err.response.data, err.response.status, 'GET_ERRORS')
)
);
};
My problem is how to stop infinite callling of api after implement socketio. How to stop infinite loop efficiently?
Infinite loop is steming from not dispatching "type: ADD_ITEM" once.This issue cause that your itemAction always would dispatch "type: ADD_ITEM" and payload with it when after every fetching then your react would re-render page again and again.
You should get rid of your dispatching action inside of addItems function and dispatch your action only inside of useEffect in App.js file .
Your code snippet should look like this in App.js:
useEffect(() => {
//...
const socket = openSocket('http://localhost:5000');
socket.on('postsChannel', (data) => {
if (data.action === 'creating') {
dispatch({ type: ADD_ITEM, payload: data.team });
}
});
}, [dispatch]);

Browsers hangs while using Socket.io with ReactJS and ExpressJS

Problem:
I am working on a chat application. When I send more than 9-10 requests, the browser slows down and eventually, it just hangs. On refreshing the page, everything is back to normal.
I searched the socket.io documentation but couldn't get any solution regarding this matter.
Code:
Here is my Backend Express.JS code:
index.js
const express = require("express");
const { addUser, getUser, deleteUser, getAllUsers } = require("./users_api");
const app = express();
const server = require("http").createServer(app);
const io = require("socket.io")(server, {
cors: {
origin: "http://localhost:3000",
methods: ["GET", "POST"],
},
});
const port = 5000 || process.env.PORT;
io.on("connection", (socket) => {
socket.on("join", ({ name, room }, callback) => {
const { err, user } = addUser({ id: socket.id, name, room });
if (err) return callback(err);
if (user) {
socket.emit("message", {
user: "admin",
text: `${user.name} has entered the room ${user.room}.`,
});
socket.broadcast.to(user.room).emit("message", {
user: "admin",
text: `${user.name} has joined the room.`,
});
socket.join(user.room);
io.to(user.room).emit("users", {
room: user.room,
users: getAllUsers(user.room),
});
callback();
}
});
socket.on("client_message", (msg) => {
const user = getUser(socket.id);
if (user) io.to(user.room).emit("message", { user: user.name, text: msg });
});
socket.on("disconnect", () => {
console.log(6);
const user = deleteUser(socket.id);
if (user) {
io.to(user.room).emit("message", {
user: "admin",
text: `${user.name} has left the room.`,
});
io.to(user.room).emit("users", {
room: user.room,
users: getAllUsers(user.room),
});
}
});
});
server.listen(port, () => console.log(`Server started at port ${port}.`));
users_api.js:
const current_users = [];
const addUser = ({ id, name, room }) => {
name = name.trim().toLowerCase().split(" ").join("_");
room = room.trim().toLowerCase();
const exist_user = current_users.find(
(user) => name === user.name && room === user.room
);
if (exist_user)
return {
err: "User with this name already exists.",
};
current_users.push({ id, name, room });
return { user: { id, name, room } };
};
const deleteUser = (id) => {
const index = current_users.findIndex((user) => user.id === id);
if (index !== -1) return current_users.splice(index, 1)[0];
};
const getUser = (id) => current_users.find((user) => user.id === id);
const getAllUsers = (room) =>
current_users.filter((user) => user.room === room);
module.exports = { addUser, deleteUser, getUser, getAllUsers };
Front-end Code:
import io from "socket.io-client";
import React, { useEffect, useRef, useState } from "react";
import queryString from "query-string";
import "./ChatPage.css";
const END_POINT = "http://localhost:5000";
const ChatPage = (props) => {
const [message, setMessage] = useState("");
const [messages, setMessages] = useState([]);
const [users, setUsers] = useState([]);
const socket = useRef(null);
const handleSubmit = () => {
socket.current.emit("client_message", message);
setMessage("");
};
useEffect(() => {
const { name, room } = queryString.parse(props.location.search);
socket.current = io(END_POINT);
socket.current.emit("join", { name, room }, (error) => {
if (error) alert(error);
});
return () => {
socket.current.disconnect();
socket.current.off();
};
}, [props.location.search, END_POINT]);
useEffect(() => {
socket.current.on("message", (msg) => {
setMessages([...messages, msg]);
});
socket.current.on("users", ({ users }) => {
setUsers(users);
});
}, [messages.length]);
return (
<div className="chat-room-container">
<div className="chat-room-left">
<ul>
{messages.map((message, i) => (
<li key={i}>
{message.name}
{message.text}
</li>
))}
</ul>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
<button type="button" onClick={handleSubmit}>
Submit
</button>
</div>
<div className="chat-room-right">
<ul>
{users.map((user, i) => (
<li key={i}>{user.name}</li>
))}
</ul>
</div>
</div>
);
};
export default ChatPage;
I don't understand what's wrong, please help.
Instead of sending a message on event or broadcast try to send list of messages to particular user or all users list with their messages belonging to same room. This will leads to less render in frontend.
For example,
Your Frontend code will change like,
useEffect(() => {
socket.current.on("message", (msgList) => {
setMessages(msgList);
});
socket.current.on("users", ({ users }) => {
setUsers(users);
});
}, [messages.length]);
Also if different rooms then there occurs a problem to filter the messages. So to solve this there are two possible ways:
When you send filter data from server.
Filter data in client according to user name and room name.
As this solution is not a good practice as every time the whole list of message will come and applying filter on that will increase time complexity. But you can reduce to some extent by making proper structure of data state on server.
I think this will help you.
You are creating new socket connection on every useEffect, so after ten messages, you have ten connections.
socket = io(END_POINT);
I store the created socket connection in useRef - so if it is created again, it is overwritten with the new one - and it does not duplicate.
const socketConection = useRef(null)
useEffect(() => {
socketConnection.current = io(END_POINT)
}, [deps])
Of course, in all following uses you have to use socketConection.current

Hooking up a websocket to AWS Elastic Beanstalk

I'm having an issue with connecting my websocket on port 7777 to my aws deployment. I am new to sockets and deployment. I have my main port 8081 hooked up, but having issues with the websocket. I've been at this for a day and would like some pointers! Thanks!
Currently, receiving this error:
React code
import React from 'react';
import openSocket from 'socket.io-client';
let socket = openSocket('http://my-api.us-east-2.elasticbeanstalk.com:7777', { transports: ['websocket'] });
export class HomePage extends React.Component {
state = {
channels: null,
channelSelected: false,
socket: null,
typers: {},
channel: 1,
messages: [],
msg: ''
}
socket;
componentDidMount() {
}
componentDidUpdate() {
if (this.props.username && !this.state.channelSelected) {
this.configureSocket();
this.setState({ channel: 1, channelSelected: true });
this.socket.emit('joinChannel', { channel: 1, username: this.props.username });
}
}
configureSocket = () => {
socket.on('connection', () => {
if (this.state.channel) {
this.handleChannelSelect(this.state.channel.id);
}
});
socket.on('incomingChat', username => {
let typers = this.state.typers;
if (!typers[username]) {
typers[username] = 1;
this.setState({ typers })
}
});
socket.on('clearingChat', username => {
let typers = this.state.typers;
delete typers[username]
this.setState({ typers });
});
socket.on('incomingMessage', details => {
let messages = this.state.messages;
let typers = this.state.typers;
delete typers[details.username]
messages.push(details);
this.setState({ messages, typers });
})
socket.on('channel', channel => {
let channels = this.state.channels;
channels.forEach(c => {
if (c.id === channel.id) {
c.participants = channel.participants;
}
});
this.setState({ channels });
});
socket.on('message', message => {
let channels = this.state.channels
channels.forEach(c => {
if (c.id === message.channel_id) {
if (!c.messages) {
c.messages = [message];
} else {
c.messages.push(message);
}
}
});
this.setState({ channels });
});
this.socket = socket;
}
handleChannelSelect = e => {
e.preventDefault();
this.setState({ channel: 1 });
this.socket.emit('leaveChannel', this.props.username);
}
handleSendMessage = e => {
e.preventDefault();
this.socket.emit('send-message', { msg: this.state.msg, username: this.props.username, channel: this.state.channel });
this.setState({ msg: '' });
}
handleChange = e => {
const { value } = e.target;
this.setState({ msg: value });
if (!value) this.socket.emit('addingMessage', { username: this.props.username, msg: 'cleared' });
if(value) this.socket.emit('addingMessage', { username: this.props.username, msg: value });
}
render() {
return (
<div className='chat-app'>
<form onSubmit={this.handleSendMessage}>
<input type="text" onChange={this.handleChange} value={this.state.msg} />
</form>
<button onClick={this.handleChannelSelect}></button>
{ this.state.messages.length > 0 && this.state.messages.map(({ username, msg }) => <p key={Math.random()}>{`${username}: ${msg}`}</p>) }
{Object.keys(this.state.typers).length > 0 && Object.keys(this.state.typers).map(el => el !== this.props.username && <p>{`${el} typing....`}</p>)}
</div>
);
}
}
server code
const app = require("./app");
const socketio = require('socket.io');
const express = require('express');
const http = require('http');
const db = require("./db");
const AWS = require("aws-sdk");
const CryptoJS = require("crypto-js");
const em = express();
const server = http.createServer(em);
const chatPort = process.env.LIVE_CHAT_PORT || 7777;
const io = socketio(server);
server.listen(chatPort, () => {
console.log(`Live Chat sock running on port ${chatPort}`)
});
app.listen(process.env.PORT || 8081, () => {
console.log("Listening on PORT 8081");
});
io.on('connection', socket => {
let user;
socket.on('joinChannel', async ({ username, channel }) => {
user = userJoin(socket.id, username, channel);
socket.on('addingMessage', details => {
const { username, msg } = details;
if (msg === 'cleared') {
io.to(user.room).emit('clearingChat', username);
} else {
io.to(user.room).emit('incomingChat', username);
}
})
socket.join(user.room);
});
socket.on('send-message', async details => {
const { msg, username, channel } = details;
let em = await db.query(`
INSERT INTO messages(conversation_id, user_id, shared_post, message, posted)
VALUES($1, $2, null, $3, current_timestamp)
RETURNING id;
`, [channel, username, msg])
io.to(user.room).emit('incomingMessage', details)
})
socket.on('leaveChannel', username => {
const user = userLeave(username);
socket.disconnect();
});
});

Resources