Advanced - Socket.io Join Room in external file on HTTP request - node.js

I am getting confused with this Node.js, Angular 13 and Socket IO scenario.
First of all let's asume we are already saving all required info in a Database, like roomId, roomOwner, username etc.
So, let's say we want to create an Online Quizz game using sockets to sync all players, 6 max for this scenario. HOWEVER, this is the problem...
On the Angular code there is this Service which is connecting Client
with Back-End
SocketService.ts
export class SocketService {
socket: any;
readonly url: string = "ws://localhost:3000";
constructor() {
this.socket = io(this.url)
}
}
On the Server side index.js inits webSocket
index.js
const app = express();
const io = require('./sockets/websocket')(app);
Inside webSocket.js we create the instance of socketIO to be exported and used across the whole back-end controllers as needed
webSocket.js
module.exports = function(app){
this.server = require('http').createServer(app);
this.socket = require('socket.io');
this.io = socket(server, {
cors: {
origin: "https://localhost:4200",
credentials: true
}
});
this.server.listen(3000, () => {
console.log("Socket IO is lestineng on port 3000");
});
io.on("connection", function (socket) {
console.log("A user connected");
});
this.registerSocketToRoom = function(roomId){
try{
console.log('[socket]','join room :',roomId)
io.join(roomId);
io.sockets.to(roomId).emit('user joined', socket.id);
}catch(e){
console.log('[error]','join room :',e);
io.emit('error','couldnt perform requested action');
}
} }
This is an example controller. We import the exported instance of SocketIO exported from webSocket.js file. Let's say we want to join a room if Client makes an http request to join a room HOWEVER, WE DID NOT joined the room "on socket connection" so we have to do it now. We try to use the exported method {registerSocketToRoom}.
GameRoomManagerController.js
require('../../sockets/websocket');
... // Some code here
exports.joinGameRoom = function(req, res){
const roomId = req.params.roomId;
console.log(roomId);
registerSocketToRoom(roomId);
return res.send({status: "success", msg: `joined Room: ${roomId}` });
}
When executing the process of creating a room -> saving the info to the DB -> Join Room the following error occurs.
TypeError: io.sockets.join is not a function
In theory this sound right to me, but I think I am misunderstanding the difference between io and socket.
Can someone explain to me what's going on here? Is it even possible
to export the same instance of io to be used in any place of the
back-end?
Is it even possible to join a room AFTER the connection was
created?
What's the difference between io and socket?

Before starting the topic, it is better to get acquainted with some terms from the socket.io library
io
In fact, it refers to all sockets connected to the server. You can
send messages individually, in groups, or to all sockets.
Your idea of ​​the socket that is written in this way
io.on('connection', socket => {
socket.on('message', data => {
});
});
In this section, you can only read the information related to this
event or you can transfer this information between sockets
Well, now we are going to solve this problem. The reason for this error is not following this hierarchy in your coding. I suggest you refer to the socket.io document next time and strengthen your foundation.
And finally, I will provide you with a simple example of the correct implementation method
let app = require('express')(),
http = require('http').Server(app),
io = require('socket.io')(http);
let listOfRoom = [];
io.on('connection', socket => {
let joinUserInRoom = (roomId) => {
if (socket.adapter.rooms.has(roomId) === false) {
listOfRoom.push(roomId);
socket.join(roomId);
}
},
leaveUserInRoom = (roomId) => {
if (listOfRoom.includes(roomId)) {
listOfRoom.splice(listOfRoom.indexOf(roomId), 1);
socket.leave(roomId);
}
};
socket.on('joinRoom', data => {
joinUserInRoom(data.roomId);
})
socket.on('disconnect', data => {
leaveUserInRoom(data.roomId);
});
socket.on('messageRoom', data => {
io.to(data.roomId).emit('eventMessageRoom', data); // send data in special room
});
});

Related

Socket IO not sending new sql data after client connects

