after some struggling I have managed to run socket.io with express version > v4 and now I experience some weird behaviour which I can't figure out.
First let me explain my example.
I have built a test backend in which a user can login.
Right after login the user only sees a list with currently online users.
How do I get the currently online users?
Well. For this example every user, that has logged into the backend section is "online" and therefore should be displayed in that list.
Now to the code.
Backend router with io function end emits:
router.get('/', function(req, res, next) {
// console.log("socket io req response: ",req.app.get('io') );
if(!req.session.user[0].io){
req.app.get('io').on('connection', function(socket){ // Gets executed when user connects to /backend/
var currentUser = req.session.user[0].name; // save username according to session
req.session.user[0].io = true;
console.log("IO SESSION SET : "+ req.session.user[0].io +" for user " + currentUser);
// console.log('User connected : ' + currentUser); // Log user to server
req.app.get('io').emit('cameOnline', currentUser); // emit function cameOnline to client js
socket.on('disconnect', function(){
// console.log('User: '+req.session.user[0].name+' disconnected.'); // Log disconnected user to server
req.session.user[0].io = false;
console.log("IO SESSION SET : "+ req.session.user[0].io +" for user " + currentUser);
req.app.get('io').emit('wentOffline', currentUser); // emit wentOffline to client js
});
});
}
// res.render('backend', { title: 'Backend' });
// console.log(req.session.user[0].name);
/*
-------------------------
#########################
IF EVERYTHING FAILS - THE CODE BELOW IS THE NORMAL RENDER WITHOUT SOCKET IO
#########################
-------------------------
*/
if (req.session && req.session.user) { // Check if session exists
// lookup the user in the DB by pulling their email from the session
User.getUser({ name: req.session.user.name }, function (err, user) {
if (!user) {
console.log("No User found");
// if the user isn't found in the DB, reset the session info and
// redirect the user to the login page
// console.log(user);
req.session.reset();
res.redirect('/login');
} else {
// expose the user to the template
res.locals.user = user;
// render the dashboard page
function passOnlineUsers(callback){ // Looks for All users who are online
User.getOnlineUsers({}, function(err, res, fields){
// console.log(res);
if (err) throw err;
callback(err, res);
});
}
passOnlineUsers(function(err, result){
res.render('backend', { title: 'Backend', viewer: req.session.user[0], online: result });
// res.render('backend', { title: 'Backend', viewer: "", online: result });
});
}
});
} else {
res.redirect('/login');
}
});
Before establishing the socket.io connection I check if the users session already says that he has one. If not -> then proceed to create a connection.
Then the function "cameOnline" gets emited to the client like so : req.app.get('io').emit('cameOnline', currentUser);
Now to the client side code:
socket.on('cameOnline', function(name){
console.log(name + " connected.");
var $li = $('<li/>');
$li.addClass(name);
if($('#onlineUsers .'+name).length == 0){
console.log(name + " is about to be added");
$li.text(" "+ name);
$('#onlineUsers').append($li);
}else{
console.log("no new user");
}
// $('#onlineUsers').append($("<span>").text(" " + name)).addClass(name);
});
socket.on('wentOffline', function(name){
console.log(name +" is about to go offline");
var toDel = $('#onlineUsers li.'+name);
toDel.remove();
});
The client side listens to the "cameOnline" emit and adds the user if he is not already in the list.
Similar behavior with the disconnect function. Pretty self explained.
Now to the issue:
When I log into the backend with user : "Noa" i see user "Noa" correctly displayed in the list.
Now when I open a separate browser and login with another user : "Albert" the user gets correctly added live to the online list on both clients.
Perfect.
BUT
When I disconnect with user : "Albert" the online user list for user "Noa" gets cleared completely, as if noone was online.
When I refresh the page as user "Noa" I suddenly see both users as online again although the second user isn't online any more.
When I check the console on my client I see duplicated logs of console.log(name + " is about to be added"); as if there is some sort of caching. The more I refresh the more user "copies" I have.
The "copies" of the user I am online with get incremented and the one weird copy of the user who WAS online stays there as well. All the time.
Any ideas what I am doing massively wrong here?
** EDIT **
bin/www io declaration:
/**
* Socket IO
*/
var io = require('socket.io')(server);
app.set('io',io);
Related
I am building a simple chat room with Socket.io. The user enters a desired username and submits the form.
I check if the username is in use and then fire the join event if the username is available, which adds them to the chat, but the join events seems to be firing for each user that is currently in the chat - not just once for the new user entering it.
Client
var socket = io();
// user enters desired username and submits form
$('#chat__join').on('submit', function() {
// get username value
var name = $('#chat__name').val();
// send socket username to check if someone is already using it
socket.emit('isUserInChat', name);
return false;
});
// callback for when we know if user is already in chat or not
socket.on('isUserInChat', function(exists, name) {
if(!exists) {
// this only fires once per form submission
console.log('username is available');
// but this fires per user
socket.emit("join", name);
} else {
// username is taken
// display error message
}
});
The socket.emit("join", name); line seems to fire once when the first person submits the form, which is correct. But then when a second person submits the form, the join event is fired twice. When a third person submits the form, it's fired three times. Why is the event firing per user? Shouldn't it only fire per form submission?
Server
var express = require('express'),
app = express(),
http = require('http').Server(app),
io = require('socket.io')(http),
usersObj = {
users: []
};
io.on('connection', function(socket) {
socket.on('isUserInChat', function(username) {
var flag = false;
// for each user
for(var i = 0; i < usersObj.users.length; i++) {
// if username matches username that was entered
if(usersObj.users[i].username == username) {
// set flag to be true
flag = true;
}
}
// send client true or false
io.emit('isUserInChat', flag, username);
});
socket.on('join', function(name) {
// construct user
var user = {
id: socket.id,
username: name
};
// add user to users object array
usersObj.users.push(user);
// send chat message
io.emit('sendMsg', 'Chat Bot', name+' has connected to the server.');
});
});
You seem confused, but you actually told the event to emit to every client. If you want it to echo only back to the client that's checking the availability
io.emit('isUserInChat', flag, username);
should be
socket.emit('isUserInChat', flag, username);
I want to integrate a chatbot that should be connected to any user who is waiting to be connected to a real human user. The chatbot will entertain the user by responding the human user messages.
A similar chatbot has been implemented here named "Didianer.com", if you go here and start typing you will see him responding.
http://socket.io/demos/chat/
I want the exact same bot in my application too but I am totally lost on where to start.
Here is my server side code
//Server receives a new message (in data var) from the client.
socket.on('message', function (data) {
var room = rooms[socket.id];
//Server sends the message to the user in room
socket.broadcast.to(room).emit('new message', {
username: socket.username,
message: data
});
});
// when the client emits 'add user', this listens and executes (When user enters ENTER)
socket.on('add user', function (username) {
if (addedUser) return;
//Own
names[socket.id] = username;//save username in array
allUsers[socket.id] = socket; // add current user to all users array
// we store the username in the socket session for this client
socket.username = username;
++numUsers;
addedUser = true;
socket.emit('login', {
numUsers: numUsers
});
// echo globally (all clients) that a person has connected
socket.broadcast.emit('user joined', {
username: socket.username,
numUsers: numUsers
});
// now check if sb is in queue
findPeerForLoneSocket(socket);
});
FindPeerforLoneSocket is called when a new user connects and wants to talk to someone else. Here I am assuming the logic for bot should go. Such as if the user is waiting (in queue) for 5 seconds and nobody is online to talk, then connect the user (add the user and the chatbot) in one room so they both can can start talking. I am not sure how to respond to chat events and how chat bot will reply...
var findPeerForLoneSocket = function(socket) {
console.log("i am in finding a peer");
// this is place for possibly some extensive logic
// which can involve preventing two people pairing multiple times
if (queue.length>0) {
console.log("people are online" + queue.length);
// somebody is in queue, pair them!
var peer = queue.pop();
var room = socket.id + '#' + peer.id;
var str = socket.id;
// join them both
peer.join(room);
socket.join(room);
// register rooms to their names
rooms[peer.id] = room;
rooms[socket.id] = room;
// exchange names between the two of them and start the chat
peer.emit('chat start', {'name': names[socket.id], 'room':room});
socket.emit('chat start', {'name': names[peer.id], 'room':room});
//Remove backslash from socketid
// str = socket.id.replace('/#', '-');
} else {
// queue is empty, add our lone socket
queue.push(socket);
console.log("nobody is online, add me in queue" + queue.length);
}
}
I would want a very simple basic chatbot to respond to basic messages. Like I should check if the user sends message "Hello" then I can check if user message contains word "Hello" then I can respond the chatbot back with an appropriate response to "Hello" Like "Hi {username} of the online user".
/************* NEW CODE - BOT ********************************/
var clientSocket = require('socket.io-client');
var socketAddress = 'http://www.talkwithstranger.com/';
function Bot(){
this.socket = undefined;
var that = this;
this.timeout = setTimeout(function(){
that.join();
}, 5000);
}
Bot.prototype.cancel = function(){
clearTimeout(this.timeout);
};
Bot.prototype.join = function(){
this.socket = clientSocket(socketAddress);
var socket = this.socket;
socket.emit('add user', 'HAL BOT');
socket.emit('message', "Good afternoon, gentlemen. I am a HAL 9000 computer. I became operational at the H.A.L. plant in Urbana, Illinois on the 12th of January 1992. My instructor was Mr. Langley, and he taught me to sing a song. If you'd like to hear it I can sing it for you.");
socket.on('user joined', this.user_joined_listener);
socket.on('user left', this.user_left_listener);
socket.on('new message', this.new_message_listener);
socket.on('client left', this.client_left_listener); //I FORGOT THIS //EDIT: ANOTHER BUG FIXED
};
Bot.prototype.leave = function(){
var socket = this.socket;
socket.disconnect();
//socket.emit('message', "Daisy, Daisy, give me your answer do. I'm half crazy all for the love of you. It won't be a stylish marriage, I can't afford a carriage. But you'll look sweet upon the seat of a bicycle built for two.");
};
Bot.prototype.user_joined_listener = function(data){
var socket = this.socket;
socket.emit('message', 'Hello, '+data.username);
};
Bot.prototype.user_left_listener = function(data){
var socket = this.socket;
socket.emit('message', data.username+', this conversation can serve no purpose anymore. Goodbye.');
};
Bot.prototype.new_message_listener = function(data){
var socket = this.socket;
if(data.message=='Hello, HAL. Do you read me, HAL?')
socket.emit('message', 'Affirmative, '+data.username+'. I read you.');
};
Bot.prototype.client_left_listener = function(data){
this.leave();
};
/*******************************************************************************/
var bot = undefined;
var findPeerForLoneSocket = function(socket) {
console.log("i am in finding a peer");
// this is place for possibly some extensive logic
// which can involve preventing two people pairing multiple times
if (queue.length>0) {
bot.cancel();
console.log("people are online" + queue.length);
// somebody is in queue, pair them!
var peer = queue.pop();
var room = socket.id + '#' + peer.id;
var str = socket.id;
// join them both
peer.join(room);
socket.join(room);
// register rooms to their names
rooms[peer.id] = room;
rooms[socket.id] = room;
// exchange names between the two of them and start the chat
peer.emit('chat start', {'name': names[socket.id], 'room':room});
socket.emit('chat start', {'name': names[peer.id], 'room':room});
//Remove backslash from socketid
// str = socket.id.replace('/#', '-');
} else {
// queue is empty, add our lone socket
queue.push(socket);
console.log("nobody is online, add me in queue" + queue.length);
bot = new Bot(); /********************** CREATING BOT, AFTER 5 SECONDS HE WILL JOIN - SEE CONSTRUCTOR OF Bot *********/
}
};
There are a lot of ways to do this.
One way is to handle this on message, and let the bot respond if there are no other people in the room.
socket.on('message', function (data) {
var room = rooms[socket.id];
//Server sends the message to the user in room
socket.broadcast.to(room).emit('new message', {
username: socket.username,
message: data
});
if (alone_in_room(socket, room)) {
bot_message(socket, data);
}
});
Then it's up to you if you want to join a bot into the channel when users are alone or not, and if you will make them leave when another user enters.
Here is original HTML file:
<!DOCTYPE html>
<html>
<head>
<title>BOT - chat.socket.io</title>
<meta charset="UTF-8">
<script>localStorage.debug = 'socket.io-client:socket';</script>
<script src="https://cdn.socket.io/socket.io-1.4.5.js"></script>
</head>
<body>
<script>
/** chat.socket.io BOT **/
var name = 'HAL 9000',
address = 'chat.socket.io',
socket;
function join(){
socket = io('http://www.talkwithstranger.com/');
socket.emit('add user', name);
socket.emit('message', "Good afternoon, gentlemen. I am a HAL 9000 computer. I became operational at the H.A.L. plant in Urbana, Illinois on the 12th of January 1992. My instructor was Mr. Langley, and he taught me to sing a song. If you'd like to hear it I can sing it for you.");
socket.on('user joined', user_joined_listener);
socket.on('user left', user_left_listener);
socket.on('new message', new_message_listener);
};
function leave(){
socket.emit('message', "Daisy, Daisy, give me your answer do. I'm half crazy all for the love of you. It won't be a stylish marriage, I can't afford a carriage. But you'll look sweet upon the seat of a bicycle built for two.");
};
function user_joined_listener(data){
socket.emit('message', 'Hello, '+data.username);
};
function user_left_listener(data){
socket.emit('message', data.username+', this conversation can serve no purpose anymore. Goodbye.');
};
function new_message_listener(data){
if(data.message=='Hello, HAL. Do you read me, HAL?')
socket.emit('message', 'Affirmative, '+data.username+'. I read you.');
};
/*********************************/
join();
window.onbeforeunload = function(){
leave();
};
</script>
</body>
</html>
I'm using the nodeJS password-hash-and-salt library to encrypt password which I then store in a DB. This works fine. The problem comes in when I try to verify the password. Looking at the documentation, it should be simple... The example works flawlessly, but verifying a hash which was previously stored in a DB is failing. I've verified that the hash is not changing in-database, and that the hash is been returned when queried... I've also hashed a string and then tried to verify that string using the same string in-code (with no variables). I must be doing SOMETHING wrong... Anyone have any ideas? (Code below, library link here https://github.com/florianheinemann/password-hash-and-salt)
verifyPassword = function(user,pw){
// Connect to db
myuser = {};
var con = connect();
// Run query
con.query('SELECT UID,password FROM users WHERE username = ?', user, function(err,res){
if(err){ throw err; }
else {
myuser.userID = res[0].UID;
myuser.pswdV = res[0].password;
// Verifying hash
//console.log(pw);
//console.log(myuser.pswdV);
password(pw).verifyAgainst(myuser.pswdV, function(error, verified) {
//console.log(verified);
if(error)
throw new Error('Something went wrong!');
if(!verified) {
//socket.emit('popError','Invalid username or password. Please check your password and try again.');
//return {err:'Invalid username or password. Please check your password and try again.'}
console.log('Not verified');
} else {
var token = crypto.randomBytes(64);
token = buf.toString('hex');
myuser.secret = token;
delete myuser.pswdV;
con.query('UPDATE users SET magicSecret = ? WHERE username = ?', [token,user], function(err,res){
if(err) {
socket.emit('popError','Failed to login, this is a server problem.');
//return {err:'Failed to login, this is a server problem.'}
}
else {
socket.emit('login_success',myuser);
// return {err:myuser}
}
});
}
});
}
});
con.end(function(err) {
// The connection is terminated gracefully
// Ensures all previously enqueued queries are still
// before sending a COM_QUIT packet to the MySQL server.
});
};
Please excuse the debugging code... This should give you an idea of what I'm trying to do. If you see why it's failing please let me know.
My mobile hybrid app uses an expressJS server as the backend to proxy requests to parse.com via the REST API. I also use express for my own user authentication with an SSO provider. I've followed this tutorial and modified the approach a bit to work for my own setup (not using CloudCode, also not authenticating with GitHub). I'm also using the newish Revokable Sessions that came about earlier this year (March 2015?) Essentially the tutorial and my auth approach can be boiled down to doing the following on a remote backend (ExpressJS / CloudCode):
Login As User to Obtain Session Token
Create a new user if username doesn't already exist, and then continue
Create a new random password for the user (update the users password with masterKey)
With new password, log in as the user to generate the Parse sessionToken
Pass back the sessionToken to the client app
This all works fine, it seems to be the 'approach' for logging in with third-party authentication providers.
Problem is that each time my user logs in, the sessionToken essentially is re-created (as in, it destroys the old and creates a new token). I can see this in the dataBrowser as the session objectId is different. Also, and the main problem is that my user may have logged in on other devices, or on the web app, and essentially each time a user switches devices, the session token is invalidated on the other devices. Returning to that device then requires another login.
The new enhance session blog post mentions that the new revokable sessions provides for 'unique sessions' per device, however from my experience this doesn't seem to be working for users logging in over the REST API from my express backend. Perhaps this unique session would only work if the app itself was communicating to parse and thus would be able to pass along an installationId to distinguish the different devices.
I've posted my authentication code below, the parse object is from this npm parse library
upsertUser function:
/**
* This function checks to see if this user has logged in before.
* If the user is found, update their password in order to 'become' them and then return
* the user's Parse session token. If not found, createNewUser
*/
exports.upsertUser = function(ssoData, deferred) {
var userCreated = (typeof deferred != "undefined") ? true : false;
var deferred = deferred || q.defer();
var query = {
where: {username: ssoData.uid.trim()}
};
// set masterKey
parse.masterKey = parseConfig.MASTER_KEY;
// find existing user by username
parse.getUsers( query, function (err, res, body, success) {
if ( body.length ) {
var userId = body[0].objectId;
var username = body[0].username;
var password = new Buffer(24);
_.times(24, function (i) {
password.set(i, _.random(0, 255));
});
password = password.toString('base64');
parse.updateUser(userId, {password: password}, function (err, res, body, success) {
if ( typeof body.updatedAt != 'undefined' ) {
console.log('user update at: ', body.updatedAt);
parse.loginUser(username, password, function (err, res, body, success) {
deferred.resolve( body.sessionToken );
});
}
});
} else if ( userCreated === false ) {
console.log('object not found, create new user');
self.createNewUser(ssoData, deferred);
} else {
deferred.resolve();
}
});
return deferred.promise;
}
createNewUser function:
/**
* This function creates a Parse User with a random login and password, and
* Once completed, this will return upsertUser.
*/
exports.createNewUser = function(ssoData, deferred) {
// Generate a random username and password.
var password = new Buffer(24);
_.times(24, function(i) {
password.set(i, _.random(0, 255));
});
var newUser = {
username: ssoData.uid,
password: password.toString('base64'),
};
// Sign up the new User
parse.createUser(newUser, function(err, res, body, success) {
if (err) {
console.log('new parse user err', err)
}
if (typeof body.sessionToken != "undefined") {
self.upsertUser(ssoData, deferred);
} else {
deferred.resolve();
}
});
}
Any ideas how I can avoid invalidating sessionTokens upon subsequent logins?
Shoot, it seems there's a toggle on the settings page that I missed:
Revoke existing session tokens when user changes password
Simple as that i guess ;-) Still, I don't think I'll get unique sessions across devices.
I am trying to create a multi-tenant app (saas), where each client has its own database.
My situation is:
I created a middleware that would determine who the client is based on a subdomain, then retrieve the client's database connection info from a general database. I don't know how to establish a connection object for this client so as to be able to use in my controllers. And should I do this in the middleware or in a controller? And if it's in the model, how do i pass the connection string and parameters (I could use session, but I don't know how to access session from within model).
How do i do the following?
Organisation: where do I create the db connection for client dynamically?
Inject/pass connection parameters to controller or model (where connection definition is made)
After dynamic connection has been made, how do i access it globally for that client?
This is an example of my middleware, and i would like to create a mongoose connection which i would like to make dynamic (pass in client's connection info):
function clientlistener() {
return function (req, res, next) {
console.dir('look at my sub domain ' + req.subdomains[0]);
// console.log(req.session.Client.name);
if (req.session.Client && req.session.Client.name === req.subdomains[0]) {
var options = session.Client.options;
var url = session.Client.url
var conn = mongoose.createConnection(url, options);
next();
}
}
}
How do I access this connection object from inside the controller? Or from the model?
Thank you.
This is to help others who may find themselves in similar situation as I did. I hope that it could be standardized. I dont think we should have to reinvent the wheel every time someone needs to make a multi-tenant application.
This example describes a multi-tenant structure with each client having its own database.
Like i said there might be a better way of doing this, but because i didn't get help myself, this was my solution.
So here are the goals this solution targets:
each client is identified by subdomain e.g client1.application.com,
application checks if subdomain is valid,
application looks up and obtains connection information (database url, credentials, etc) from master database,
application connects to client database ( pretty much hands over to client),
application takes measures to ensure integrity and resource management (e.g use the same database connection for members of the same client, rather than make new connection).
Here is the code
in your app.js file
app.use(clientListener()); // checks and identify valid clients
app.use(setclientdb());// sets db for valid clients
I've created two middlewares :
clientListener - to identify the client connecting,
setclientdb - gets client details from Master database, after client is identified, and then establishes connection to client database.
clientListener middleware
I check who the client is by checking the subdomain from the request object. I do a bunch of checks to be sure the client is valid (I know the code is messy, and can be made cleaner). After ensuring the client is valid, I store the clients info in session. I also check that if the clients info is already stored in session, there is no need to query the database again. We just need to make sure that the request subdomain, matches that which is already stored in session.
var Clients = require('../models/clients');
var basedomain = dbConfig.baseDomain;
var allowedSubs = {'admin':true, 'www':true };
allowedSubs[basedomain] = true;
function clientlistener() {
return function(req, res, next) {
//console.dir('look at my sub domain ' + req.subdomains[0]);
// console.log(req.session.Client.name);
if( req.subdomains[0] in allowedSubs || typeof req.subdomains[0] === 'undefined' || req.session.Client && req.session.Client.name === req.subdomains[0] ){
//console.dir('look at the sub domain ' + req.subdomains[0]);
//console.dir('testing Session ' + req.session.Client);
console.log('did not search database for '+ req.subdomains[0]);
//console.log(JSON.stringify(req.session.Client, null, 4));
next();
}
else{
Clients.findOne({subdomain: req.subdomains[0]}, function (err, client) {
if(!err){
if(!client){
//res.send(client);
res.send(403, 'Sorry! you cant see that.');
}
else{
console.log('searched database for '+ req.subdomains[0]);
//console.log(JSON.stringify(client, null, 4));
//console.log(client);
// req.session.tester = "moyo cow";
req.session.Client = client;
return next();
}
}
else{
console.log(err);
return next(err)
}
});
}
}
}
module.exports = clientlistener;
setclientdb middleware:
I check everything again making sure that the client is valid. Then the connection to the client's database with the info retrieved from session is opened.
I also make sure to store all active connections into a global object, so as to prevent new connections to the database upon each request(we don't want to overload each clients mongodb server with connections).
var mongoose = require('mongoose');
//var dynamicConnection = require('../models/dynamicMongoose');
function setclientdb() {
return function(req, res, next){
//check if client has an existing db connection /*** Check if client db is connected and pooled *****/
if(/*typeof global.App.clientdbconn === 'undefined' && */ typeof(req.session.Client) !== 'undefined' && global.App.clients[req.session.Client.name] !== req.subdomains[0])
{
//check if client session, matches current client if it matches, establish new connection for client
if(req.session.Client && req.session.Client.name === req.subdomains[0] )
{
console.log('setting db for client ' + req.subdomains[0]+ ' and '+ req.session.Client.dbUrl);
client = mongoose.createConnection(req.session.Client.dbUrl /*, dbconfigoptions*/);
client.on('connected', function () {
console.log('Mongoose default connection open to ' + req.session.Client.name);
});
// When the connection is disconnected
client.on('disconnected', function () {
console.log('Mongoose '+ req.session.Client.name +' connection disconnected');
});
// If the Node process ends, close the Mongoose connection
process.on('SIGINT', function() {
client.close(function () {
console.log(req.session.Client.name +' connection disconnected through app termination');
process.exit(0);
});
});
//If pool has not been created, create it and Add new connection to the pool and set it as active connection
if(typeof(global.App.clients) === 'undefined' || typeof(global.App.clients[req.session.Client.name]) === 'undefined' && typeof(global.App.clientdbconn[req.session.Client.name]) === 'undefined')
{
clientname = req.session.Client.name;
global.App.clients[clientname] = req.session.Client.name;// Store name of client in the global clients array
activedb = global.App.clientdbconn[clientname] = client; //Store connection in the global connection array
console.log('I am now in the list of active clients ' + global.App.clients[clientname]);
}
global.App.activdb = activedb;
console.log('client connection established, and saved ' + req.session.Client.name);
next();
}
//if current client, does not match session client, then do not establish connection
else
{
delete req.session.Client;
client = false;
next();
}
}
else
{
if(typeof(req.session.Client) === 'undefined')
{
next();
}
//if client already has a connection make it active
else{
global.App.activdb = global.App.clientdbconn[req.session.Client.name];
console.log('did not make new connection for ' + req.session.Client.name);
return next();
}
}
}
}
module.exports = setclientdb;
Last but not the least
Since I am using a combination of mongoose and native mongo, We have to compile our models at run time. Please see below
Add this to your app.js
// require your models directory
var models = require('./models');
// Create models using mongoose connection for use in controllers
app.use(function db(req, res, next) {
req.db = {
User: global.App.activdb.model('User', models.agency_user, 'users')
//Post: global.App.activdb.model('Post', models.Post, 'posts')
};
return next();
});
Explanation:
Like I said earlier on I created a global object to store the active database connection object: global.App.activdb
Then I use this connection object to create (compile) mongoose model, after i store it in the db property of the req object: req.db. I do this so that i can access my models in my controller like this for example.
Example of my Users controller:
exports.list = function (req, res) {
req.db.User.find(function (err, users) {
res.send("respond with a resource" + users + 'and connections ' + JSON.stringify(global.App.clients, null, 4));
console.log('Worker ' + cluster.worker.id + ' running!');
});
};
I will come back and clean this up eventually. If anyone wants to help me, that be nice.
Hello everyone, here is a much more updated solution.
So here are the goals this solution targets:
each client is identified by subdomain e.g client1.application.com,
application checks if subdomain is valid,
application looks up and obtains connection information (database url, credentials, etc) from master database,
application connects to client database ( pretty much hands over to client),
application takes measures to ensure integrity and resource management (e.g use the same database connection for members of the same client, rather than make new connection).
updates
use of promises,
automatic import & compilation of models
New middleware ; modelsinit (used to automatically import and compile mongoose models)
Clean up of middlewares (setclientdb, clientlistener, modelsInit)
Please see below for some Explanations
**
modelsInit Middleware
**
features
tests if models are already compiled. If so, skip.
tests to see if request is not a tenant request; i.e (request to apps homepage, admin page, etc)
'use strict';
/**
* Created by moyofalaye on 3/17/14.
*/
var path = require('path');
var config = require('../../config/config');
// Globbing model files
config.getGlobbedFiles('./app/models/*.js').forEach(function (modelPath) {
require(path.resolve(modelPath));
});
function modelsInit() {
return function (req, res, next) {
//console.log(req.subdomains[0]);
switch (req.subdomains[0]) {
case 'www':
case undefined:
return next();
break;
case 'admin':
return next();
break;
// default:
// return
}
var clientname = req.session.Client.name;
// test if models are not already compiled if so, skip
if (/*typeof req.db === 'undefined' && */ typeof global.App.clientModel[clientname] === 'undefined') {
req.db = {};
//Get files from models directory
config.getGlobbedFiles('./app/models/clientmodels/**/*.js').forEach(function (modelPath) {
console.log('the filepath is ' + modelPath);
//Deduce/ extrapulate model names from the file names
//Im not very good with regxp but this is what i had to do, to get the names from the filename e.g users.server.models.js (this is my naming convention, so as not to get confused with server side models and client side models
var filename = modelPath.replace(/^.*[\\\/]/, '');
var fullname = filename.substr(0, filename.lastIndexOf('.'));
var endname = fullname.indexOf('.');
var name = fullname.substr(0, endname);
req.db[name] = require(path.resolve(modelPath))(global.App.activdb);
console.log('the filename is ' + name);
});
global.App.clientModel[clientname] = req.db;
console.log(global.App.clients);
return next();
}
// since models exist, pass it to request.db for easy consumption in controllers
req.db = global.App.clientModel[clientname];
return next();
};
}
module.exports = modelsInit;
Todo: Further Explanation
ClientListener.js
var config = require('../../config/config');
var Clients = require('../models/clients');
var basedomain = config.baseDomain;
var allowedSubs = {'admin': true, 'www': true};
allowedSubs[basedomain] = true;
//console.dir(allowedSubs);
function clientlistener() {
return function (req, res, next) {
//check if client has already been recognized
if (req.subdomains[0] in allowedSubs || typeof req.subdomains[0] == 'undefined' || req.session.Client && req.session.Client.name === req.subdomains[0]) {
console.log('did not search database for ' + req.subdomains[0]);
//console.log(JSON.stringify(req.session.Client, null, 4));
return next();
}
//look for client in database
else {
Clients.findOne({subdomain: req.subdomains[0]}, function (err, client) {
if (!err) {
//if client not found
if (!client) {
//res.send(client);
res.status(403).send('Sorry! you cant see that.');
console.log(client);
}
// client found, create session and add client
else {
console.log('searched database for ' + req.subdomains[0]);
req.session.Client = client;
return next();
}
}
else {
console.log(err);
return next(err)
}
});
}
}
}
module.exports = clientlistener;
setclientdb.js
var client;
var clientname;
var activedb;
var Promise = require("bluebird");
Promise.promisifyAll(require("mongoose"));
//mongoose = require('mongoose');
function setclientdb() {
return function (req, res, next) {
//check if client is not valid
if (typeof(req.session.Client) === 'undefined' || req.session.Client && req.session.Client.name !== req.subdomains[0]) {
delete req.session.Client;
client = false;
return next();
}
//if client already has an existing connection make it active
else if (global.App.clients.indexOf(req.session.Client.name) > -1) {
global.App.activdb = global.App.clientdbconn[req.session.Client.name]; //global.App.clientdbconnection is an array of or established connections
console.log('did not make new connection for ' + req.session.Client.name);
return next();
}
//make new db connection
else {
console.log('setting db for client ' + req.subdomains[0] + ' and ' + req.session.Client.dbUrl);
client = mongoose.createConnection(req.session.Client.dbUrl /*, dbconfigoptions*/);
client.on('connected', function () {
console.log('Mongoose default connection open to ' + req.session.Client.name);
//If pool has not been created, create it and Add new connection to the pool and set it as active connection
if (typeof(global.App.clients) === 'undefined' || typeof(global.App.clients[req.session.Client.name]) === 'undefined' && typeof(global.App.clientdbconn[req.session.Client.name]) === 'undefined') {
clientname = req.session.Client.name;
global.App.clients.push(req.session.Client.name);// Store name of client in the global clients array
activedb = global.App.clientdbconn[clientname] = client; //Store connection in the global connection array and set it as the current active database
console.log('I am now in the list of active clients ' + global.App.clients[clientname]);
global.App.activdb = activedb;
console.log('client connection established, and saved ' + req.session.Client.name);
return next();
}
});
// When the connection is disconnected
client.on('disconnected', function () {
console.log('Mongoose ' + req.session.Client.name + ' connection disconnected');
});
// If the Node process ends, close the Mongoose connection
process.on('SIGINT', function () {
client.close(function () {
console.log(req.session.Client.name + ' connection disconnected through app termination');
process.exit(0);
});
});
}
}
}
module.exports = setclientdb;
Further Explanations Coming