Accessing socket controller from model in NodeJS - node.js

So I'm working on a school project and we've got a code template.
I have a mysql database running, and whenever a certain table gets an insert, I want anyone viewing the table on the website to have it updated using socket.io.
But I cannot access the socketcontroller from the model, nor from the routecontroller(where I call the model). I understand that it's probably best practice to return the inserted data back to the controller(via callback) and then in the controller emit the message using io.
in index.js
var httpServer = http.Server(app);
var io = require('socket.io').listen(httpServer);
io.use(sharedsession(session));
var router = require('./controller.js');
app.use('/API', router);
var socketController = require('./socketController.js');
io.on('connection', function (socket) {
socketController(socket, io);
});
var model = require('./model.js');
in socketController.js
module.exports = function (socket, io) {
socket.on('join', function (req) {
console.log(req);
var security = req.security;
socket.join(security);
});
socket.on('update', function (req) {
//this is the function i want to "call" from the model/routecontroller
});
}
in controller.js
router.post('/orders', function(req, res) {
//var socket = io(); <--- not defined
model.matchOrder(req.body, function(result) {
res.json({order: req.body});
})
});
in model.js
function createTrade(security, price, amount, buyer, seller) {
console.log("Trade created");
//var socket = io() <-- not defined
Trades.create({
security: security,
price: price,
amount: amount,
buyer: buyer,
seller: seller
});
}
So 1. Where should i try to access the socket, in controller.js or model.js?
and 2. How do i get access to io() from controller.js/model.js?

Related

socket.io connects with same socket id

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.

Router.express() -> What is the proper way for expressing router.use?

