socket.io connects with same socket id - node.js

my socket creates problem when frontend loads before the server,
My problems are
1.I get same the socketid from the cookies of multiple clients
2.I get only one client who is connected with multiple socketids from the server
3.When I get this problem, my API calls will not work and I won't get any data from my database
I also get this problem when I restart the server, and when I refresh the frontend multiple times with different clients
my server side code
const mongoose = require("mongoose");
express = require("express");
app = express();
bodyParser = require("body-parser");
cookieParser = require("cookie-parser");
cors = require("cors");
user = require("./routes/user");
message = require("./routes/message");
http = require("http");
server = http.createServer(app);
io = require("socket.io")(server);
var userdata = require("./controllers/user");
mongoose
.connect(process.env.DATABASE, {
useNewUrlParser: true,
useUnifiedTopology: true,
useCreateIndex: true,
})
.then(() => {
console.log("DB CONNECTED");
})
.catch((err) => console.log(err));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(cors());
app.use(express.json());
app.use("/use", user);
app.use("/use", message);
let users = [];
io.on("connection", (socket) => {
socket.on("done", () => {
let userdata = require("./controllers/user");
console.log("connected");
userdata.userdata &&
users.push({ userid: userdata.userdata._id, socketid: socket.id });
console.log(users);
});
socket.broadcast.emit("message");
socket.on("more", function (c) {
console.log(c.a, c.b);
let d = users.find((s) => s.userid === c.b);
if (d) {
return io.to(d.socketid).emit("message", c);
}
});
socket.on("disconnect", () => {
console.log(socket.id);
if (users) {
for (let e = 0; users.length; e++) {
if (users[e] && users[e].socketid === socket.id) {
return users.splice(e, 1);
}
}
}
console.log(users);
return console.log("disconnected");
});
});
// app.use();
const port = process.env.PORT || 8000;
server.listen(port, () => {
console.log(`app is running at ${port}`);
});
I found that my problem is caused by userdata, when I had deleted everything related to userdata, I didn't get any problem even when the server is reloaded.
Here userdata comes from a middileware called isSignedIn,this middleware is called before every API call from this webpage, so userdata gets updated frequently by the frontend code.This is my isSignedIn function
exports.isSignedIn = async (req, res, next) => {
const header = req.headers["authorization"];
const token = header && header.split(" ")[1];
if (!token) return res.json("no token");
jwt.verify(token, "jsdhbcjsd", (err, User) => {
if (err) return res.json(`${err} not signedin`);
req.User = User;
exports.userdata = User;
next();
});
};
I tried to call isSignedIn() instead of importing userdata, which would be lot better, but I was getting an error from the headers, so I couldn't call this function.
error I get when I call this function isSignedIn()
Promise {
<rejected> TypeError: Cannot read property 'headers' of undefined
at exports.isSignedIn (D:\message\backend\controllers\user.js:86:22)
it tells about this line
const header = req.headers["authorization"];
I made sure that the socket gets connected in the frontend only after calling the APIs using await,so that the userdata gets updated before connecting to the socket.I had tested it in the console,socket gets connected only after calling APIs
async componentDidMount() {
//my API calls
await this.friends(token);
await this.findfriends(token);
//connect the socket
this.start();
this.recieve();
}}
My frontend code
const client = require("socket.io-client");
var socket
export default class Home extends Component {
constructor(props) {
super(props);
this.start = this.start.bind(this);
this.send = this.send.bind(this);
this.recieve = this.recieve.bind(this);
this.friends= this.friends.bind(this);
this.findfriends= this.findfriends.bind(this);
}
start(){
socket=client("http://localhost:8000");
}
send(){
socket.emit("more", c)
}
recieve(){
socket.on("message", c)
}
async componentDidMount() {
//my API calls
await this.friends(token);
await this.findfriends(token);
//connect the socket
this.start();
this.recieve();
}}
render(){
return(my data)
}
}

After thinking for a while about requesting headers,which isn't possible, I thought, why couldn't I get userid from the socket when just it gets connected, then I tried this code, it worked perfectly fine
client side
start = () => {
socket = client("http://localhost:8000");
socket.on("connect", () => {
return socket.emit("userinfo", this.state.User._id);
});
};
server side
socket.on("userinfo", function (user) {
users.push({ userid: user, socketid: socket.id });
console.log("C O N N E C T E D");
});

