I am trying to build simple social network and I am following this book(Building Node Applications with MongoDB and Backbone)(https://github.com/Swiftam/book-node-mongodb-backbone/tree/master/ch10). However, I just realized that the node.js version has been updated.
I tied to solve some the issue however I got problem in chat.js that states this is the error:
ch10/routes/chat.js:27
data.sessionStore.load(data.sessionID, function(err, session) {
TypeError: Cannot read property 'load' of undefined
module.exports = function(app, models) {
var io = require('socket.io');
var utils = require('connect').utils;
var cookie = require('cookie');
this.io = io;
//var Session = require('connect').middleware.session.Session;
var sio = io.listen(app.server);
sio.configure(function() {
// Utility methods to see if the account is online
app.isAccountOnline = function(accountId) {
var clients = sio.sockets.clients(accountId);
return (clients.length > 0);
};
sio.set('authorization', function(data, accept) {
var signedCookies = cookie.parse(data.headers.cookie);
// var cookies = utils.parseSignedCookies(signedCookies, app.sessionSecret);
// data.sessionID = cookies['express.sid'];
data.sessionStore = app.sessionStore;
data.sessionStore.load(data.sessionID, function(err, session) {
if (err || !session) {
accept("Error", false);
} else {
data.session = session;
accept(null, true);
}
});
});
sio.sockets.on('connection', function(socket) {
var session = socket.handshake.session;
var accountId = session.accountId;
var sAccount = null;
socket.join(accountId);
io.use(function (socket, next) { next(); });
// Immediately trigger the login event
// of this account
app.triggerEvent('event:' + accountId, {
from: accountId,
action: 'login'
});
var handleContactEvent = function(eventMessage) {
socket.emit('contactEvent', eventMessage);
};
var subscribeToAccount = function(accountId) {
var eventName = 'event:' + accountId;
app.addEventListener(eventName, handleContactEvent);
console.log('Subscribing to ' + eventName);
};
// Find the account contacts and subscribe
models.Account.findById(accountId, function subscribeToFriendFeed(account) {
var subscribedAccounts = {};
sAccount = account;
account.contacts.forEach(function(contact) {
if (!subscribedAccounts[contact.accountId]) {
subscribeToAccount(contact.accountId);
subscribedAccounts[contact.accountId] = true;
}
});
// Subscribed to my feed as well
if (!subscribedAccounts[accountId]) {
subscribeToAccount(accountId);
}
});
// Remove listeners if socket disconnects
socket.on('disconnect', function() {
sAccount.contacts.forEach(function(contact) {
var eventName = 'event:' + contact.accountId;
app.removeEventListener(eventName, handleContactEvent);
console.log('Unsubscribing from ' + eventName);
});
app.triggerEvent('event:' + accountId, {
from: accountId,
action: 'logout'
});
});
var cookieParser = require('cookie-parser')(SESSION_SECRET);
// ### Cookie parser
// Wrapper arround Express cookie parser, so we can use the same cookie parser for socket.io.
// Parse Cookie header and populate `socket.request.cookies` with an object keyed by the cookie names.
// Uses signed cookies by passing a secret string, which assigns `socket.request.secret` so it may be used by other middleware.
function cookieParserWrapper (socket, next) {
// request, response and callback
cookieParser(socket.request, {}, next);
}
// Handle incoming chats from client
socket.on('chatclient', function(data) {
sio.sockets.in(data.to).emit('chatserver', {
from: accountId,
text: data.text
});
});
});
});
}
Without testing the code myself or anything.
"TypeError: Cannot read property 'load' of undefined"
That particular error means that data.sessionStore is undefined and that "load" does not exists as a property, since there is literally nothing defined in data.sessionStore.
So the problem in my opinion is that your session system is not working properly. Hope that helps a bit!
Related
I am having some issues using socket.io is modules. I have changed the way I do it quite drastically, however everything seems to be working, except being able to send userdata back to my socket connection:
Here is my io.js file: /config/io
/*jshint esversion: 6*/
var io = require('socket.io')();
const moment = require('moment');
// Socket stuff
io.on('connection', function (socket) {
socket.on('login', function (userdata) {
socket.handshake.session.userdata = userdata;
socket.handshake.session.save();
console.log(socket.handshake.session.userdata);
});
// Server Time
var interval = setInterval(function () {
var momentNow = moment();
var data = momentNow.format('LT');
socket.emit('time', data);
}, 60000);
// Chat - Needs work
socket.on('chat', function (msg) {
console.log(msg);
var username = 'Message'; //socket.handshake.session.userdata.username;
var message = '[' + moment().format('LT') + '] ' + username + ': ' + msg;
io.emit('message', message, username);
});
socket.on('disconnect', function () {
if (socket.handshake.session.userdata) {
delete socket.handshake.session.userdata;
socket.handshake.session.save();
}
console.log('user disconnected');
});
});
module.exports = io;
Here is where I'm trying to emit the data /config/passport: (please note that userdata does indeed contain the right information!)
/*jshint esversion: 6 */
const LocalStrategy = require('passport-local').Strategy;
const db = require('../config/db');
const bcrypt = require('bcryptjs');
var io = require('./io');
module.exports = function(passport) {
// Local Strategy login
passport.use(new LocalStrategy(function(username, password, done) {
// Match Username
let sql = 'SELECT * FROM users WHERE username = ?';
db.query(sql, [username], function(err, rows) {
if (err)
return done(err);
if (!rows.length) {
return done(null, false, {
type: 'loginMessage',
message: 'Wrong Login',
});
}
// Match Password
bcrypt.compare(password, rows[0].password, function(err, isMatch) {
if (err)
return done(err);
if (isMatch) {
var userdata = rows[0];
io.emit('login', userdata); // HERE IS WHERE I TRY TO EMIT IT
// console.log(rows[0]);
return done(null, rows[0]);
} else {
return done(null, false, {
type: 'loginMessage',
message: 'Wrong Login',
});
}
});
});
}));
Now here is my main app file: (leaving out a bunch of stuff)
var io = require('./config/io');
// Init App
const app = express();
// Init http server
const server = http.createServer(app);
// Attach IO
io.attach(server);
// Listen
server.listen(8080, function () {
console.log('Server listening on port 8080...');
});
Now, everything seems to be working fine, except being able to emit the data. Now I tried logging it client side as well (just in case it was emitting on client-side and not server-side) but it is not doing that as well.
Okay, so here is an actual working answer. It s a work-around, and I completely abandoned trying to do it from the passport login handler itself. But here is how I did it:
IO code:
var session = socket.handshake.session;
socket.on('login', function () {
if (socket.handshake.session.passport === undefined) {
var destination = '/';
socket.emit('not logged', destination);
} else {
console.log('user logged in');
var userId = session.passport.user;
var sql = 'SELECT * FROM users WHERE id = ?';
var query = db.query(sql, userId, function (err, rows) {
session.userdata = rows[0];
session.save();
var dataObj = session.userdata;
socket.emit('sart up', dataObj);
});
}
});
And jQuery:
// Connection Successful
socket.on('connect', function () {
connected = true;
socket.emit('login');
});
socket.on('disconnect', function () {
connected = false;
});
socket.on('not logged', function (destination) {
window.location.href = destination;
});
socket.on('start up', function (dataObj) {
});
I'm not a huge fan of having to do it this way, I would have liked to handle everything sever-sided, but for now this is working, and will use until I figure out how to do it the way I'd like to.
I am using digits web. I am using the cannonball example. I am running the below code on my local comptuter.
Heres my code of client side
<script>document.getElementById('digits-sdk').onload = function() {
Digits.init({ consumerKey: 'my consumer key' });
Digits.embed({
container: '#my-digits-container',
theme: {
/* Input fields borders */
},
phoneNumber: '+91',
})
.done(onLogin) /*handle the response*/
.fail(onLoginFailure);
};
function onLoginFailure(loginResponse) {
console.log('Digits login failed.');
//setDigitsButton('Verify again');
}
/* Validate and log use in. */
function onLogin(loginResponse){
// Send headers to your server and validate user by calling Digits’ API
//var oAuthHeaders = loginResponse.oauth_echo_headers;
var oAuthHeaders = parseOAuthHeaders(loginResponse.oauth_echo_headers);
$.ajax({
type: 'POST',
url: '/digits',
data: oAuthHeaders,
success: onDigitsSuccess
});
// setDigitsButton('Step 2.....');
}
function parseOAuthHeaders(oAuthEchoHeaders) {
var credentials = oAuthEchoHeaders['X-Verify-Credentials-Authorization'];
var apiUrl = oAuthEchoHeaders['X-Auth-Service-Provider'];
console.log(apiUrl);
return {
apiUrl: apiUrl,
credentials: credentials
};
}
function onDigitsSuccess(response) {
console.log(response.phoneNumber);
setDigitsNumber(response.phoneNumber);
}
function setDigitsNumber(phoneNumber) {
document.getElementById('notr').value = phoneNumber;
console.log('Digits phone number retrieved.');
}
</script>
In the above code I have changed the consumer key only. So ignore that.
And heres my server code
var express = require("express");
var app = express();
var router = express.Router();
var path = __dirname + '/static/';
app.use(express.static(__dirname + '/static'));
router.use(function (req,res,next) {
console.log("/" + req.method);
next();
});
router.get("/",function(req,res){
res.sendFile(path + "homepage9.html");
});
router.get("/about",function(req,res){
res.sendFile(path + "about.html");
});
router.get("/contact",function(req,res){
res.sendFile(path + "contact.html");
});
app.use("/",router);
app.use("*",function(req,res){
res.sendFile(path + "404.html");
});
app.listen(3000,function(){
console.log("Live at Port 3000");
});
var fs = require('fs');
var nconf = require('nconf');
var url = require('url');
var request = require('request');
router.post('/digits', function (req, res) {
console.log("digits entered")
var apiUrl = req.body['apiUrl']
var credentials = req.body['credentials']
var verified = true;
var messages = [];
if (credentials.indexOf('oauth_consumer_key="' + 'my consumer key' + '"') == -1) {
verified = false;
messages.push('The Digits API key does not match.');
}
var hostname = url.parse(req.body.apiUrl).hostname;
if (hostname != 'api.digits.com' && hostname != 'api.twitter.com') {
verified = false;
messages.push('Invalid API hostname.');
}
// Do not perform the request if the API key or hostname are not verified.
if (!verified) {
return res.send({
phoneNumber: "",
userID: "",
error: messages.join(' ')
});
}
// Prepare the request to the Digits API.
var options = {
url: apiUrl,
headers: {
'Authorization': credentials
}
};
// Perform the request to the Digits API.
request.get(options, function (error, response, body) {
if (!error && response.statusCode == 200) {
// Send the verified phone number and Digits user ID.
var digits = JSON.parse(body)
return res.send({
phoneNumber: digits.phone_number,
userID: digits.id_str,
error: ''
});
} else {
// Send the error.
return res.send({
phoneNumber: '',
userID: '',
error: error.message
});
}
});
});
But on the node console i am getting
cannot read property 'apiUrl' of undefined.
on google chrome console i am getting
Failed to load resource: the server responded with a status of 500 (Internal Server Error)
Can any one help of what I am doing wrong.
Also in the cannon ball example i found that it nowhere uses the consumer secret key. Why is that?
I have models: Team, Project, Task. Tasks in projects, projects in teams, teams consists users.
I'm creating my app like in example - https://docs.strongloop.com/display/MSG/Building+a+real-time+app+using+socket.io+and+AngularJS
In my example:
server/server.js
...
app.use(loopback.token({ model: app.models.accessToken }));
// Bootstrap the application, configure models, datasources and middleware.
// Sub-apps like REST API are mounted via boot scripts.
boot(app, __dirname, function(err) {
if (err) throw err;
// start the server if `$ node server.js`
if (require.main === module) {
//Comment this app.start line and add following lines
//app.start();
app.io = require('socket.io')(app.start());
require('socketio-auth')(app.io, {
authenticate: function (socket, value, callback) {
var AccessToken = app.models.AccessToken;
//get credentials sent by the client
var token = AccessToken.find({
where:{
and: [{ userId: value.userId }, { id: value.id }]
}
}, function(err, tokenDetail){
if (err) throw err;
if(tokenDetail.length){
callback(null, true);
} else {
callback(null, false);
}
}); //find function..
} //authenticate function..
});
app.io.on('connection', function(socket){
console.log('a user connected');
socket.on('disconnect', function(){
console.log('user disconnected');
});
});
}
});
server/pubsub.js
'use strict';
var loopback = require('loopback');
//Writing pubsub module for socket.io
module.exports = {
//Publishing a event..
publish: function(socket, options ){
var ctx = loopback.getCurrentContext();
if(options){
var collectionName = options.collectionName;
var method = options.method;
var data = options.data;
var modelId = options.modelId;
if(method === 'POST'){
//console.log('Posting new data');
var name = '/' + collectionName + '/' + method;
socket.emit(name, data);
}
else{
var name = '/' + collectionName + '/' + modelId + '/' + method;
socket.emit(name, data);
}
}else{
throw 'Error: Option must be an object type';
}
}, //End Publish..
isEmpty:function (obj) {
var hasOwnProperty = Object.prototype.hasOwnProperty;
// null and undefined are "empty"
if (obj == null) return true;
// Assume if it has a length property with a non-zero value
// that that property is correct.
if (obj.length > 0) return false;
if (obj.length === 0) return true;
// Otherwise, does it have any properties of its own?
// Note that this doesn't handle
// toString and valueOf enumeration bugs in IE < 9
for (var key in obj) {
if (this.hasOwnProperty.call(obj, key)) return false;
}
return true;
} //isEmpty function..
}
common/models/task.js
var pubsub = require('../../server/pubsub.js');
var loopback = require('loopback');
module.exports = function(Task) {
//Task after save..
Task.observe('after save', function (ctx, next) {
console.log('Task after save');
var socket = Task.app.io;
if(ctx.isNewInstance){
//Now publishing the data..
pubsub.publish(socket, {
collectionName : 'Task',
data: ctx.instance,
method: 'POST'
});
}else{
//Now publishing the data..
pubsub.publish(socket, {
collectionName : 'Task',
data: ctx.instance,
modelId: ctx.instance.id,
method: 'PUT'
});
}
//Calling the next middleware..
next();
}); //after save..
//TaskDetail before delete..
Task.observe("before delete", function(ctx, next){
var socket = Task.app.io;
//Now publishing the data..
pubsub.publish(socket, {
collectionName : 'Task',
data: ctx.instance.id,
modelId: ctx.instance.id,
method: 'DELETE'
});
//move to next middleware..
next();
}); //before delete..
}; //Module exports..
I want deliver task, project, team changes via sockets. Some projects or tasks can be private. It means that only invited to project/task members can see them. Where can I put my logic witch determines who will receive notification? In general, all team members have to receive changes in tasks, projects and teams, but in private tasks and projects is another logic.
What is the best way to do it? Create namespace or room like team/team_id for common case and send individual notification in private case. Or is it better to create namespace or room for each connected user and on task change check who have to receive changes and send to them?
In my example, when I save a task all users receives this task via sockets...
Thanks.
What is going wrong with my string parameter?
var express = require('express');
var app = module.exports = express();
var mongoose = require('mongoose');
var bodyParser = require('body-parser');
var braintree = require("braintree");
Schema = mongoose.Schema;
var user = require('../shared/userFunctions.js')
//register functions
app.register = function(api) {
api.get('get_client_token', generateClientToken);
api.get('find_customer', findCustomer);
api.post('checkout', checkout);
api.post('create_customer', createCustomer);
api.post('create_payment_method', newPaymentMethod);
}
The checkout function is where I call the local function with user.getuser
function checkout(request, response) {
var email = request.body.email;
var nonce = request.body.payment_method_nonce;
//var nonce = req.param("payment_method_nonce");
var amount = request.body.amount;
// Use payment method nonce here
gateway.transaction.sale({
amount: amount,
paymentMethodNonce: nonce,
}, function (err, result) {
if(err){
return response.send(500, "Checkout failed")
}
/* request.add({"amount": 10})
request = nonce;
newPaymentMethod(request);*/
/* return res.send(200, "Checkout Success")*/
});
user.getuser(email, function(u){
console.log("returning user: " + JSON.stringify(u))
return response.send(200, JSON.stringify(u))
})
}
If I hard core the email address into the mongoose query, it returns the user. What gives? Please give advice on my node async style. I am still new to it, but sometimes error first fucntions don't work and sometimes I need "next". The static email works but is my style the problem?
exports.getuser = function(email, res) {
var db = mongoose.connection;
mongoose.connect(process.env.MongoConnectionString);
db.on('error', function () {
});
db.once('open', function callback() {
console.log("Sucessfully Logged into mongo");
User.findOne({email:email}, function (err, user, next) {
if (err) {
mongoose.disconnect();
return next(err);
}
mongoose.disconnect();
console.log("Sending user response");
if(!user){
console.log("failed to get user")
return
}
return res(user);
});
});
EDIT
This function is responsible for calling the internal function. It seems to work exactly like the checkout function, except for its magical ability to work correctly.
function getUser(request, response) {
var email = request.param('email');
user.getuser(email, function(user){
return response.send(200, JSON.stringify(user))
})
};
Using a REST client so I assure you that body/params is not the problem. Thanks for the help thus far.
you can check your paratmeter in your api like this :
var password = req.body.passwordBrow || '';
var uidUser = req.body.uidUser || '';
and then check it :
if(password && uidUser){
// here you can log your parameters
}else{
// the parameter is undefined, so you need to check your request in the client
res.json({
status : "not_ok",
result : "empty_data",
resultType : serverConst.EmptyParams
});
}
hope it helps you.
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));
});
}