How to store socket id and emit to that user - javascript

Hey guys i am working on a webrtc app using socket.io and node, and i have this issue where i made a database in mongodb to store every admin who created a room then fetch the admin id when a user joins the room, but my problem is how do i store the id and emit to that specific user. i tried using io.to(socketID).emit(event,message) but it doesn't emit to the user please help
my code
const users = {}
const admins = {}
io.on('connection',socket =>{
socket.on('join-class',async(classId,palsid)=>{
socket.join(classId)
const gEtLectID = await LiveClass.findOne({"address":classId})
if(gEtLectID){
socketId = gEtLectID.startedBy
}
console.log(socketId,'is admin')
io.to(socketId).emit('user-connected', "let's play a game");
// socket.to(admins[socket.id]).emit('user-connected',userID)
//on disconnection
socket.on('disconnect',()=>{
socket.to(classId).broadcast.emit('user-disconnect',palsid)
})
})

You need to handle the id in the client. when you send an event to the server, include the toId and fromId. So, in the client var socket = io("http://127.0.0.1:3000/", { query: "id=4ny1d" });.
And in the server
io.on('connection', socket => {
var id = socket.handshake.query['id'];
socket.leave(socket.id);//leaving default room
socket.join(id);//joining to custom room(this is admin room)
//maybe you'll want a roomId, but the steps are the same
socket.on('join-class', data => {
socket.join(data.classId);//Here you join to the class
socket.emit('class-ready', data);//message with class info to the suscriber
//here you can store classId in a var or in an array to use socket.to().emit()
})
}

Related

How to maintain the same socket connection on two different pages?

I am creating a page to play a card game, this is for several players who can connect to different rooms. I am having problems in the lobby, where you can see which players are connected to a room. It happens that using the sockets I call the io() function in a section, this connection that is generated is used to connect the client to a room when requested, as I need to use the connection (socket) in another section, I import it with javascript, but this makes me call the io() function again and create a new connection and this is not connected to the room that I had already specified.
This is my code of one of the client parts:
const socket = io();
$("#createParty").click(() => {
const code = newCode();
socket.emit("client: newParty", {userName: sessionStorage.getItem("userName"), code: code});
window.location.href = "/room";
});
$("#connectParty").click(() => {
const codeConnect = $("#txt-codeConnect").val();
socket.emit("client: tryConnectParty", {userName: sessionStorage.getItem("userName"), codeConnect: codeConnect});
window.location.href = "/room";
});
export default socket;
There are two buttons, one that creates the room or party and another one that connects you to one of them, both emit different events to the server.
Here is my server side code:
// Import for server...
import { Server as WebSocketServer } from "socket.io";
import http from "http";
import app from "./app.js";
const server = http.createServer(app);
const io = new WebSocketServer(server);
var rooms = [];
export var usersRoom = [];
// Events socket...
io.on("connection", (socket) => {
console.log("New conection:", socket.id);
socket.on("client: newParty", (data) => { // data = {userName, code}
const code = data.code;
socket.join("room-"+ code);
rooms.push(
{
room: code,
users: [
data.userName
]
}
);
});
socket.on("client: tryConnectParty", (data) => { // data = {userName, codeConnect}
const code = data.codeConnect;
for (let i = 0; i < rooms.length; i++) {
if (rooms[i].room == code) {
const userName = data.userName;
const room = "room-"+ code;
socket.join(room);
rooms[i].users.push(userName);
usersRoom = rooms[i].users;
socket.to(room).emit("server: newConnection", userName);
}
}
});
});
export default server;
I use a variable called rooms, that is composed by the existing rooms and the users connected in it, this is so that the new player that connects can see all the players already connected, the problem is for the players that were already connected at the moment that a new one enters.
As you can see the server receives the two different events, in the event "tryConnectParty" the server checks if the code of the room is in the rooms variable, when it finds it, it emits an event to that room.
This is my code from another part of the client:
import socket from "/main.js";
const userName = sessionStorage.getItem("userName");
$("#txt-userName").html(`<b class='text-white'>${userName}</b>`);
$("#txt-codeRoom").html(`<b>${sessionStorage.getItem("code")}</b>`);
var divPlayers = $("#players");
socket.on("server: newConnection", (userName) => {
divPlayers.append(createCard(userName));
});
function createCard(user) {
return `
<div class="card d-inline-flex text-center" style="width: 300px;">
<div class="card-body">
<span class="h1 text-dark">${user}</span>
</div>
<img class="card-img-bottom" src="img/avatar.png" alt="avatar">
</div>
`;
}
This javascript code is called by a html one:
<script src="/logic/players.js" type="module"> </script>
This is the "room" section, here you can receive the "newConnection" event that creates a card with the name of the new user so that the user can see the new player.
The event "newConnection" never arrives or is received. I was checking several times the code and it was when I realized that when the "socket" is imported is when the io() function is called again and the connection is lost, as this is a new connection and the event is only sent to those who are in the room.
I want to know if there is a better way to do it or how I could solve this, since I can't move forward without this.

Show user invoices for simultaneously logged in users using Expressjs

I have created a simple invoice application using the MERN stack. The application is great at handling data for the logged in user as long as one user is logged in, but if another user logs in then the invoices for the user that first logged in is shown.
I am currently using app.set() and app.get() to pass data between endpoints and send to my frontend client. Auth0 handles the authentication layer, but would express-session solve this issue? And if it is how would I go about implementing this? Or is there a better solution?
Below is the code that sends the invoices currently to the frontend:
var express = require('express');
var app = express();
var userInvoices = express.Router();
const axios = require('axios');
const InvoiceModel = require('../models/Invoice');
const UserModel = require('../models/User');
//Functions//
async function clientCall() {
const url = `${process.env.REACT_APP_SAVE_USER}`;
const axiosConfig = {
method: 'get',
url
};
await axios(axiosConfig).catch((e) => {console.log(e)});
};
async function fetchUsersFromDB() {
const usersFromDB = await UserModel.find().catch((e) => {console.log(e)});
return usersFromDB;
};
async function saveUser(User) {
const condition = {email: User.email};
const query = {
nickname: User.nickname,
name: User.name,
picture: User.picture,
email: User.email,
email_verified: User.email_verified,
sub: User.sub,
};
const options = { upsert: true };
const update = await UserModel.updateMany(condition, query, options).catch((e) => {console.log(e)});
// Log the amount of documents that where updated in the DB.
if(update.nModified > 0 ) {
console.log('Number of Users added or updated to DB:', update.nModified)
}
};
function findCommonUser(dbUser, User) {
if(dbUser.length <= 0) {
UserModel.create(User, () => {console.log('Users saved to database')});
console.log('An Error has occured with Database')
} else {
dbUser.forEach((DBElement) => {
if(User.email !== DBElement.email) {
saveUser(User);
}
})
}
console.log(' Users match')
};
function matchUserAndInvoice(dbInvoices, loggedInUser) {
let newArray = [];
dbInvoices.forEach(async (DBElement) => {
if(DBElement.customer_email === loggedInUser.email){
newArray.push(DBElement);
app.set('userInvoices', newArray);
}
})
}
// prevents user from having to refresh to get data.
clientCall();
userInvoices.post('/saveUser', async (req, res) => {
try {
const User = req.body;
const usersFromDB = await fetchUsersFromDB().catch((e) => {console.log(e)});
findCommonUser(usersFromDB, User);
app.set('Users', User)
} catch (error) {
console.log(error)
}
})
userInvoices.get('/fetchUserInvoices', async (req,res) => {
try {
const invoices = await InvoiceModel.find().catch((e) => {console.log(e)});
const user = await app.get('Users');
await matchUserAndInvoice(invoices,user);
const userInvoices = await app.get('userInvoices')
res.json(userInvoices);
} catch (error) {
console.log(error)
}
});
;
module.exports = userInvoices;
app.get() is essentially global to your server instance so putting data there to use between requests for individual users will (as you have discovered) get data confused between different users as all users are trying to store data in the same place.
The usual way to solve a problem like this is to use express-session. This cookies the end-user's connection the first time they connect to your server and then creates a server-side object that is automatically associated with that cookie. Then, inside of any request handler, you can read or set data in req.session and that data will uniquely belong to just that user.
If the user changes devices or clears their cookies, then the association with the session object for that user will be lost (creating a new session object for them upon their next connection). But, if you have a persistent data store and you use some sort of login that allows you to populate the session from the user's persistent store, you can even make the session persistent across devices for the same user (though often times this is not required).
In the specific application you describe in your question (tracking invoices), it seems like your invoice data should be tagged with a specific user when it is stored in your database and then any future requests to display invoices should query based on the particular user that is making the request. This requires a login and login cookie so that you can uniquely identify each user and thus show them only the invoices that pertain to them.
The above-described session object should only be used for temporal session state, not for persistent storage such as invoices. If your server implementation is truly RESTFUL, you may not even need any data stored in the session object other than user's logged in userID.

socket.io Get data io.sockets.clients(); Not working anymore

Before you can create objects with user properties on frontend and assign it to the socket.user property for each connection using a code like this ex below in the backend.
socket.on("new_visitor", user => {
console.log("new_visitor", user);
socket.user = user;
emitVisitors();
});
then retrieve all these data through the sockets object eg.
const getVisitors = () => {
let clients = io.sockets.clients().connected;
let sockets = Object.values(clients);
let users = sockets.map(s => s.user);
return users;
};
//frontend
componentWillMount() {
axios.get('http://geoplugin.net/json.gp').then(res => {
const {
geoplugin_request,
geoplugin_countryCode,
geoplugin_city,
geoplugin_region,
geoplugin_countryName
} = res.data;
const visitor = {
ip: geoplugin_request,
countrycode: geoplugin_countryCode,
city: geoplugin_city,
state: geoplugin_region,
country: geoplugin_countryName
}
socket.emit("new_visitor", visitor);
socket.on("visitors", visitors => {
this.setState({
visitors: visitors
})
})
});
}
But now the io.sockets.clients is not working anymore and is not recognized as a function.Every API provided seem t return only the Id. For anyone who knows a workaround on this please let us know. Thanks a lot.
Problem : How to hold custom data for each socket (serverside)
For each socket that connects to your socket-io server you want to be able to store some custom data in reference to said socket, so at a later point, other sockets can retrieve this information.
Solution : Add a simple in-memory-store (serverside)
I strongly advise to not add anything or mutating the socket object. Instead use the socket id to maintain a simple in-memory store for all connected sockets.
🚧 Please note: the following snippets are just pointers and are not meant to just be copy pasted. Instead, try to use them to understand your problem and adjust them to your needs.
Server Side
const store = {};
io.on('connection', function (socket) {
// add socket to store, like this
// note 'data' is null at this point, yet needs to be set
store[socket.id] = {
socket : socket,
data : null
}
socket.on('SET_CLIENT_DATA', function (clientdata) {
// here we receive data from frontend, and add it to the serverside reference
store[socket.id].data = clientdata;
// once a socket updates his custom client data
// emit all custom data to all clients
io.emit('ALL_CONNECTED_CLIENTS', Object.values(store).map(e => e.data));
});
socket.on('disconnect', function () {
// if socket disconnects, make sure to remove the reference in your store
delete store[socket.id];
});
});
Client Side
socket.emit("SET_CLIENT_DATA", clientdata);
socket.on("ALL_CONNECTED_CLIENTS", (allclients) => {
/* here the client receives all custom client data that we kept serverside for each connected client */
/* do some more code here */
});

Have user chat output to admin chat in socket.io?

I'm very new to JavaScript and Socket.io, I'm trying to create a space for the admins and the regular users.
only catch is I want the regular users chat to output to the admin chat.
How could I go about doing something like this? I've read the get started part and I'm still a bit lost.
I will explain User flow, This may help to solve your problem.
let adminSockets = []; // Here I'm considering multiple admin
let userSocketMap = {};
When user Login you want to connect socket to server with identical(admin) handshake
io.of('/').on('connect', socket => {
let { isAdmin, userId } = socket.handshake.headers
if(isAdmin) {
adminSockets.push(socket)
} else {
userSocketMap[userId] = socket;
}
});
While user send message to one another(User 1 send message to User 2)
socket.on('send', (data) => {
let fromUserId = socket.handshake.headers.userId // User 1
let toUser = data.toUserId // User 2 ( This is the user to whom you want to send message)
let message = data.message
let toSocket = userSocketMap[toUser];
toSocket.emit({form: fromUserId, message: message})
adminSockets.forEach(adminSocket => {
adminSocket.emit({form: fromUserId, to: toUser, message: message})
});
})

How to scale push notifications in Firebase Cloud functions for this use case?

In my app, I am sending push notifications to the followers of a user when that user creates a new post. As you can see in the code below, I have some additional settings that I need to query from each follower's profile to get their push token and check for some additional notification settings. I am afraid that this query of each user's profile might become a bottleneck if a user has a large number of followers i.e. 1000.
What is the best way to approach this?
// The cloud function to trigger when a post is created
exports.newPost = functions.database.ref('/posts/{postId}').onCreate(event => {
const postId = event.params.postId;
const post = event.data.val();
const userId = post.author;
let tokens = [];
let promises = [];
return admin.database().ref(`/followers/${userId}`).once('value', (followers) => {
followers.forEach((f) => {
let follower = f.key;
promises.push(
admin.database().ref(`users/${follower}`).once('value')
);
});
})
.then(() => {
return Promise.all(promises).then((users) => {
users.forEach((user) => {
const userDetails = user.val();
if (userDetails.post_notifications) {
if(userDetails.push_id != null) {
tokens.push(userDetails.push_id);
}
}
})
})
})
.then(() => {
if (tokens.length > 0) {
const payload = {
notification: {
title: 'New Post!',
body: 'A new post has been created'
}
};
// Send notifications to all tokens.
return admin.messaging().sendToDevice(tokens, payload);
}
});
})
EDIT:
We have thought about using topics. But we are not sure how we can still have our customized notifications settings working with topics. Here's our dilemma.
We have multiple actions that can create notifications and we provide individual switches for each type of notification in the app so a user can select which type of notifications they want to switch off.
Let's say when User A follows User B. We can subscribe User A to "User B's topic" so whenever User B performs an action which sends out notifications to his/her followers, I can send send out notifications to users subscribed to "User B topic".
Because we have multiple notification switches in the app and when user A changes his/her settings that they do not want notifications for new posts but still want other types of notifications from the users he/she follows, we have not been able to figure out how we can use topics in this case.
Instead of using tokens, you can use topics for this. So lets say a user started following someone, then he will register to that topic.
Lets say he followed someone called "Peter", then you can execute this:
FirebaseMessaging.getInstance().subscribeToTopic("Peter");
Now if you have this database:
posts
postid
postdetails: detailshere
author: Peter
then using onCreate():
exports.newPost = functions.database.ref('/posts/{postId}').onCreate(event => {
const postId = event.params.postId;
const post = event.data.val();
const authorname = post.author;
const details=post.postdetails;
const payload = {
data: {
title:userId,
body: details,
sound: "default"
},
};
const options = {
priority: "high",
timeToLive: 60 * 60 * 24
};
return admin.messaging().sendToTopic(authorname, payload, options);
});
You can use this, everytime the author creates a new post, onCreate() is triggered then you can add the details of the post and the author name in the notification (if you want) and sendToTopic() will send it to all users subscribed to the topic that is the authorname (ex: Peter)
After your edit, I think you want the user to be unsubscribed from a topic, but stay subscribed to other topics, then you have to use the admin sdk for this:
https://firebase.google.com/docs/cloud-messaging/admin/manage-topic-subscriptions
Using the admin sdk you can unsubscribe the user also from a topic, a simple example:
// These registration tokens come from the client FCM SDKs.
var registrationTokens = [
'YOUR_REGISTRATION_TOKEN_1',
// ...
'YOUR_REGISTRATION_TOKEN_n'
];
// Unsubscribe the devices corresponding to the registration tokens from
// the topic.
admin.messaging().unsubscribeFromTopic(registrationTokens, topic)
.then(function(response) {
// See the MessagingTopicManagementResponse reference documentation
// for the contents of response.
console.log('Successfully unsubscribed from topic:', response);
})
.catch(function(error) {
console.log('Error unsubscribing from topic:', error);
});

Categories

Resources