You can't reassign exports.userdata = User; in middleware. That will affect every single request that uses those exports so they will all end up looking at the same userdata, no matter which user they are. That's the source of your confusion. There's only one exports object for each module and everyone who uses that module sees the same exports object. So, you can't use exports for request-specific data.
I see you are already assigning req.User = User. That is an appropriate place to put request-specific data and other users of that data in the processing of the request should get the data from req.User, not from the exported object. That will keep the data separate for each request and each user.

Related

When I click the client site then show this error

This is my Code
const express = require('express');
const { MongoClient } = require('mongodb');
const cors = require('cors');
require('dotenv').config()
const app = express();
const port = 5000;
// middle ware
app.use(cors());
app.use(express.json());
const uri = `mongodb+srv://${process.env.DB_USER}:${process.env.DB_PASS}#cluster0.84pml.mongodb.net/myFirstDatabase?retryWrites=true&w=majority`;
const client = new MongoClient(uri, { useNewUrlParser: true, useUnifiedTopology: true });
client.connect(err => {
const collection = client.db("carMechanic").collection("services");
// perform actions on the collection object
client.close();
});
async function run() {
try {
await client.connect();
const database = client.db("carMechanic");
const servicesCollection = database.collection("services");
// post API
app.post('/services', async (req, res) => {
const service = req.body;
// console.log('hit the post is', service);
const result = await servicesCollection.insertOne(service);
console.log(result);
res.send('post hitted')
});
}
finally {
// await client.close();
}
}
run().catch(console.dir);
app.get('/', (req, res) => {
res.send('Running Genius Server');
});
app.listen(port, () => {
});
And This is the error message
G:\web_projects\practice\node\module-65-Genius-car\backend\node_modules\mongodb\lib\utils.js:690
throw new error_1.MongoRuntimeError(`illegal state transition from [${target.s.state}] => [${newState}], allowed: [${legalStates}]`);
^
MongoRuntimeError: illegal state transition from [closed] => [connected], allowed: [closed,connecting]
at stateTransition (G:\web_projects\practice\node\module-65-Genius-car\backend\node_modules\mongodb\lib\utils.js:690:19)
at G:\web_projects\practice\node\module-65-Genius-car\backend\node_modules\mongodb\lib\sdam\topology.js:226:21
at G:\web_projects\practice\node\module-65-Genius-car\backend\node_modules\mongodb\lib\cmap\connection_pool.js:272:25
at handleOperationResult (G:\web_projects\practice\node\module-65-Genius-car\backend\node_modules\mongodb\lib\sdam\server.js:363:9)
at MessageStream.messageHandler (G:\web_projects\practice\node\module-65-Genius-car\backend\node_modules\mongodb\lib\cmap\connection.js:479:9)
at MessageStream.emit (events.js:375:28)
at processIncomingData (G:\web_projects\practice\node\module-65-Genius-car\backend\node_modules\mongodb\lib\cmap\message_stream.js:108:16)
at MessageStream._write (G:\web_projects\practice\node\module-65-Genius-car\backend\node_modules\mongodb\lib\cmap\message_stream.js:28:9)
at writeOrBuffer (internal/streams/writable.js:358:12)
at MessageStream.Writable.write (internal/streams/writable.js:303:10)
The problem is this line:
client.connect(err => {
const collection = client.db("carMechanic").collection("services");
// perform actions on the collection object
client.close();
});
you don't need this because you made connection to the database later
I answered myself.
I had made a mistake, that was I wrote the extra code given below.
client.connect(err => {
const collection = client.db("carMechanic").collection("services");
// perform actions on the collection object
client.close();
});
Because this has already been declared inside of my function.
await client.connect();
const database = client.db("carMechanic");
const servicesCollection = database.collection("services");
When I remove this--
client.connect(err => {
const collection = client.db("carMechanic").collection("services");
// perform actions on the collection object
client.close();
});
the code is working well.
I have my code below and works as is. A few things:
Install dependencies again if needed
I am using the sample airbnb db they provide in Atlas service on cloud.mongodb.com
Change username:0987654321 to your own username and password where 'username' is the username and 0987654321 is the password.
Run() works fine locally but it's not straight forward to have a local method start a server endpoint and keep track of it or vice versa. For that you need good chaining
So only USE the run method to test locally and it works fine or use the express endpoints to test via your app client side or curl or postman and that works too independently.
Heres my whole server file:
const express = require('express');
const { MongoClient } = require('mongodb');
const cors = require('cors');
// middle ware
const app = express();
const port = 5000;
// middle ware
app.use(cors());
app.use(express.json());
const uri = "mongodb://username:0987654321#cluster0-shard-00-00.czraf.mongodb.net:27017,cluster0-shard-00-01.czraf.mongodb.net:27017,cluster0-shard-00-02.czraf.mongodb.net:27017/sample_airbnb?ssl=true&replicaSet=atlas-wmfxrm-shard-0&authSource=admin&retryWrites=true&w=majority";
async function run(){
//1 connect
const client = await MongoClient.connect(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
//2 set db and collection
const collection = client.db("sample_airbnb").collection("listingsAndReviews");
// perform actions on the collection object
//3 CREATE
let doc = {
"_id":"100009090",
"listing_url":"https://www.airbnb.com/rooms/100009190",
"name":"Ribeira Charming Duplex",
"summary":"Fantastic duplex apartment with three bedrooms, located in the historic area of Porto, Ribeira (Cube)",
"neighborhood_overview":"In the neighborhood of the river, you can",
"price":"250gbp"
}
//4.0 POST INDEPENDENTLY
//collection.insertOne(doc).then(doc => { console.log('inserted id is: ', doc.insertedId)});
//FIND ONE
//const result = await collection.find( {_id: {$eq: "10006546"} }).toArray();
//FIND MANY
//const many = await collection.find( {_id: { $in: ["10006546", "100009090"] } } ).toArray();
//LOGGER
//console.log(many)
client.close();
};
//server methods
//get route
app.get('/', (req, res) => {
res.send('Running Genius Server');
});
//get docs
app.get('/docs', async (req, res) => {
//FIND MANY
const many = await collection.find( {_id: { $in: ["10006546", "100009090"] } } ).toArray();
//LOGGER
console.log(many)
res.send('docs returned', JSON.stringify(many));
});
//add docs
app.post('/test', async (req, res) => {
//get data through body
const service = req.body.name;
//get data through query params in url
const qparam = req.query.name;
const _id = req.query.id;
//build your doc
let doc = {service:qparam, _id: _id};
//connect to db and collection
//2 set db and collection
//1 connect
const client = await MongoClient.connect(uri, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
const collection = client.db("sample_airbnb").collection("listingsAndReviews");
collection.insertOne(doc).then(doc => { console.log('inserted id is: ', doc.insertedId)});
// /res.status(status).send(body)
res.send(JSON.stringify(service + ' ' + qparam));
});
app.listen(port, () => {
});
run();

NodeJS express getting hanging requests on Windows 10

I have been getting hanging requests, with only expressjs, on my windows 10 machine. The code I will post is the current version of the code; it was tried a) without async, and b) without redis anywhere and just the 'test' route. The request just hangs, whether called from Postman or accessed directly from a browser. Admin privileges were given with the same result.
const express = require('express')
import { createClient } from "redis"
import { UserApi } from "./api/users"
(async () => {
const app = express()
const client = createClient()
//Check redis
client.on('error', (err) => console.log('Redis Client Error', err))
//Connect to redis client
await client.connect()
//Create API instances
const ua = new UserApi(client)
//Initialize middleware\
app.use(express.json)
//Create routes
//test
app.get("/test", function(req, res) {
res.send("got")
})
//signup - creates user
app.post("/signup", async function(req, res) {
console.log("starting")
const u = {username: req.body.username, email: req.body.email, password: req.body.password}
console.log("why")
try {
console.log("trying")
const token = await ua.create(u)
res.send(token)
} catch (error) {
res.status(400).send(error)
}
})
app.listen(3000, () => {
console.log("server up")
})
}) ()

