Socket emit to specific user - sockets

I found several questions on the same subject, and none of theme worked for me. I suppose that's because I'm missing something in the process:
I want to send a message to a specific user.
I use:
"express": "^4.13.3"
"socket.io": "^1.3.7"
"socket.io-client": "^1.3.7"
"mongodb": "^2.2.2"
"mongoose": "^4.5.4"
Here is my code:
server (As you can see, I use mongoose to handle session):
const io = new SocketIo(server);
io.use(socketioJwt.authorize({
secret: configSocket.secret,
handshake: true
}));
// persistence store of our session
const MongoStore = mongoStoreFactory(session);
const sessionMiddleware = session({
store: new MongoStore({
mongooseConnection: mongoose.connection,
ttl: (1 * 60 * 60) // 1 hour
}),
secret: configSocket.secretSession,
httpOnly: true,
resave: false,
saveUninitialized: false,
// rolling: true,
// secure: true,
cookie: { maxAge: 86400000 }
});
app.use(sessionMiddleware);
...
socketRouter(io);
then the socketRouter function. I store the socketId of the user profile with mongo dataStore to aim the user with emit.
exports = module.exports = (io) => {
io.sockets.on('connection', (socket) => {
Users.findById(socket.decoded_token.sub, (err, user) => {
if (user) {
// if the user exists, save the socket Id in the user collection
Users.update({_id: user._id}, {$set: {socketId: socket.id}}, (err2, result) => {
// ------ PROTECTED ROUTES ------ //
// MOBILE CALLS
...
// DASHBOARD CALLS
socket.on('forceNotif', (data) => Notif.force(data, io, socket));
// ------------------------------ //
});
}
});
socket.on('disconnect', ()=> {
Users.update({_id: socket.decoded_token.sub}, {$set: {socketId: null}});
});
});
The function called by 'forceNotif'. Here I expect a different behavior. I want socket to send the value to a specific user (different from the one sending the request). I retrieve the socketId from MongoDb and it's exact. Then I want to use it for my purpose. Several different propositions are made on web. I tested the followings:
exports.force = (par, io, socket) => {
// retrieve the socket id of the user to aim
Users.findOne({_id: par.id}, {socketId: 1, pseudo: 1}, (err, res) => {
console.log('--------------------------------------------------');
console.log('err: ', err);
console.log('pseudo: ', res.pseudo);
console.log('socketId: ', res.socketId);
// emit the value to a specific user
// not working
io.sockets.to(res.socket).emit('notifExtUpdate', {val: 'TO'});
io.to(res.socket).emit('notifExtUpdate', {val: 'TO'});
io.broadcast.to(res.socket).emit('notifExtUpdate', {val: 'TO'});
socket.broadcast.to(res.socket).emit('notifExtUpdate', {val: 'TO'});
socket.to(res.socket).emit('notifExtUpdate', {val: 'TO'});
// working well, but not my purpose
io.emit('notifExtUpdate', {val: 'BROADCAST'});
socket.broadcast.emit('notifExtUpdate', {val: 'BROADCAST'});
});
};
I hope somebody can help me :-)

