Using MongoDB for real-time chat database - node.js

I am working on a personal project to increase my skill and experience with Nodejs, Express, Socket.io and MongoDB. I seem to have hit a wall in formulating how the database should work for this sort of app.I have been thinking about it and could use some help from anyone who can take some time. My application allows the user to enter a username and a choose a room title. After this they are loaded into the room and others can join that room and chat in real time. I want to persist the data, saving it for each room on disconnect and repopulating it on connect.
Each Room has a name associated with it and the messages themselves. Each message has a name of sender, the timestamp, and the text/content.
But when it comes to actually structuring the models and how to organize collections, I am getting confused. Can anyone help me out or set me on the right path for this kind of application?
server.js(backend)
require('dotenv').config();
const path = require('path');
const http = require('http');
const express = require('express');
const socketio = require('socket.io');
const mongoose = require('mongoose');
const formatMessage = require('./utils/messages');
const {
userJoin,
getCurrentUser,
userLeave,
getRoomUsers
} = require('./utils/users');
const app = express();
const server = http.createServer(app);
const io = socketio(server);
// Set static folder
app.use(express.static(path.join(__dirname, 'public')));
const botName = 'ChatCord Bot';
const messages = [];
//Database connection
const uri = process.env.ATLAS_URI;
mongoose.connect(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true
});
const connection = mongoose.connection;
connection.once('open', () => {
console.log("MongoDB database connection established successfully");
})
// Run when client connects
io.on('connection', socket => {
socket.on('joinRoom', ({
username,
room
}) => {
const user = userJoin(socket.id, username, room);
socket.join(user.room);
// Welcome current user
socket.emit('message', formatMessage(botName, 'Welcome to ChatCord!'));
//Load messages for room from database
socket.broadcast.to(user.room).emit(
'message', formatMessage()
)
// Broadcast when a user connects
socket.broadcast
.to(user.room)
.emit(
'message',
formatMessage(botName, `${user.username} has joined the chat`)
);
// Send users and room info
io.to(user.room).emit('roomUsers', {
room: user.room,
users: getRoomUsers(user.room)
});
});
// Listen for chatMessage
socket.on('chatMessage', msg => {
const user = getCurrentUser(socket.id);
io.to(user.room).emit('message', formatMessage(user.username, msg));
});
// Runs when client disconnects
socket.on('disconnect', () => {
const user = userLeave(socket.id);
if (user) {
io.to(user.room).emit(
'message',
formatMessage(botName, `${user.username} has left the chat`)
);
// Send users and room info
io.to(user.room).emit('roomUsers', {
room: user.room,
users: getRoomUsers(user.room)
});
}
//Save messages for room to database
});
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => console.log(`Server running on port ${PORT}`));
main.js(frontend)
const chatForm = document.getElementById('chat-form');
const chatMessages = document.querySelector('.chat-messages');
const roomName = document.getElementById('room-name');
const userList = document.getElementById('users');
//Get username and room from URL
const {username, room } = Qs.parse(location.search, {
ignoreQueryPrefix: true
});
console.log(username, room);
const socket = io();
//Join chatroom
socket.emit('joinRoom', {username, room});
//Get room and users
socket.on('roomUsers', ({ room, users }) => {
outputRoomName(room);
outputUsers(users);
})
socket.on('message', message => {
outputMessage(message);
//Scroll down on new message
chatMessages.scrollTop = chatMessages.scrollHeight;
});
//Message submit
chatForm.addEventListener('submit', (e) => {
e.preventDefault();
//Get message text
const msg = e.target.elements.msg.value;
//Emit message to server
socket.emit('chatMessage',msg);
//Clear input
e.target.elements.msg.value = '';
e.target.elements.msg.focus();
});
//Output message to DOM
function outputMessage(message) {
const div = document.createElement('div');
div.classList.add('message');
div.innerHTML = `<p class="meta">${message.username} <span>${message.time}</span></p>
<p class="text">
${message.text}
</p>`;
document.querySelector('.chat-messages').appendChild(div);
}
//Add room name to DOM
function outputRoomName(room) {
roomName.innerText = room;
}
//Add users to DOM
function outputUsers(users) {
userList.innerHTML = `${users.map(user => `<li>${user.username}</li>`).join('')}`;
}
room.model.js
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const roomSchema = new Schema({
id: mongoose.ObjectId,
messages: [
{
id: mongoose.ObjectId,
authorUsername: String,
time: Date,
content: String
}
]
});
const Room = mongoose.model("Room", roomSchema);
module.exports = Room;
users.js
const users = [];
//Join user to chat
function userJoin(id, username, room) {
const user = {id, username, room};
users.push(user);
return user;
}
//Get current user
function getCurrentUser(id) {
return users.find(user => user.id === id);
}
//User leaves chat
function userLeave(id) {
const index = users.findIndex(user => user.id === id);
if(index !== -1) {
return users.splice(index, 1)[0];
}
}
//Get room users
function getRoomUsers(room) {
return users.filter(user => user.room === room);
}
module.exports = {
userJoin,
getCurrentUser,
userLeave,
getRoomUsers
}

A simple approach is structuring the db with only one schema, the Room schema.
When a user sends a message in a room, push to the messages array of that same room.
Then, in socket.io (node) you emit a socket (with the room id) and send the message, and in the client side, set the user to receive all sockets of that room.
Room schema:
{
id,
messages: [
{
id,
authorUsername,
content
}
]
}

Related

App stops working when i keep sending multiple messages with socket.io

I have a react native expo CLI app add i am trying to create a in app built messaging app like whatsapp inside. All my api calls work in storing the messages and get all the conversations. I have an all messages screen is shows all the conversation the user has with the last message sent inside the chat and a chat screen that shows all the messages sent inside that conversation.
I am using socket.io-client and everything works accordingly except when I keep sending multiple messages my backend stops connecting to my app, then I need to reload my app again then the same happens. Why is that happening?
Also, something is wrong with my code, the messages are getting emitted multiple times.
Here is my code
NodeJS
const server = http.createServer(app);
const io = socket(server)
io.on('connection', (socket) => {
// connection
console.log('User ' + socket.id + ' connected')
// joining a room
socket.on('subscribe', (room) => {
console.log('user socket', socket.id, 'joining room', room);
socket.join(room);
});
// send message within room
socket.on('message', (data) => {
io.in(data.conversationId).emit('send_message', {
message: data.message,
receiverId: data.to,
senderId: data.from,
id: data.conversationId,
data: data.data
});
})
// seen message
socket.on('markSeen', (data) => {
// Emit 'markedSeen' event
console.log(socket.id, 'has seen your message')
io.emit('markedSeen', data);
});
// main socket listening
socket.on('listening', (data) => {
console.log(data)
console.log('user socket', socket.id, 'is listening', data.conversationId, data.message)
io.emit('socketListening', data)
});
// user typing within room
socket.on('typing', (data) => {
console.log('user socket', socket.id, 'in room', data.chatId, data.text)
io.in(data.chatId).emit('typingResponse', data.text)
});
// waiting to enter room
socket.on('waiting', (data) => {
console.log('user', socket.id, 'is waiting.');
});
// disconnect
socket.on('disconnect',() => {
console.log('USER DISCONNECTED', socket.id)
})
socket.on('unsubscribe', (data) => {
console.log(data)
socket.leave(data.id);
socket.to(data.id).emit('user left', socket.id);
console.log('user left', socket.id)
});
})
React Native
Messages Screen
const [listings, setListings] = useState([]);
const [error, setError] = useState(false);
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const loadListings = async() => {
setLoading(true);
const response = await messagesApi.getMessages();
setLoading(false);
if(refreshing) setRefreshing(false);
if (!response.ok) return setError(true);
setError(false);
setListings(response.data)
};
const { user } = useAuth();
const socketUrl = 'IP_ADDRESS';
let socket = useRef(null);
useEffect(()=>{
loadListings();
socket.current = io.connect(socketUrl)
socket.current.on('connect', msg => {
console.log(`user: ${user.id} is waiting.`)
console.log(`user: ${user.id} is waiting. socketID: ${socket.current.id}`)
socket.current.emit('waiting', user.id);
});
return(() => {
console.log(`user: ${user.id} --- socketid ${socket.current.id} deleted`)
socket.current.removeAllListeners('connect')
});
}, [socketUrl])
useEffect(() => {
socket.current.on("socketListening", (msg) => {
console.log('MSG',msg)
const result = listings.find(e => e.id === msg.conversationId)
if (result !== undefined) {
const resultMessages = result.Messages;
const newMessages = [msg,...resultMessages]
result.Messages = newMessages;
result.lastMessage = msg.message;
const arraywithoutrecord = listings.filter(e=>e.id!=msg.conversationId)
setListings([result,...arraywithoutrecord])
}
})
socket.current.on('markedSeen', (message) => {
const result = listings?.find(e => e.id === message)
if (result !== undefined) {
result.Messages[0].seenByUser = true;
const arraywithoutrecord = listings?.filter(e=>e.id!=message)
setListings([result,...arraywithoutrecord])
}
});
}, [listings]);
Chat Screen
const message = route.params.message;
const [messages, setMessages] = useState([]);
const [refreshing, setRefreshing] = useState(false);
const [text, setText] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
const[isAdminTyping, setIsAdminTyping] = useState(false)
const { user } = useAuth();
const socketUrl = 'IP_ADDRESS';
let socket = useRef(null);
useEffect(() => {
setMessages(message.Messages)
socket.current = io.connect(socketUrl)
socket.current.on('connect', msg => {
console.log(`user: ${user.id} has joined conversation ${message.id}`,'connection socket id', socket.current.id)
socket.current.emit('subscribe', message.id);
socket.current.emit('markSeen', message.id);
});
socket.current.on("send_message", (msg) => {
setMessages(messages => [msg, ...messages]);
socket.current.emit('markSeen', message.id);
});
socket.current.on("typingResponse", (msg) => {
if (msg === 'Admin is typing...') {
setIsAdminTyping(true)
} else {
setIsAdminTyping(false)
}
})
return(() => {
console.log(`user: ${user.id} left the room ${message.id} --- socketid ${socket.current.id}`)
socket.current.close()
});
}, [socketUrl]);
const seenByUser = async () => {
const response = await messagesApi.seenByUser({conversationId:message.id});
if (response.ok) {
console.log('all messages seen')
}
}
useEffect(() => {
seenByUser();
},[messages])
const onSend = async(conversationId, senderId, receiverId, message) => {
const response = await messagesApi.sendMessage({ conversationId, senderId, receiverId, message });
if (!response.ok) return setError(true);
console.log("sent")
const to = (user.id === route.params.message.user ?route.params.message.admin : route.params.message.user)
socket.current.emit('message', { to: to, from: user.id, message, conversationId, data: response.data });
setText("")
socket.current.emit('listening',response.data);
};
const onFocus = () => {
socket.current.emit('typing', { chatId: message.id, text: 'User is typing...' });
}
const onBlur = () => {
socket.current.emit('typing', { chatId: message.id, text: 'User is NOT typing...' });
}
My entire Node index.js file
const express = require("express");
const users = require("./routes/users");
const auth = require("./routes/auth");
const listing = require("./routes/listing");
const helmet = require("helmet");
const compression = require("compression");
const config = require("config");
const app = express();
var cors = require('cors')
app.use(cors())
//Socket.io
const http = require("http");
const socket = require("socket.io")
const server=http.createServer(app);
const io =socket(server,
{
cors: {
origin:
[
"http://localhost:3000", /\.localhost\:3000$/,"IP_ADDRESS"
]
,
methods: ["GET", "POST"],
credentials: true
},
maxHttpBufferSize: 4e6 // 4Mb
}
)
io.on('connection',(socket)=>{
// connection
console.log('User '+socket.id+' connected')
// joining a room
socket.on('subscribe', (room)=> {
console.log('user socket',socket.id,'joining room', room);
socket.join(room);
});
// send message within room
socket.on('message', (data) => {
socket.to(data.conversationId).emit('send_message', {
message: data.message, receiverId: data.to,senderId:data.from,id:data.conversationId,data:data.data
});
})
// seen message
socket.on('markSeen', (data)=> {
// Emit 'markedSeen' event
console.log(socket.id,'has seen your message')
io.emit('markedSeen', data);
});
// main socket listening
socket.on('listening', (data) => {
console.log(data)
console.log('user socket',socket.id,'is listening',data.conversationId,data.message)
io.emit('socketListening',data)
}
);
// user typing within room
socket.on('typing', (data) => {
console.log('user socket',socket.id,'in room',data.chatId,data.text)
io.in(data.chatId).emit('typingResponse', data.text)
}
);
// waiting to enter room
socket.on('waiting', (data)=> {
console.log('user',socket.id, 'is waiting.');
});
// disconnect
socket.on('disconnect',(reason)=>{
socket.disconnect()
console.log('socket disconnected',socket.disconnect().disconnected)
console.log('USER DISCONNECTED',socket.id)
})
})
app.use(express.json({limit: '50mb'}));
app.use(express.urlencoded({limit: '50mb', extended: true, parameterLimit: 500000}));
app.use(express.static("public"));
app.use(helmet());
app.use(compression());
app.use("/api/users/", users);
app.use("/api/auth/", auth)
app.use("/api/listings/", listing);
const port = process.env.PORT || config.get("port");
server.listen(port, function() {
console.log(`Server started on port ${port}...`);
});
Cant speak to why it stops connecting, would really need more details on that one. For example, is it 'new' connections, or is your current connection dropping etc.
The whole point of WS is a persistent connection, so, once a user is connected, they should not need to reconnect again (unless they got disconnected)
Without knowing the whole flow of things in your app ( this is just going off what you have posted here )
It looks like you are having a user connect 'each time' they hit that page, if my assumption is correct here, then don't do that :D
This kind of leads into your other question about multiple messages being sent and then ties back into the whole websocket idea in the first place, its a persistent connection so you only need to do thing once.
For example, if you subscribe to a channel or room, you only need to do that one time, once you subscribe to a channel that socketID is tied to those emits, and a socket can subscribe to a channel multiple times. Knowing that, if, when a user hits your messages screen, and your code goes through and subscribes them to that channel each time they will in turn get multiple emits.
Again, without seeing the full flow of data it looks like every time a user hit your chat screen you are attempting to
connect to socket
server setting up the on events
Essentially telling them to connect again and listen for those calls, the more times they join, the more they see right.
What you want to do is make sure you define your socket globally AND only 'listen' for channels or emits 'once'
What I have done in the past is to have a main socket function when a user joins the event, this is done on login and or opening the app. This will perform the socket connect, run through all the connections etc and at the end I call a socketInit function. This function sets up all my .on statements in a conditional block, that ensures it will only run one time. So, something like if(socketInit = false) {run all my on code etc}
This ensures everything is only called one time. Then, inside each route, I do the 'emit' to the socket server telling it to 'join the room' or 'getchats' etc. Because I only have the .on called once, it means when I asked the socket server for the data via the emit what it returns on my .on only gets called once, because that .getChat on is only defined once and it knows what to do with that data and updates the screen accordingly.
Hope that helps!

React Native one to one conversation using socket.io

i currently have a react native app with nodejs express Sequelize as my backend and postgres as my database.
So, on my posts screen next to each post, i have a text input and a button where the current user can send the user of the post an initial message. Once the button is pressed, a conversation between these 2 users about this post is created in my database and stored in my conversation table and an entry of the message sent is also stored in my messages table.
I have implemented bidirectional communication between these 2 users. But my problem is i need to refresh the app in order to show the user current user the sent message and to show the receiving user the received message.
I have been researching for a while now and trying to understand how to implement this feature using socket.io but could not get anywhere.
Client Side
Here is my Chat Screen
function ChatScreen({route,navigation}) {
const message = route.params.message;
const [messages, setMessages] = useState(message.Messages);
const [text, setText] = useState('');
const { user } = useAuth();
const [socket, setSocket] = useState(null);
useEffect(() => {
const newsocket =io.connect(socketurl)
setMessages(messages);
newsocket.on('connection', msg => {
console.log('i have joined')
setMessages(messages=>messages.concat(msg))
setSocket(newsocket)
})
return()=>newsocket.close;
}, []);
const updateText=(text)=>{
setText(text);
}
const onSend = (ConversationId,senderId,receiverId,message) => {
console.log("sent")
messagesApi.sendMessage({ConversationId,senderId,receiverId,message});
setText("")
socket.emit('message', { to: (user.id===route.params.message.user1 ?
route.params.message.user2 : route.params.message.user1), from:
user.id, message,ConversationId });
};
return(
<Text>{user.id === message.Recipient.id ?
message.Creator.name:message.Recipient.name}</Text>
<KeyboardAvoidingView
style={{
display: "flex",
flex: 1,
}}
behavior={Platform.OS === "ios" ? "padding" : null}
keyboardVerticalOffset={Platform.OS === "ios" ? 25 : 0}
>
<FlatList
inverted
data={message.Messages}
keyExtractor={(message) => message.id.toString()}
renderItem={({item,index})=>(
<MessageBubble
text={item.message}
mine={item.senderId !== user.id}
/>
)}/>
<View style={styles.messageBoxContainer}>
<TextInput
style={styles.messageBox}
placeholder="Message..."
multiline
clearButtonMode="while-editing"
onChangeText={updateText}
value={text}
autoCorrect={false}
/>
<TouchableOpacity onPress={onSend}>
<Text style={styles.send}>Send</Text>
</TouchableOpacity>
</View>
</KeyboardAvoidingView>
)
Server Side
index.js
const express = require("express");
const app = express();
const http = require("http");
const socketio = require("socket.io")
const server=http.createServer(app);
const io =socketio(server)
io.on("connection", socket => {
socket.on('message', (data) => {
socket.join(data.ConversationId);
io.sockets.in(data.to).emit('send_message', { message: data.message,
to: data.to });
});
});
const port = process.env.PORT || config.get("port");
server.listen(port, function () {
console.log(`Server started on port ${port}...`);
});
Currently when i send a message, the message gets stored in my database but my chat does not update instantly (ie. not live), i need to refresh my app and the messages appear.
Can someone please help me and check if the implementation of socket i currently have is correct and if so, how do i render my flatlist instantly?
UPDATE
i think something is wrong in my useEffect, because when i open the chat i am not getting "i have joined" in the console:
useEffect(() => {
setMessages(messages);
socket.on('connect', msg => {
console.log('i have joined')
setMessages(messages=>messages.concat(msg))
})
}, []);
Your currently creating a new connection on every state change.. const socket =io.connect(socketurl)
You have a useEffect callback and that would be the logical place to put your connection logic, currently your only listening once for a connection, but creating multiple connections, so your 'connection' event is never called on these new connections. But you only want to connect once anyway, so we just need to put the connection logic also inside the useEffect, not just the connection event.
Because connecting to a socket is async, you will want to wait for the connection before rendering. So what we could do is store the socket in state, and when we get a connection set socket state, this will fire a re-render with a now valid socket.
eg.
const [socket, setSocket] = useState(null);
...
useEffect(() => {
const socket = io.connect(socketurl)
setMessages(messages);
newsocket.on('connect', msg => { //connect not connection
console.log('i have joined')
setMessages(messages=>messages.concat(msg));
setSocket(newSocket);
});
//might make sense to close the socket too,
//otherwise a memory leak.
return () => newSocket.close();
}, [route, navigation]);
if (!socket) {
//we don't have a socket yet,
return "loading..";
} else {
// we have a socket,
const onSend = (ConversationId,senderId,receiverId,message) => {...
....
// now render..
return (
<Text>{.........
Socket Index.js
/** Socket.io server listens to our app **/
var io = require('socket.io').listen(app);
io.on('connection', function (socket) {
/** On User Log In **/
socket.on('login', function (data) {
console.log('user joined >>', data)
userList.addUser(data, socket);
});
// Example Event //
socket.on('get_online_friends', userId => {
//Get List Data And Then //
let data = [UserList];
socket.emit('send_online_friend', data);
}
)
// On Logout //
socket.on('disconnect', function (reason) {
var offlineId = userList.removeUser(socket)
);
}
user_list.js
var userList = {};
module.exports = userList;
var userData = [];
var userSocData = {};
userList.user = userData;
userList.userSoc = userSocData;
userList.getUserList = function () {
return userSocData;
};
userList.addUser = function (user, client) {
userSocData[user] = {
socketId: client.id
}
};
userList.setReceiverId = function (user, client) {
var index = userData.findIndex(x => x.user_id == user['user_id']);
if (index !== -1) {
userData[index]['receiver_id'] = user['receiver_id'];
}
userSocData[user['user_id']] = {
socket: client.id
};
};
userList.removeUser = function (client) {
for (const property in userSocData) {
if (client.id === userSocData[property].socketId) {
var userID = property;
delete userSocData[property]
}
}
return userID;
};
Front End
***
socket.emit("get_request", userData.user_id);
socket.on("get_request_data", function (data) {
if (data.status) {
self.setState({ onlineFriends: data.data });
}
});
***

Node js Socket.iO accessing socket outside for multiple client

How can we access socket object outside for multiple socket connection. I created a object globally and tried to do this. But it always works for last connected socket.
'use strict';
const path = require('path')
const express = require('express');
const http = require('http');
const chalk = require('chalk');
const socketio = require('socket.io');
var connectionString = '';
const eventHubConsumerGroup = ""
const app = express()
const server = http.createServer(app ,() => {
console.log(chalk.green('Server created'))
})
const io = socketio(server)
const port = process.env.port || 3000
const publicDirectoryPath = path.join(__dirname , '../public')
var server_token = "1234567890";
app.use(express.static(publicDirectoryPath))
var localSocket;
io.on('connection',function(socket){
localSocket = socket;
console.log(socket.handshake.query.deviceID)
console.log('on user connected '+socket.id);
//report = new Report(socket);
socket.auth = false;
socket.on('authenticate',function(token){
console.log('token recieved is '+token);
if(server_token == token){
socket.auth = true;
console.log('connection is authenticated '+socket.id);
socket.emit("authenticate",true);
} else {
console.log("Connection not established")
socket.emit("authenticate",false);
}
})
socket.on('sendSocketEvent' , message => {
console.log(chalk.yellowBright(`Message recieved from ${socket.id} + ${message}`));
io.to(socket.id).emit('recieveSocketEvent', `Hello test`);
})
socket.on('disconnect',function(){
console.log('one user disconnected '+socket.id);
})
setTimeout(function(){
if(!socket.auth){
console.log('disconnecting the socket '+socket.id);
socket.emit("timeOut");
socket.disconnect();
}
},1000);
})
server.listen(port,() => {
console.log(chalk.redBright(`Server is up on port ${port}`))
})
var printMessage = function (message) {
console.log(JSON.stringify(message));
console.log(message.DeviceId);
if (localSocket != null){
if (message.DeviceId == localSocket.handshake.query.deviceID) {
localSocket.emit('recieveSocketEvent', message);
}
}
};
class EventHubReader {
constructor(connectionString, consumerGroup) {
this.connectionString = connectionString;
this.consumerGroup = consumerGroup;
this.eventHubClient = undefined;
this.receiveHandlers = undefined;
}
async startReadMessage(startReadMessageCallback) {
try {
console.log(this.connectionString)
const client = await EventHubClient.createFromIotHubConnectionString(this.connectionString);
console.log('Successfully created the EventHub Client from IoT Hub connection string.');
this.eventHubClient = client;
const partitionIds = await this.eventHubClient.getPartitionIds();
console.log('The partition ids are: ', partitionIds);
const onError = (err) => {
console.error(err.message || err);
};
const onMessage = (message) => {
const deviceId = message.annotations['iothub-connection-device-id'];
return startReadMessageCallback(message.body, message.enqueuedTimeUtc, deviceId);
};
this.receiveHandlers = partitionIds.map(id => this.eventHubClient.receive(id, onMessage, onError, {
eventPosition: EventPosition.fromEnqueuedTime(Date.now()),
consumerGroup: this.consumerGroup,
}));
} catch (ex) {
console.error(ex.message || ex);
}
}
// Close connection to Event Hub.
async stopReadMessage() {
const disposeHandlers = [];
this.receiveHandlers.forEach((receiveHandler) => {
disposeHandlers.push(receiveHandler.stop());
});
await Promise.all(disposeHandlers);
this.eventHubClient.close();
}
}
var { EventHubClient, EventPosition } = require('#azure/event-hubs');
const eventHubReader = new EventHubReader(connectionString, eventHubConsumerGroup);
(async () => {
console.log("Step1")
await eventHubReader.startReadMessage((message, date, deviceId) => {
console.log("Here getting called");
try {
const payload = {
IotData: message,
MessageDate: date || Date.now().toISOString(),
DeviceId: deviceId,
};
printMessage(payload);
} catch (err) {
console.error('Error broadcasting: [%s] from [%s].', err, message);
}
});
})().catch();
the problem is in condition "printMessage" . here I am trying to restrict the emit based on socket deviceID, but it's only working for last connected socket.
Can You please help me in this.
var localSocket;
io.on('connection',function(socket){
localSocket = socket;
})
You're overwriting the same variable, on each new connection, which means it will always point to the last socket connected.
What exactly do you want to do? To send this message to all connected sockets?

how to put websocket in to database (mongoDB/mongoose)?

when a player login, i put his websocket in database(mongoose mixed) and save it.however, after some testing i understand that if i put websocket in database , websocket becames a bunch of data and loses its websocket properties and cant send message to user. On the other hand, if i put websocket in array, i can totally send message. Is there anyway to cast that "bunch of data" to websocket again ? because i dont want to have one massive array that holds users websockets.
app.js (my server)
const express = require('express');
const { createServer } = require("http");
const mongoose = require('mongoose');
const config = require('./config');
const WebSocket = require('ws');
const activeUsers = require("./Models/ActiveUsersModel");
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
const server = createServer(app);
app.locals.wsArray = [];
app.use("/RegisterApi", require("./Routes/RegisterApi/RegisterApi"));
app.use("/MatchMakingApi", require("./Routes/MatchMakingApi/MatchMakerApi"));
const wss = new WebSocket.Server({ server });
server.listen(config.PORT, function () {
console.log(`im listening at ${config.PORT}`);
mongoose.connect(config.MONGODB_URI, {
useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true,
useFindAndModify: false
})
});
//function to split parameters from url
function getVars(tempVars) {
var userObject = {};
tempVars.forEach((a) => {
var splited = a.split("=");
userObject[splited[0]] = splited[1];
});
return userObject;
}
wss.on("connection",async (ws, request) => {
console.log("Total connected clients: ", wss.clients.size);
var tempVars = request.url.split("?")[1].split("&");
var userObject = getVars(tempVars);
console.log(userObject);
const currentUser = await activeUsers.findOne({ _id: userObject.parentID });
console.log('user with id ' + currentUser.currentActiveUser._id + ' login');
currentUser.webSocket = ws;
await currentUser.markModified('webSocket');
await currentUser.save();
app.locals.wsArray.push(ws);
});
const db = mongoose.connection;
db.on('error', (err) => console.log(err))
active user schema
const ActiveUsers = new schema({
currentActiveUser: Users.schema,
webSocket: mongoose.Mixed,
isSearchingGame: {
type: Boolean,
default: false
},
isInMatch: {
type: Boolean,
default: false
}
});
const ActiveUsersModel = mongoose.model("ActiveUsers", ActiveUsers);
module.exports = ActiveUsersModel;
matchmaking api( middleware of server) where error occur
router.post('/searchMatch'/*,verifyToken*/, async (req, res) => {
const currentUserData = await activeUsers.findOne({ _id: req.body.tempID });
if (currentUserData.currentActiveUser.money < req.body.money) { res.send('insufficient money'); return; }
req.app.locals.wsArray[0].send('aaaa'); // sends message
await currentUserData.webSocket.send('aaaa'); // gets error " send() isnt a function"
});
You can't store Websocket connections: the objects you call ws as your first parameter to your wss.on('connection' ...) event handler. You can't store them in MongoDB, and you can't serialize them to JSON: they're not pure data, but rather an interface to your machine's network stack.
So, you need to maintain them in your nodejs program. The Websocket server (wss) has a clients object. You can do
wss.clients.forEach(...)
to look at all the connections. You can add attributes to your connections, something like this:
wss.on("connection", async (ws, request) => {
ws.locals = {}
console.log("Total connected clients: ", wss.clients.size)
var tempVars = request.url.split("?")[1].split("&")
var userObject = getVars(tempVars)
ws.locals.parentId = userObject.parentId
const currentUser = await activeUsers.findOne({ _id: userObject.parentID })
wss.on("message", function (event) {
/* retrieve the user */
currentUser = await activeUsers.findOne({ _id: this.locals.parentId})
})
})
This lets you get back to the user for a particular connection when you get an incoming message.

SocketIO Identify what user disconnected

I am making a simple Node.js game that uses Express, Socket.io, and an Http server. All of the users are stored in a multidimensional object on the server. This is how the server-side code works:
var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
app.use(express.static(__dirname + '/'));
var playerList = {};
createPlayer = function(array,width,height,spdx,spdy,x,y,color,name,id) {
var player = {
width:width,
height:height,
spdx:spdx,
spdy:spdy,
x:x,
y:y,
wKeyDown:false,
aKeyDown:false,
sKeyDown:false,
dKeyDown:false,
color:color,
name:name,
id:id
}
array[id] = player;
}
io.on('connection', function(socket) {
socket.on('new player', function(id, name) {
id = parseInt(id);
if (!playerList[id]) {
createPlayer(playerList,25,25,4,4,Math.round(Math.random() * 800),Math.round(Math.random() * 600),randomColor(),name,id);
}
socket.on('pressW', function(id, keyDown) {
playerList[id].wKeyDown = keyDown;
});
socket.on('pressA', function(id, keyDown) {
playerList[id].aKeyDown = keyDown;
});
socket.on('pressS', function(id, keyDown) {
playerList[id].sKeyDown = keyDown;
});
socket.on('pressD', function(id, keyDown) {
playerList[id].dKeyDown = keyDown;
});
});
socket.on('disconnect', function() {
});
};
sendPlayerList = function() {
//newPlayerList is used to prevent client from seeing other users IDs
var newPlayerList = {};
var count = 0;
for (var q in playerList) {
player = {
x:playerList[q].x,
y:playerList[q].y,
width:playerList[q].width,
height:playerList[q].height,
color:playerList[q].color,
name:playerList[q].name,
}
newPlayerList[count] = player;
count++;
}
io.emit('edit playerlist', newPlayerList);
}
SPLInterval = setInterval(sendPlayerList, 1000);
Here is the client-side code for connection:
var id;
$('#playbutton').click(function() {
var name = document.getElementById('name').value;
id = Math.floor(Date.now() * Math.random());
socket.emit('new player', id, name);
});
On the client-side, in the update loop, when the game wants to tell the server your input, it emits your input like so:
update = function() {
ctx.clearRect(0,0,canvas.width,canvas.height);
if (document.hasFocus()) {
socket.emit('pressD', id, dKeyDown);
socket.emit('pressS', id, sKeyDown);
socket.emit('pressA', id, aKeyDown);
socket.emit('pressW', id, wKeyDown);
}else{
socket.emit('pressD', id, false);
socket.emit('pressS', id, false);
socket.emit('pressA', id, false);
socket.emit('pressW', id, false);
}
clientUpdatePlayer();
updatePlayers();
}
}
var updateInterval = setInterval(update, 31.25);
The function to update players just draws players based on the player list sent from the server.
My problem is that when a user disconnects, they stay in the player list.
I don't understand how I should go about fixing this. I identify users by getting the ID they send from the client, but I can't get the user's id when they disconnect.
There is a lot more code, but I tried to only include the code that I thought was necessary. I am willing to include more code if that is needed.
You could just store the id value in the parent scope, which the disconnect event handler would have access to:
io.on('connection', function(socket) {
var userId;
socket.on('new player', function(id, name) {
userId = id = parseInt(id);
// ...
});
socket.on('disconnect', function() {
delete playerList[userId];
});
};
Maybe I'm late to the party but I was stuck with something similar and found it the hard way and this may help someone.
The best way to detect if the user is disconnected is would be to first set the username in socket session.
Send the name from the client on emit
socket.emit("newUser", username);
and on server
socket.on('newUser',function (username) {
// we store the username in the socket session for this client
socket.username = username;
});
and when the user disconnects find that on the disconnect event
socket.on('disconnect', function () {
var connectionMessage = socket.username + " Disconnected from Socket " + socket.id;
console.log(connectionMessage);
});
and you can take it from there.
This worked for me:
On every new connection or user who comes online generate a socket Id, add it to the user object, and add it to the array of all the users online.
const users = [];
io.on('connection', (socket) => {
const socketId = socket.id;
socket.on('user online', (data) => {
users.push({ ...data, socketId });
io.emit('view user online', user);
});
Then in the disconnect, use forEach to loop through each object in the array, then use for to loop through and delete each key in the object:
socket.on('disconnect', () => {
users.forEach((user) => {
if (user.socketId === socket.id) {
for (const key in user) {
delete user[key];
}
}
});
logger(`A user has disconnected`);
});
});
});
Tweak to the way you want.
var users = [];
socket.on('newUser', (username) => {
users.push({
id: socket.id,
username: username
});
});
socket.on('disconnect', () => {
const presentUser = users.find(user => user.id == socket.id);
users = users.filter(user => user != presentUser);
});
We can use socket id for storing data as a refrence in playerList. whenever user will disconnect you can delete element from object according to socket id
var playerList = {};
io.on("connection", socket => {
if (!Object.values(playerList).includes(playername) && playername != null) {
var U_data = {
[socket.id]: playername
};
playerList = { ...playerList, ...U_data };
}
socket.on("disconnect", function(e, id) {
console.log(socket.id);
delete playerList[socket.id];
io.emit("broadcast", Object.values(playerList));
});
}

Resources