For router.use, it does not work like this anymore:
router.use("/api", apiRoutes);
Instead an error is thrown:
throw new typeerror('router.use() requires a middleware function but got a ' + gettype(fn))
How do I re-purpose that expression so that it works? I have not found any examples that were useful so far. Here is some of my sample code:
routes/index.js (this does not work)
const path = require("path");
const router = require("express").Router();
const apiRoutes = require("./api");
// API Routes
router.use("/api", apiRoutes);**// this throws an error**
router.use(function(req, res) {
res.sendFile(path.join(__dirname, "../client/build/index.html"));
});
module.exports = router;
Here is an example of my attempt to re-purpose but I do not think it's correct:
var path = require("path");
var router = require("express").Router();
var apiRoutes = require("./api");
//API Routes
//authRouter.use(require('./authenticate').basic(usersdb))
//router.use("./api", apiRoutes);
console.log("Hitting API routes...")
router.use("./api", function(req, res, next) { **//re-purpsose attempt here**
res.send(apiRoutes)
console.log("API Routes:", apiRoutes)
next()
});
console.log("API Routes hit")
// //If no API routes are hit, send the React app
// router.use(function(req, res) {
// res.sendFile(path.join(__dirname, "../client/public/index.html"));
// });
module.exports = router
This is the overall error I'm getting (404 returned):
GET /api/website_1_function_call/scrape 404 4.004 ms - 173
I know that this may be due to something else indirectly but I really am not sure about the router.use part.
I know for sure that the routes are not being hit properly and would like to fix.
Any advice would be appreciated. Thank you in advance.
Here is more code:
server.js
require("dotenv").config();
var express = require("express");
var cors = require('cors');
var bodyParser = require('body-parser');
var logger = require("morgan");
//const mongoose = require("mongoose");
var db = require("./models")
var routes = require("./routes");
var app = express();
var PORT = process.env.PORT || 3001;
var path = require('path');
//Define middleware here
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(bodyParser.json());
//Serve up static assets (usually on heroku)
if (process.env.NODE_ENV === 'production') {
app.use(express.static("client/build"));
}
app.use(cors());
app.use(logger("dev"));
//Add routes, both API and view
app.use(routes);
//replaced with below:
//app.use(app.router);
//routes.initialize(app);
// //Connect to the Mongo DB
// mongoose.connect(process.env.MONGODB_URI || "mongodb://localhost/kaibru");
var syncOptions = { force: false };
// If running a test, set syncOptions.force to true
// clearing the `testdb`
if (process.env.NODE_ENV === "test") {
syncOptions.force = true;
};
// Starting the server, syncing our models ------------------------------------/
db.sequelize.sync(syncOptions).then(function() {
app.listen(PORT, function() {
console.log(
"==> 🌎 Listening on port %s. Visit http://localhost:%s/ in your browser.",
PORT,
PORT
);
});
});
// //Start the API server
// app.listen(PORT, function() {
// console.log(`🌎 ==> API Server now listening on PORT ${PORT}!`);
// });
routes/index.js
var path = require("path");
var router = require("express").Router();
var apiRoutes = require("./api");
//API Routes
//authRouter.use(require('./authenticate').basic(usersdb))
//router.use("/api", apiRoutes);
console.log("Hitting API routes...")
router.use("/api", function(req, res, next) { // this is my re-purpose
attempt
apiRoutes
console.log("API Routes:", apiRoutes)
// next()
}); // this is my r-purpose attempt
console.log("API Routes hit")
// //If no API routes are hit, send the React app
// router.use(function(req, res) {
// res.sendFile(path.join(__dirname, "../client/public/index.html"));
// });
module.exports = router
routes/api/index.js
var router = require("express").Router();
require("./website_1");
var website_1Routes = require("./website_1_function_call");
//const userRoutes = require("./user");
//Website_1 routes
//http://localhost:3000/api/website_1_function_call/scrape
//authRouter.use(require('./authenticate').basic(usersdb))
//router.use("/website_1_function_call", website_1Routes);
//experimental use
router.use("/website_1_function_call", function(req, res, next) { // this is my re-purpose attempt
website_1Routes
console.log("website_1Routes:", website_1Routes)
// next()
}); //this is my re-purpose attempt
//router.use("/user", userRoutes);
module.exports = router
routes/api/website_1_function_call.js
require("./website_1");
require("./website_1_db");
require("./website_1_router");
//Call scrape functions from website_1 file
mainscrape();
//specificScrape() //let's leave this one dormant for now
//Now for saving to database
saveToDatabase();
//Now for the routes
routing();
I think my re-purpose attempt worked ( I removed next() since there are no defined routes right after). It seems to be processing. However, now my response hangs and this happens:
GET /api/website_1_function_call/scrape - - ms - -
This prints in the browser console:
GET http://localhost:3000/api/website_1_function_call/scrape
net::ERR_EMPTY_RESPONSE
0.chunk.js:871 Uncaught (in promise) Error: Network Error
at createError (0.chunk.js:871)
at XMLHttpRequest.handleError (0.chunk.js:366)
So now I think my scraper code and my code to update the database does not work.
Scrape function code:
//var express = require("express");
var router = require("express").Router();
require("../../controllers/website_1controller");
//requiring this website's models
var Items_1 = require("../../models/website_1");
//require("./website_1_db");
//require("./website_1_router");
// Our scraping tools
// Axios is a promised-based http library, similar to jQuery's Ajax method
// It works on the client and on the server
var axios = require("axios");
var cheerio = require("cheerio");
mainscrape = function() {
//Now to configure the routes
router.get("/scrape", function(req, res) {
//instead of simple res.render, user router.get
console.log("scraping started...");
//Grab the html body with axios
axios.get("url placeholder").then(function(response) {
//Load to cheerio and save to $ selector
console.log("Scraping all greenheartshop mainpage...");
var $ = cheerio.load(response.data);
var output = [];
var promises = [];
//Now we need to grab the title reference for each article
$("article").each(function(i, element) {
//save empty result object
var result = {};
//thumbnail
result.thumbnail = $(this)
//.children("article.product-grid-item.product-block").html()
.children("figure.product-item-thumbnail")
.children("a")
.attr("href")
//console.log("result thumbnail")
//console.log(result)
console.log(result.thumbnail)
var result = {}
//details
result.detail= $(this)
//.children("product-item-mask").html()
.children("div.product-item-details")
// .children("div.product-item-brand")
// .children("h5.product-item-title")
// .children("a")
// .children("div.product-item-price")
//.children("product-price-line")
//.children("price-value")
.text()
//result.detail = result.detail.trim();
//console.log("result detail")
//console.log(result)
console.log(result.detail)
//Capture the scraped data and save to database
console.log("Capturing Scrape")
if(result.detail !== '') {
var promise = Items_1
.saveToDatabase(result, result, {upsert:true, new:true})
console.log("saveToDatabase");
promises.push(promise);
}
Promise.all(promises).then((data) => {
res.json(data);
});
//saveToDatabase();
// if (result.thumbnail !== {} && result.detail !== "") {
// var promise = Items_1
// // .items_1_create({
// // resultThumbnail: result.thumbnail,
// // resultDetails: result.detail
// // })
// promises.push(promise)
// // .then(dbModel => output.push(dbModel));
// Promise.all(promises).then((data) => {
// res.json(data)
// })
// }
});
});
//Now to CREATE the results using controller file
// console.log("creating items in the database now...")
// router.post('/scrape', website_1Controller.items_1_create);
//Now to display the results
// console.log("Items now being displayed...")
// router.get('/scrape/display', website_1Controller.items_1_list)
});
}
module.exports = router;
module.exports = mainscrape;
module.exports = specificScrape;
Code to update the database:
require("../../controllers/website_1controller");
require("./website_1");
var Items_1 = require( "../../models");
//After scraping the main page, the following function is to save to the
database
saveToDatabase = function() {
//prepare the data
var result = {}
var dataToStore = Items_1.items_1_create
console.log(dataToStore)
//console.log(items_1_create)
//insert data to the database
// dataToStore.save().// We will not sue this part for now
// then(() => {
// console.log("Data successfully saved");
// }).catch(err => {
// console.log("Error: ", err);
// });
}
module.exports = saveToDatabase;
Code for final routing (after scrape is complete)
var website_1Controller = require("../../controllers/website_1controller");
var router = require("express").Router();
routing = function() {
//Now to CREATE the results using controller file
console.log("creating items in the database now...")
//router.route("/browse")
router.post('/browse', website_1Controller.items_1_create);
router.get('/browse', website_1Controller.items_1_list);
//Now to display the results
console.log("Items now being displayed...")
//router.route("/browse:search")
router.get('/:search', website_1Controller.items_1_specific);
};
require("./website_1");
module.exports = routing;
module.exports = router;
models
'use strict';
// Dependencies
// =============================================================
// Sequelize (capital) references the standard library
//var Sequelize = require("sequelize");
// sequelize (lowercase) references our connection to the DB.
//var sequelize = require("../config/connection.js");
// Creates a "Items_1" model that matches up with DB
module.exports = function(sequelize, DataTypes) {
var Items_1 = sequelize.define("Items_1", {
// the routeName gets saved as a string
detail: DataTypes.STRING,
// the name of the character (a string)
thumbnail: DataTypes.BLOB,
// the character's role (a string)
//role: Sequelize.STRING,
// the character's age (a string)
//age: Sequelize.INTEGER,
// and the character's force points (an int)
//forcePoints: Sequelize.INTEGER
}, {
// disable the modification of tablenames; By default, sequelize will
automatically
// transform all passed model names (first parameter of define) into
plural.
// if you don't want that, set the following
freezeTableName: true
});
return Items_1;
//Syncs with DB
//Items_1.sync();
// Makes the Items_1 Model available for other files (will also create a table)
};
controller
// *********************************************************************************
// website_1controllers.js - this file offers a set of routes for displaying and saving data to the db
// *********************************************************************************
// Dependencies
// =============================================================
var db = require("../models");
//display results for mainpage scrape
exports.items_1_create = function(req, res) {
db.Items_1.findOneAndUpdate(req.body, req.body, {upsert: true, new:
true})
.then(dbModel => res.json(dbModel))
.catch(err => res.status(422).json(err))
console.log("findOneAndUpdate complete")
},
exports.items_1_list = function(req,res) {
db.Items_1.findAll({})
},
exports.items_1_specific = function(req,res) {
db.Items_1.findById(req.params.search)
},
function(err, results) {
if (err) { return next(err); } //Error in API usage.
if (results.result.thumbnail==={} && results.result.detail==="") {//No
Results.
var err = new Error('Results not found');
err.status = 404;
return next(err)
}
//Successful, so render
res.render("click_results", { title: 'Click Results', resultThumbnail:
result.thumbnail, resultDetails: result.detail });
}
So the new issue is that the response hangs. I think it's because the code to update the database does not work (using sequelize). Let me know if anything else is needed and thank you in advance.
Thanks for all of the input everyone. After reviewing I found out that the function itself does not have to be re-purposed as I initially thought... I didn't know that if, for example, you are using "router.use("/directoy", directory) and you are using it in succession to point to different directories, the final directory hit must have a defined route like router.get(). I modularized my code to the point where the final directory was just a list of functions (one of these functions had the router.get method). This did not work. When I point the file directly to the code containing the router.get method, my scraped data returns to the terminal. Just thought I'd share my findings at least because I didn't know this at all . Many thanks to #mehta-rohan and #Anand Undavia for the insights. I'm still trying to get the data to render to the page but that's a different problem altogether.