So here the solution I found:
the socket documentation says than "each socket automatically joins a room identified by the id". But for some reasons I still don't understand, when I emit in the following way (where the socketId is stored, retrieved and checked with mongoDb), nothing happens:
socket.broadcast.to(socketId).emit('blabla', msg);
But finally I joined manually the user to a personal room in the following way:
io.on('connection', (socket) => {
Users.findById(socket.decoded_token.sub, (err, user) => {
if (user) {
// create a room for every user
socket.join(user._id);
socket.on('exemple', (data) => functionExemple(data, io));
...
and then I can emit to a specific user in functionExemple like this (where the targetId is the _id of the user in the collection):
exports.functionExemple= (par, io) => {
returnEmit.to(targetId).emit('blabla', msg);
};
I hope it will help somebody :-)

Related

Passport OAuth2 strategy / facebook strategy is loosing user

I am trying to authorize a pre logged in user with a Facebook account. I want to store the auth token of Facebook to later post stuff using my CMS.
I am using Express/NodeJS and Passport JS.
My FacebookStrategy looks like this:
module.exports = new FacebookStrategy(
{
clientID,
clientSecret,
callbackURL: `${config.apiUrl}/v1/auth/connect/facebook/callback`,
passReqToCallback: true
},
async function(req, token, tokenSecret, profile, done) {
console.log("SESSION?", req.session)
console.log("THIS SHOULD BE SET!", req.user) // But is not!
// Stuff is done.
done(null, token, {savedConnectionForLaterUse});
}
I also have two routes:
router.get('/connect/facebook',
API_KEY_OR_JWT_AUTH_MIDDLEWARE,
(req, res, next) => {
// Save authInfo in session
Object.assign(req.session, {account: req.authInfo.account._id, user: req.user._id})
passport.authorize('facebookConnect', {
failureRedirect: `${frontUrl}/settings/connections`,
scope: facebookOAuthScopes, // This is an array of scopes I need
})(req, res, next)
},
);
router.get('/connect/facebook/callback',
passport.authorize('facebookConnect', {
failureRedirect: `${apiUrl}/v1/auth/connect/facebook/failure`,
}),
(req, res) => {
const { session: {connection} } = req;
res.redirect(`${frontUrl}/settings/connections/edit/${connection}`);
}
);
When I am running this on my local machine it works due to the fact that the session is there and in the session I can find my user for later use. As soon as I am deploying this on a server (with kubernetes) the session is gone.
The configuration of the express session looks like this:
app.use(
expressSession({
secret: config.security.secret,
resave: true,
saveUninitialized: true,
cookie: {
sameSite: 'none', // This was something I tried.. didn't help thou
secure: true,
},
})
)
Can anyone point me into the right direction? What am I doing wrong?
Thank you all for your help in advance. I am really at the end of my knowledge. The struggle is real! :D

Socket.io 4.x - How do I fetch users as username lists?

First, the client enters the room name and user name. And I'm sending them to the server.
Client
socket.on('connect', () => {
socket.emit("join", { roomID: roomID, name: name });
})
Then I take it on the server and put it in the room.
Server console.log(room) returns {roomID: "1", name: "user"}, so it's good
io.on('connection', socket => {
socket.broadcast.emit('a user connected')
socket.on('join', (room) => {
console.log(room);
socket.join(room.roomID);
});
})
Now I need to send the list of users in this room back to the client.
However
How do I use the username when doing socket.join?
How do I send the users in this room to the client as a list? (I need usernames)
Thanks.
Assuming you have kept the list of users somewhere, you can use a call back
Client
socket.on('connect', () => {
socket.emit("join", { roomID: roomID, name: name }, (data) => {
//This is the function that's called below using callback.
});
})
SERVER
socket.on('join', (room, callback) => {
console.log(room);
socket.join(room.roomID);
callback(/* array of users here */)
});

Get all documents from an authenticated user (relation OneToMany)

I am learning to use mongoDB AND ExpressJS by building a Rest API that I would use with ReactJS.
I have always chosen MySQL for the management of my database, but the mongoDB database is not relational and it is still difficult for me to understand.
An example of what I want to do
Let's say that I have created a blog and want to get all the articles from a user logged in with an account.
All these operations are managed with a REST API and MongoDB.
How to create a OneToMany relationship between articles and a user.
With MySQL I just had to specify a user_id key for each article in an article table.
But with mongoDB how to create this and especially for a user who is logged in with an account, so that only a logged in user can view his articles.
EDIT
I have tried something, it works but I don't know if it's the right approach.
Context:
I made a REST API with NodeJS and ExpressJS.
The API will allow a user to organize their applications to facilitate the search for a job.
A user must create an account and log in to take advantage of all of the application's features, so no information is publicly available.
For registration and authentication of a user, I use PassportJS, mongoConnect and ExpressSession
To start, the User model of mongoDB
const userSchema = mongoose.Schema({
name: {
type:String
},
email: {
type:String,
required:true,
unique:true
},
email_is_verified: {
type:Boolean,
default:false
},
password: {
type:String,
},
referral_code : {
type:String,
default: function() {
let hash = 0;
for(let i=0; i < this.email.length; i++){
hash = this.email.charCodeAt(i) + ((hash << 5) - hash);
}
let res = (hash & 0x00ffffff).toString(16).toUpperCase();
return "00000".substring(0, 6 - res.length) + res;
}
},
referred_by : {
type: String,
default:null
},
third_party_auth: [ThirdPartyProviderSchema],
date: {
type:Date,
default: Date.now
}
},
{ strict: false }
);
module.exports = mongoose.model('Users', userSchema);
The Apply model represents an apply for a job, for now there is only the title.
To create the OneToMany relationship, I add a User field which refers to my User model
Function to retrieve all applies, so I retrieve the user id of the session.
const applySchema = mongoose.Schema({
title: { type:String, required:true },
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
}
})
module.exports = mongoose.model('Apply', applySchema);
I created a controller for the management of a user's applies
exports.getAllApplies = (req, res, next) => {
res.locals.currentUser = req.user;
const userId = res.locals.currentUser.id
Apply.find({ user:userId })
.then(applies => res.status(200).json({ message:'success',
applies:applies }))
.catch(error => res.status(400).json({ error:error, message: 'Failed'}))
}
Function allowing to consult an apply
exports.getOneApply = (req, res, next) => {
res.locals.currentUser = req.user;
const userId = res.locals.currentUser.id
Apply.findOne({ _id:req.params.id, user:userId })
.then(apply => res.status(200).json({ message: `Apply with id
${apply._id} success`, apply:apply}))
.catch(error => res.status(500).json({ error:error, message:'Failed'}))
}
The routes of my api, I add an auth middleware to allow requests only for a user with a token
const express = require('express');
const router = express.Router();
const auth = require('../middleware/auth');
const applyCtrl = require('../controllers/apply');
router.get('/', auth, applyCtrl.getAllApplies);
router.get('/:id', auth, applyCtrl.getOneApply);
module.exports = router;
I apologize for the length of the post, if you have any questions, I would be happy to answer them.
Thank you in advance for your help and your answers.

React + Sails + Socket.io

This is quite a broad question, however I currently have a Sails API server and a React Front-end (Standalone).
Note: The React Front-End is NOT part of Sails
I'm trying to get to grips with sockets, so I figured I would start simple. I want to achieve the following:
User visits my website (React)
React opens a socket and connects to Sails
Sails streams the data from within a function/model
React updates when new data is added to the model
I semi understand how this works using Express and React, however I cannot get my head around how Sails implements their version of WebSockets on top of Sockets.io.
What I've done is install the sockets.io-client within React, and then trying to use sails.sockets inside Sails.
This is what I currently have:
React Component NB: I don't think this is correct at all
componentDidMount =()=> {
this.getSessionData();
UserStore.listen(this.getSessionData);
Socket.emit('/listSessions', function(data){
console.log(data);
})
}
Sails Function (listSessions)
listSessions: function(req, res) {
Session.find({ where: {visible: true}, sort: 'createdAt DESC'},
function(err, sessions){
if(req.isSocket){
Session.watch(req.socket);
console.log('User subscribed to ' + req.socket.id);
}
if(err) return res.json(500, {
error: err,
message: 'Something went wrong when finding trades'
});
return res.json(200, {
sessions: sessions,
});
})
},
Sails Function (createSession) Trying to use publishCreate to use in conjunction with Session.watch in the above function
createSession: function(req, res){
var token = jwt.sign({
expiresIn: 30,
}, 'overwatch');
Session.create({
username: req.body.username,
platform: req.body.platform,
lookingFor: req.body.lookingFor,
microphone: req.body.microphone,
gameMode: req.body.gameMode,
comments: req.body.comments,
avatar: null,
level: null,
hash: token,
competitiveRank: null,
region: req.body.region,
visible: true,
}).exec(function(err, created){
Session.publishCreate(created);
if(err) {
console.log(err);
return res.send({
error: err,
message: 'Something went wrong when adding a session',
code: 91
})
}
if(req.isSocket){
Session.watch(req.socket);
console.log('User subscribed to ' + req.socket.id);
}
return res.send({
session: created,
code: 00,
})
});
},
Both of the Sails functions are called using POST/GET.
I'm completely stumped as where to go with this, and it seems to documentation or explanation on how to get this working is limited. All the Sails documentation on Sockets seems to relate to using Sails as a front-end and server
OK so I managed to solve this:
Simply put:
Within React, I had to include https://github.com/balderdashy/sails.io.js/tree/master
Then within my React component I did:
componentDidMount =()=> {
io.socket.get('/listSessions',(resData, jwres) => {
console.log('test');
this.setState({
sessions: resData.sessions,
loaded: true,
})
})
io.socket.on('session', (event) => {
if(event.verb == 'created') {
let sessions = this.state.sessions;
sessions.push(event.data);
this.setState({
sessions: sessions
})
} else {
console.log('nah');
}
});
}
This makes a virtual get request to Sails using Socket.io, and sets the response in state. It also watches for updates to the 'session' connection and updates the state with these updates meaning I can update a list in real time
Within my Sails controller I have:
listSessions: function(req, res) {
if(req.isSocket){
Session.find({ where: {visible: true}, sort: 'createdAt DESC'},
function(err, sessions){
Session.watch(req.socket);
if(err) return res.json(500, {
error: err,
message: 'Something went wrong when finding trades'
});
return res.json(200, {
sessions: sessions,
});
})
}
},
The Session.watch line listens for updates via publishCreate on the model which is found in my model as follows:
afterCreate: function(message, next) {
Session.publishCreate(message);
next();
},
Adding to answer by #K20GH , add the following to my "index.js" in React to help get sails.io.js from the CDN :
const fetchJsFromCDN = (src, externals = []) => {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.setAttribute('src', src);
script.addEventListener('load', () => {
resolve(
externals.map(key => {
const ext = window[key];
typeof ext === 'undefined' &&
console.warn(`No external named '${key}' in window`);
return ext;
})
);
});
script.addEventListener('error', reject);
document.body.appendChild(script);
});
};
fetchJsFromCDN(
'https://cdnjs.cloudflare.com/ajax/libs/sails.io.js/1.0.1/sails.io.min.js',
['io']
).then(([io]) => {
if (process.env.NODE_ENV === 'development') {
io.sails.url = 'http://localhost:1337';
}
});
Once you have this, you'll be able to use the HTTP type GET, PUT, POST and DELETE methods. So here you can do:
componentDidMount =()=> {
io.socket.get('/listSessions',(resData, jwres) => {
console.log('test');
this.setState({
sessions: resData.sessions,
loaded: true,
})
})
io.socket.on('session', (event) => {
if(event.verb == 'created') {
let sessions = this.state.sessions;
sessions.push(event.data);
this.setState({
sessions: sessions
})
} else {
console.log('Not created session');
}
});
}
And you can do the required setup in sails for the models of sessions as suggested above

How do I Build A .json Endpoint with a Sequelize Query?

Struggling to get my head around this for a week and a half, I was wondering how to get a .json endpoint that is from a query from the Sequelize ORM. Currently it logs a 404 error "GET /api/users 404 3ms". As you may have heard the documentation for Sequelize is pretty limited and I've been searching github repo after tutorial and none have worked thus far, so I'd thought I'd ask here.
A small excerpt (code on https://github.com/NatuMyers/A.M.E.N.SQL-Stack):
// VARS -----------------------------
var express = require('express')
, bodyParser = require('body-parser')
, errorHandler = require('errorhandler')
, methodOverride = require('method-override')
, morgan = require('morgan')
, http = require('http')
, path = require('path')
, db = require('./models')
var router = require('express').Router();
var app = express()
// all environments
app.set('port', process.env.PORT || 3000)
app.set('views', __dirname + '/views')
app.set('view engine', 'jade')
app.use(morgan('dev'))
app.use(bodyParser())
app.use(methodOverride())
app.use(express.static(path.join(__dirname, 'public')))
// SEQUELIZE MODELS
var userVar = require('./models/user');
// dev only
if ('development' === app.get('env')) {
app.use(errorHandler())
}
// Make db, and make it listen
db
.sequelize
.sync()
.complete(function(err) {
if (err) {
throw err
} else {
http.createServer(app).listen(app.get('port'), function() {
console.log('Express server listening on port ' + app.get('port'))
})
}
})
// HTTP GET endpoints
module.exports = function() {
router.get('/', function(req, res, next){
res.json({ message: 'This works at localhost:3000/api but getting a list of users is a pain :(' });
});
// question
router.get('/users', function(req, res, next){
res.json(/* I need to make sequelize send a part of the User db here. */);
});
return router;
};
I moved on from this by using Epilogue.js (in a vanilla way).
I added models INLINE with Sequelize (I wasted lots of time trying to import models), then add any middle ware and create the restful api based on the syntax below.
// 1. ADD SEQUELIZE MODELS ---- ---- ---- ----
var database = new Sequelize('raptroopdb', 'root', 'strongpassword');
var Employee = database.define('Employee', {
name: Sequelize.STRING,
hireDate: Sequelize.DATE
});
// Add Account model with foreign key constraint to Employee
var Account = database.define('Account', {
name: Sequelize.STRING,
managerId: {
type: Sequelize.INTEGER,
references: {
// This is a reference to model Employee
model: Employee,
// This is the column name of the referenced model
key: 'id',
}
}
});
// 2. ROOM FOR MIDDLEWARE to use for all requests
router.use(function(req, res, next) {
// do logging
console.log('In server.js');
// make sure we go to the next routes and don't stop here
next();
});
// Initialize epilogue
epilogue.initialize({
app: app,
sequelize: database
});
app.use(express.static(__dirname + "/public"));
app.get('/', function(req, res) {
res.redirect('/public/index.html');
});
// 3. Create REST resource
var employeeResource = epilogue.resource({
model: Employee,
endpoints: ['/api/employees', '/api/employees/:id']
});
var acctResource = epilogue.resource({
model: Account,
endpoints: ['/api/accounts', '/api/accounts/:id']
});
// Create database and listen
database
.sync({
force: false
})
.then(function() {
app.listen(port, function() {
console.log('listening at %s', port);
});
});