I am creating a real-time app using. Socket IO, Node.js, Express.js, React for frontend, and Microsoft SQL for database. I only want to send data when the database is updated or when a new client is connected. Though when the client first connects, the IO connection fires off sending my data to the new client. But when I make a change to my database. The data never gets sent. My code is below. I feel as though I am close, but I am just missing something that makes the code work. I appreciate any kind of help.
const app = express();
const httpServer = require('http').createServer(app);
const io = require('socket.io')(httpServer);
const path = __dirname + '/views/';
let sqlQuery = require('./controllers/sqlController').queryDatabase;
let currentQueryData = {};
let connectedSocketID
let objectMatched = true;
app.use(express.static(path))
app.get('/', function (req,res) {
res.sendFile(path + "index.html");
});
// Function to emit data only when a change is found.
const sendData = (data, socket) => {
socket.emit('markerCreation', data);
}
// compare both objects and return a boolean value.
const compareObjects = (object1, object2) => {
return JSON.stringify(object1) === JSON.stringify(object2);
}
httpServer.listen(3001, () => {
console.log(`Server listening at ${3001}`);
})
io.on('connection', async socket => {
// Get new Query Data than compare the object with the currently saved Query data
let newQueryData = await sqlQuery();
objectMatched = compareObjects(currentQueryData, newQueryData)
if(!objectMatched) { // If objects matched is not true take the new data and save it in currentQueryData and send data to client.
currentQueryData = newQueryData;
sendData(currentQueryData, socket);
} else if (connectedSocketID !== socket.id) { // If socket is not already connected saved it in connected sockets and send data to client
connectedSocketID = socket.id;
sendData(currentQueryData, socket);
};
// Issue: Socket IO will stop sending to connected Client. If a new update happens on the sql database the change isn't passed along to
// the client.
});```

SocketIO emitting message to rooms doesn't work correctly

I'm creating an app that allows users to have conversations with other individual users.
As a user in the app, whenever someone I have a conversation with, is connecting to the app, I would like to get a message alerting me he is online.
For that purpose, I'm using node.js with socketIO and react.
The way I implemented the following in my server is:
const socketio = require('socket.io');
const io = socketio(server);
io.on('connection', async (socket) => {
const { user } = socket.request;
const userConversationIds = user.conversations;
socket.join(conversationIds);
let ioToConversations = io;
userConversationIds.forEach((conversationId) => {
ioToConversations = ioToConversations.to(conversationId);
});
ioToConversations.emit('online', `${user.firstName} ${user.lastName} is now online!`);
});
and on the client-side:
import io from 'socket.io-client';
const socket = io();
componentDidMount () => {
socket.on('online', (messageText) => {
console.log(messageText); // eslint-disable-line
});
};
All of the above resulted in a "user in now online" message, only to the now-connected-user itself.
I would want the other users in the conversation to have that message, and the connected user itself to have none.
What am I doing wrong here?
One mistake could be that you didn't connect to the server properly, you need to connect using this code and replacing <port> with your port number assuming that you are running locally:
io.connect("http://localhost:<port>")
Another mistake could be that you didn't used the method .io() and .emit() at the same time. What i mean by that is that you used the .emit() method after the forEach loop. You could try doing this instead:
io.on("connection", async (socket) => {
const { user } = socket.request;
const userConversationIds = user.conversations;
let ioToConversations = io;
userConversationIds.forEach((conversationId) => {
ioToConversations = ioToConversations
.to(conversationId)
.emit("online", `${user.firstName} ${user.lastName} is now online!`);
});
});
Another error could be because you didn't make the socket of each user join the specific rooms with the .join() method. You could do it with the following:
userConversationIds.forEach((conversationId) => {
socket.join(conversationId);
});
This code should be placed before the forEach loop that emits the message.
You could also join and emit at the same, however i have not tested with the following:
userConversationIds.forEach((conversationId) => {
socket.join(conversationId);
ioToConversations = ioToConversations
.to(conversationId)
.emit("online", `${user.firstName} ${user.lastName} is now online!`);
});

How to listen to socketIO private message in React client?

I have a SocketIO instance in an Express app, that listens to a React client requests. A user can send private messages to a specific person. The server receives the private message, and should dispatch it back to both sender & recipient thanks to the io.to(socketId).emit(content) method.
How to listen to this event in React and update the message array? In order to ease the process, I have created a connectedUsers object, whose keys are mongoDB's user._id, and whose values are the unique socketID generated by socketIO. This way, I can easily address message to specific persons in the client. Once sent, the messages are stored in a MongoDB database.
Here is the back-end. The point of interest is io.on("privateMessage")
const connectedUsers = {};
const socketManager = (io) => {
io.on("identifyUser", (user) => {
if (!([user.id] in connectedUsers)) {
connectedUsers[user.id] = io.id;
}
});
io.on("privateMessage", (data) => {
io.to(connectedUsers[data.recipientId]).emit(data.message);
io.to(connectedUsers[data.senderId]).emit(data.message);
});
io.on("disconnect", () => console.log("user disconnected!"));
};
Here is the listening function in React. Everything works but the "privateMessage" part.
async function getUser(socketId) {
try {
const res = await ax.get(`${serverUrl}/login`);
const socket = io(serverUrl);
socketId.current = socket;
socket.on("connect", () => {
socket.emit("identifyUser", { id: res.data._id });
socket.on("privateMessage", (data) =>
console.log("private message received!", data)
);
});
} catch (err) {
throw new Error(err);
}
}
Thanks for your help!
I think you need to put the socket.on("privateMessage") part outside the socket.on("connect") scope.
React must load all events at the beginning.
The backend side must be responsible for the authorization.
For the client there is connection event, not connect.
Subscription to event privateMessage should be outside connection callback.
This code should work. Hope this helps
import io from 'socket.io-client'
async function getUser(socketId) {
try {
const res = await ax.get(`${serverUrl}/login`);
const socket = io(serverUrl);
socketId.current = socket;
socket.on("connection", () => {
socket.emit("identifyUser", { id: res.data._id });
});
socket.on("privateMessage", (data) =>
console.log("private message received!", data)
);
} catch (err) {
throw new Error(err);
}
}

i used socketio in my nodejs app and my server slows down sometimes and sometimes it does not respond and sometimes it automatically reloads

i am building a Management App with Nodejs, Expressjs, MongoDB and Reactjs. i used socketio for realtime-ness. before using socektio, my app works fine i.e. it responds every request i made to my Nodejs API. when i used socketio, it slows down i.e. sometimes it does not respond to my queries or sometimes it automatically reloads and sends request from Client to Server. it does not show any errors. please help me to solve this issue. i have to deploy this system by next week and not getting how to solve this.
i made a separate file socket.js in which
let io;
module.exports = {
init: httpServer => {
io = require("socket.io")(httpServer, { wsEngine: "ws" });
return io;
},
getIO: () => {
if (!io) {
throw new Error("Socket is not initialized");
}
return io;
}
};
in my nodejs starting app index.js, i use this like this.
mongoose
.connect("mongodb://localhost/QuickResponse")
.then(() => {
console.log("Connected to MongoDB...");
const port = process.env.PORT || 5000;
const server = app.listen(port, () =>
console.log(`Listening on port ${port}...`)
);
const io = require("./socket").init(server);
io.on("connection", socket => {
console.log("New client connected");
socket.on("disconnect", () => {
console.log("Client is disconnected");
});
});
and whenever i want to use this in my other files, i use like this,
const io = require("../socket");
const complaint = await Complaint.findById(req.params.id);
if (!complaint)
return res.status(404).send("Complaint with given ID was not found.");
const admin = await Admin.findOne().limit(1);
complaint.assignedTo = {
_id: admin._id
};
complaint.assigned = false;
await complaint.save();
io.getIO().emit("complaints", {
action: "drop",
complaint: complaint
});
res.status(200).send("You have successfully dropped responsibility");
and on the frontend i have used socketio-client.
where i use like this.
import openSocket from "socket.io-client";
const socket = openSocket("http://localhost:5000", { reconnection: true });
socket.on("complaints", data => {
if (data.action === "new complaint") {
this.createNewComplaint(data.complaint);
toast.info(
`New Complaint has been registered with title "${
data.complaint.title
}"`
);});
i don't want my server to slow down or do not respond to my queries