How to organize socket-io files in an express server?

I have recently started using socket-io for a live-chatting feature in a project of mine. I have everything working fine but as of now, I have all the server side socket-io stuff (connection, middleware, event handlers, etc.) in the main "index.js" file. It isn't a big deal now as I am only listening to a couple of events, but I would like to organize and separate the code into smaller files before it gets out of hand.
Here is an example of what the socket-io portion of the code looks like in my index.js file:
const express = require("express");
const app = express();
const http = require("http");
const server = http.createServer(app);
const { Server } = require("socket.io");
const io = new Server(server);
const jwt = require("jsonwebtoken");
const activeSockets = {};
io.use((socket, next) => {
const { token } = socket.handshake.auth;
if (!token) return next(new Error("Invalid or missing token"));
const { _id } = jwt.verify(token, process.env.JWT_KEY);
socket.handshake.auth._id = _id;
next();
});
const addSocket = (socket) => {
const { _id } = socket.handshake.auth;
if (!activeSockets[_id]) activeSockets[_id] = [socket.id];
else activeSockets[_id] = [...activeSockets[_id], socket.id];
};
const removeSocket = (socket) => {
const { _id } = socket.handshake.auth;
if (!_id || !activeSockets[_id]) return;
const index = activeSockets[_id].indexOf(socket.id);
activeSockets[_id].splice(index, 1);
if (activeSockets[_id].length < 1) delete activeSockets[_id];
};
io.on("connection", (socket) => {
addSocket(socket);
socket.on("typing", (isTyping, recipients, conversation, sender) => {
recipients.forEach((recipient) => {
if (activeSockets[recipient._id]) {
activeSockets[recipient._id].forEach((r) => {
socket.to(r).emit("typing", isTyping, conversation, sender);
});
}
});
});
socket.on("sendMessage", ({ message, recipients, conversation }) => {
recipients.forEach((recipient) => {
if (activeSockets[recipient._id]) {
activeSockets[recipient._id].forEach((r) => {
socket.to(r).emit("receiveMessage", { message, conversation });
});
}
});
});
socket.on("disconnect", () => {
removeSocket(socket);
});
});
const port = process.env.PORT || 5000;
server.listen(port, () => {
console.log("Listening: ", port);
});
I'm just struggling to find an efficient way to extract the socket-io code into smaller more organized pieces. Should the only thing in index.js related to socket-io be the connection itself? And then I have files for different event handlers that take an "io" parameter and then I call "io.on(...)" in those external functions? Or perhaps should I listen for all the events in index.js and then extract only the logic of each event into separate files? Something like:
io.on("eventName", someExternalFunction)
This is my first experience with socket-io so I'm not too sure of the "best practices".
Thank you to anyone who can offer help!
You could put the socket event handlers into modules like.
chat/connection.js:
module.exports = (io) => {
io.on("connection", (socket) => {
console.log('connection was made');
});
}
Then in index.js require('./chat/connection.js')(io);