NodeJS/Express Room Joining when a View is accessed - Sockets

I'm creating a simple NodeJS/Express webapp that has an api and sockets. Basically, what I want to do is send data to my API from an external source, and then send this data to the socket in pageView/id. Right now it works, but it is sending data to all of the views, not the specific pageView/id.
I know I have to create Rooms and I tried doing this by having my sockets join the room when the webapp is navigated to page/id: (note I do not have logged in users)
router.get('/:id', function (req, res, next) {
res.io.on('connection', function(socket) {
console.log("user connected");
//join room here
});
});
But then this creates multiple connections, every time I refresh the pageView I get a new connection + previous connections on my server side.
How can I join the room when a pageView/id is accessed? Here is my setup...
app.js
var express = require('express');
var app = express();
var server = require('http').Server(app);
var io = require('socket.io')(server);
app.use(function(req, res, next){
res.io = io;
next();
});
module.exports = {app: app, server: server};
/bin/www
var app = require('../app').app;
var http = require('http');
var server = require('../app').server;
server.listen(port);
pageView.hbs
var socket = io.connect();
socket.on('mySocket', function (data) {
console.log(data);
});
pageView.js
router.post('/updatePage', function (req, res, next){
//send to the view
res.io.emit("mySocket", {
device: device
});
});
Ok, I understand now, your mistake is to create the listeners inside your route. You need only one set of event listeners on io. So, this would work :
var express = require('express');
var app = express();
var server = require('http').Server(app);
var io = require('socket.io')(server);
io.on('connection', function(socket) {
console.log("user connected");
socket.join(room);
});
module.exports = {app: app, server: server};
remember that you can pass parameters on your connection, for example, you can do :
var myroom = window.location.pathname.split('pageView/')[1]; //example to get room name, be creative !
var socket = io.connect("http://127.0.0.1:3000/", { query: 'room='+myroom+' });
and on the server :
io.on('connection', function(socket) {
console.log("user connected");
var room = socket.handshake.query.room; // === 'myRoom'
socket.join(room);
socket.emit("mySocket", {
device: device
});
});
pageView.js
router.post('/updatePage', function (req, res, next){
});

NodeJS, socketIO, multiple files

I'm a little bit confused;
I would like to use socketIO on NodeJS app.
I've created this (pseudo)code:
//server.js
var app = express();
//some code...
var router = require('./app/router');
app.use(router);
var server = app.listen(appConfig.app.port, function () {
var port = server.address().port;
});
var io = require('socket.io')(server);
io.on('connection', function (client) {
console.log('Client connected...');
client.on('join', function (data) {
console.log(data);
});
});
//client.js
var socket = io.connect('http://localhost:5555');
socket.on('connect', function(data) {
socket.emit('join', 'Hello World from client');
});
Everything is fine. But !
At now, I would like to emit event in another file.
I have router and POST request. I want to emit event on POST request (request handler is in another file).
//router.js
router.route("/addmenu").post(function (req, res) {
menuModel.addMenu(req.body,function(data){
//I WANT EMIT HERE
res.json(data)
});
};
);
I have to initialize router before start server, but I have to pass server to IO... How pass IO to router ?
You can try this
//server.js
var app = express();
//some code...
var io;
var getIOInstance = function(){
return io;
};
var router = require('./app/router')(getIOInstance);
app.use(router);
var server = app.listen(appConfig.app.port, function () {
var port = server.address().port;
});
io = require('socket.io')(server);
io.on('connection', function (client) {
console.log('Client connected...');
client.on('join', function (data) {
console.log(data);
});
});
//router.js
module.exports = function(getIOInstance){
router.route("/addmenu").post(function (req, res) {
menuModel.addMenu(req.body,function(data){
//I WANT EMIT HERE
getIOInstance().sockets.emit(...)
res.json(data)
});
};
return router;
);
This solution will work if you want to 'notify' all connected clients.
If you need to notify only a specific client, then I will advise you to use an event-emitter module in order to communicate these events and not share your socket instances across multiple files.
In router.js you can do something like:
//router.js
module.exports = function(io) {
var router = //What you declared it to be
router.route("/addmenu").post(function (req, res) {
menuModel.addMenu(req.body,function(data){
//I WANT EMIT HERE
res.json(data)
});
};
);
return router;
}
//server.js
//Change this line to be like the one below
var router = require('./app/router');
//.........
//.......
//Desired way
var router = require('./app/router')(io);
The answer of #jahnestacado does the job, but in case you already have an existing code base, then you need to change the structure of each file, where you might need the socket.io object, to pass it in as an argument.
A better way to do it then, would be:
To create the getIO() function—just as #jahnestacado did—, where you instantiate the io object (inside server.js), and export it.
var io;
exports.getIO = () => io;
Then require it wherever you need it. But make sure to execute the function only when you need it. Typically inside your controller function:
const getIO = require('../server').getIO;
exports.updateSAE = (req, res) => {
let io = getIO();
io.emit();
// rest of your controller function code
}
Note that I did not call the getIO function outside the controller function. For example, the following would probably not work:
const getIO = require('../server').getIO;
var io = getIO();
exports.updateSAE = (req, res) => {
io.emit();
// rest of your controller function code
}
Simply because the socket.io object could have not been initialized when you call the function, thus returning undefined.

Make two socket connections talk to each other

Here is the scenario...
I am working on an app I had an idea for, I'm building it in ember with an express backend. I am using the express-ws so I can run the ws websocket package inside express better. I was not able to get just ws to work with express.
My app will have two people connecting to two different url's that are socket connections, so that they can send and receive information to the server without the other getting it. At least that's the way I've come up in my mind to do it.
What I want is when one user does an interaction over the socket, for that socket to send a message to the other socket to perform an action and send it's information to the user connected on it.
I hope that makes sense. With express-ws here is what I have done so far which works at a basic level.
var express = require('express');
var app = express();
var expressWs = require('express-ws')(app);
app.use(function (req, res, next) {
console.log('middleware');
req.testing = 'testing';
return next();
});
app.get('/', function(req, res, next){
console.log('browser connected');
res.send('welcome to the api browser');
});
app.ws('/', function(ws, req) {
console.log('socket connected');
var object = {
message: 'welcome to the socket api',
time: Date.now().toString()
}
ws.send(JSON.stringify(object));
});
app.listen(1337);
I haven't made the other connection yet but for the time being it will be the same, but when the user on one connection sends a certain message to their socket, I want that socket to perform something and then pass some data to the other socket so it can send some information to it's user.
This might give you an idea of how to store the references for later use:
var express = require('express');
var app = express();
var expressWs = require('express-ws')(app);
// array to hold the connections
var openChannels = [];
app.use(function(req, res, next) {
console.log('middleware');
req.testing = 'testing';
return next();
});
app.get('/', function(req, res, next) {
console.log('browser connected');
res.send('welcome to the api browser');
});
app.ws('/', function(ws, req) {
console.log('socket connected');
// store connection for later reference
openChannels.push(ws);
// #todo: remove from array on disconnect
// set broadcast callback
ws.onmessage = function(msg) {
openChannels.forEach(function(index, item) {
if (item !== ws) { // make sure we're not sending to ourselves
item.send(msg);
}
});
};
var object = {
message: 'welcome to the socket api',
time: Date.now().toString()
}
ws.send(JSON.stringify(object));
});
app.listen(1337);

Resources