Socket.io - Cannot emit to a specific room but can emit to everyone

I am using Socket.io 2.0.4 and React.js (CRA)
Background: In my server code (server.js) once someone sucessfully joins a room I want to emit an event that tells all clients in that room that someone joined.
Problem: I don't get an error but nothing get's transmitted to my client if I try to use .to(room) or .in(room) in conjuction with .emit... but .emit will work on it's own.
What Works: I am successfully able to implement to the socket.join() code and in the callback I console.log the IDs of each person that's joined using the showClients function I created. I can see each person join one at a time via console.
Notes: I store the room name in the data variable and access it using data.room but I've also just wrote in the room name manually to no avail.
Client Code (abridged)
import React, {Component} from 'react';
import Card from '../card/card';
import './game.css';
const io = require('socket.io-client');
const socket = io()
class Game extends Component {
constructor(props){
super();
}
componentDidMount(){
this.gameCode();
this.cardSelected();
socket.on("cardDiscarded", this.updateDiscard);
socket.on("playerJoined", () => {
alert("hi!");
console.log("YAY!!!!!!!!!!!!!!!!!!!!");
});
}
//....rest of my code....
}
Server Code (abridged)
Look at the joinRoom function to see the issue
const express = require('express');
const app = express();
const port = process.env.PORT || 5000;
const http = require("http").Server(app);
var io = require("socket.io")(http);
var bodyParser = require('body-parser');
console.log("Hello world!");
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
io.on("connection", (socket) => {
socket.on("createNewRoom", (name) =>{
CreateNewRoom();
console.log("NEW ROOM CREATED!");
})
socket.on("joinRoomRequest", (name, room) =>{
// console.log(name, room);
console.log("Request to join room");
var data = {
name: name,
room: room
};
joinRoom(data);
})
function CreateNewRoom() {
//Get unique room number
var thisGameId = (Math.random() * 1000000 ) | 0;
//Send the room number to the browser
socket.emit('newRoomCreated', {roomID: thisGameId, mySocketID: socket.id});
//Tell socket.io this user is joining this room
socket.join(thisGameId.toString());
console.log(thisGameId);
};
function joinRoom(data){
console.log("trying to join room:" + data.room);
data.socketID = socket.id;
console.log(data);
socket.join(data.room, () => {
let rooms = Object.keys(socket.rooms);
//Let the clients know a player has joined
console.log(rooms);
console.log(data.name + " JOINED room " + data.room);
showClients(data.room);
io.to(data.room).emit("playerJoined"); //<----- DOESN't WORK
});
}
function showClients(room){
var roomClients = io.of('/').in(room).clients((err, data)=>{
if (err) throw err;
console.log("The people in room ", room, " are: ", data);
})
}
})
I managed to solve it like this:
does not work:
socket.join (my_room)
work:
socket.join (my_room.toString ())
I believe io.in(room).emit(data) is what you are looking for. I recently ran into this problem as well. According to the documentation, if you want to emit to everyone accept 'socket' (aka the user who joined) you use socket.to(room).emit. If you want to emit to everyone including the user, you use io.in(room).emit

Resources