NodeJS trouble sending messages to client

I am trying to learn Node.JS, but making a REST API using this tutorial:
https://medium.freecodecamp.org/building-a-simple-node-js-api-in-under-30-minutes-a07ea9e390d2
I am having a very stupid little issue I can't seem to fix. In my user_routes.js file, I am trying to write several messages to the express() app, however it never works after the first res.send() call. Why is this? I can't find anywhere in my code where I am closing the connection or whatever, so why can't I write more than once to the request?
My user_routes.js
module.exports = function(app, db) {
app.post('/user', function (req,res) {
res.send("User Request Recieved via POST");
// Add the user to the database, if they don't already exist
const firstName = req.body.firstName;
const lastName = req.body.lastName;
const email = req.body.email;
const password = req.body.password;
const user = {
firstName: firstName,
lastName : lastName,
email : email,
password : password
};
if (db.collection('users').find({'email':email}).count() == 0) {
res.send('Unique Email');
db.collection('users').insert(user, (err, result) => {
if (err) {
console.log("error");
} else {
console.log(result.ops[0])
}
});
} else {
res.send("Email already in use")
}
})
};
Any my server.js:
const express = require('express');
const MongoClient = require('mongodb').MongoClient;
const bodyParser = require('body-parser');
const app = express();
const port = 6969;
const db = require('./config/db')
// We need to decode data from the url using the body-parser lib
app.use(bodyParser.urlencoded({ extended: true }));
MongoClient.connect(db.url).then( function(db) {
require('./app/routes')(app, db);
app.listen(port, () => {
console.log('We are live on ' + port);
});
}).catch (function (err) {
console.log(err);
});
module.exports = app;
I don't seem to close the connection anywhere, so why is it I am only able to write one message to the client?
res.send() is the last thing your function should be doing. Think of it like a return for your function, you can't return multiple times.
res.send() == return()
res.send() is equivalent to "return" for your post -- you can only do it once per call.
Multiple Messages per res.send()
If you want to send multiple messages through one call, you need to compile an object/array of messages you want to send and send that object/array through your res.send(). Example:
ret_msg = [];
ret_msg.push("Here's your first message.");
ret_msg.push("Here's your second message.");
ret_msg.push("Here's your third message.");
res.send(ret_msg);

Authenticating socket io connections using JWT

How can I authenticate a socket.io connection? My application uses a login endpoint from another server (python) to get a token, how can I get use that token whenever a user opens a socket connection on the node side?
io.on('connection', function(socket) {
socket.on('message', function(message) {
io.emit('message', message);
});
});
And the client side:
var token = sessionStorage.token;
var socket = io.connect('http://localhost:3000', {
query: 'token=' + token
});
If the token is created in python:
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256')
How can I use this token to authenticate a socket connection in node?
It doesn't matter if the token was created on another server. You can still verify it if you have the right secret key and algorithm.
Implementation with jsonwebtoken module
client
const {token} = sessionStorage;
const socket = io.connect('http://localhost:3000', {
query: {token}
});
Server
const io = require('socket.io')();
const jwt = require('jsonwebtoken');
io.use(function(socket, next){
if (socket.handshake.query && socket.handshake.query.token){
jwt.verify(socket.handshake.query.token, 'SECRET_KEY', function(err, decoded) {
if (err) return next(new Error('Authentication error'));
socket.decoded = decoded;
next();
});
}
else {
next(new Error('Authentication error'));
}
})
.on('connection', function(socket) {
// Connection now authenticated to receive further events
socket.on('message', function(message) {
io.emit('message', message);
});
});
Implementation with socketio-jwt module
This module makes the authentication much easier in both client and server side. Just check out their examples.
client
const {token} = sessionStorage;
const socket = io.connect('http://localhost:3000');
socket.on('connect', function (socket) {
socket
.on('authenticated', function () {
//do other things
})
.emit('authenticate', {token}); //send the jwt
});
Server
const io = require('socket.io')();
const socketioJwt = require('socketio-jwt');
io.sockets
.on('connection', socketioJwt.authorize({
secret: 'SECRET_KEY',
timeout: 15000 // 15 seconds to send the authentication message
})).on('authenticated', function(socket) {
//this socket is authenticated, we are good to handle more events from it.
console.log(`Hello! ${socket.decoded_token.name}`);
});
Since I don't have enough reputation to add a comment to accepted answer:
const socketioJwt = require('socketio-jwt');
Is not actually apart of auth0 's repo and is a third-party community based package.
Answer above should be updated as the link to auth0 repo is a page 404.
Here I wrote the indepth article on how to authenticate the user on the sockets and also save the user's data
https://medium.com/#tameemrafay/how-to-authenticate-user-and-store-the-data-in-sockets-19b262496feb
CLIENT SIDE CODE
import io from "socket.io-client";
const SERVER = "localhost:4000";
const socket = io(SERVER, {
auth: {
token: "2c87b3d5412551ad69aet757f81f6a73eb919e5b02467aed419f5b2a9cce2b5aZOzgaM+bpKfjdM9jvez37RTEemAp07kOvEFJ3pBzvj8="
}
});
socket.once('connect', (socketConnection) => {
console.log('socket connected', socketConnection);
})
// Emit the message and receive data from server in callback
socket.emit("user send message", {message: "Hi you there" }, callback => {
if (callback) {
console.log("--- response from server", callback);
}
});
SERVER SIDE CODE
const initializeSockets = (io) => {
io.on('connection', (socket) => {
decryptAndStoreUserData(socket,io);
}
const decryptAndStoreUserData = async (socket,io) => {
const { token } = socket.handshake.auth; // receive the token from client
// here i decypt the auth token and get the user data
const genToken = new Middlewares().token();
const userData = genToken.decryptTokenForSockets(token);
// save the data of user in socket
socket.data.user = userData;
}
Write a middleware:
const jwt = require("jsonwebtoken");
const authSocketMiddleware = (socket, next) => {
// since you are sending the token with the query
const token = socket.handshake.query?.token;
try {
const decoded = jwt.verify(token, process.env.TOKEN_SECRET_KEY);
socket.user = decoded;
} catch (err) {
return next(new Error("NOT AUTHORIZED"));
}
next();
};
module.exports = authSocketMiddleware;
Use it inside socket server
socketServer = (server) => {
const io = require("socket.io")(server, {
cors: {
origin: "*",
methods: ["GET", "POST"],
},
});
// before connection use the middleware
io.use((socket, next) => {
authSocketMiddleware(socket, next);
});
io.on("connection", (socket) => {
console.log("user connected", socket.id);
});
};

Resources