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.
Related
I got a Microsoft bot framework chatbot deployed on Azure and I´m using Tedious to save my conversations, thing is, bot it's being used on a web and many persons can open it to interact simultaneosly, but when I save a conversation from an user, it saves all the other interactions that have been made by other users at the same time, I need that each user has it's own conversation saved separately even if they are interacting with the chatbot at the same time...
Here's my code, maybe I'm missing something:
Bot.js
//SQL Connection
var Connection = require('tedious').Connection;
var config = {
server: 'myserver',
authentication: {
type: 'default',
options: {
userName: 'myuser',
password: 'mypass'
}
},
options: {
encrypt: true,
database: 'mydatabase'
}
};
const connection = new Connection(config);
connection.on('connect', function(err) {
console.log("Connected");
});
var Request = require('tedious').Request
var TYPES = require('tedious').TYPES;
// Function to save the conversation and bot ids
function executeConversationStatement(bot, cid, ulg ) {
request = new Request("INSERT INTO table (bot_id, conversationID, conversation_date, userlogged) VALUES (#bot, #cid, CURRENT_TIMESTAMP, #ulg); SELECT ##IDENTITY AS ID",function(err) {
if (err) {
console.log(err);}
});
request.addParameter('bot', TYPES.Int, bot);
request.addParameter('cid', TYPES.NVarChar, cid);
request.addParameter('ulg', TYPES.NVarChar, ulg);
request.on('row', function(columns) {
insertedcid = columns[0].value; // This is the id I pass later
columns.forEach(function(column) {
if (column.value === null) {
console.log('NULL');
} else {
console.log("Conversation id of inserted item is " + column.value);
}
});
});
connection.execSql(request);
}
// Here on members added I save the conversation id generated by the framework
class BOT extends ActivityHandler {
constructor(conversationState,userState,telemetryClient) {
super();
this.conversationState = conversationState;
this.userState = userState;
this.dialogState = conversationState.createProperty("dialogState");
this.previousIntent = this.conversationState.createProperty("previousIntent");
this.conversationData = this.conversationState.createProperty('conservationData');
const qnaMaker = new QnAMaker({
knowledgeBaseId: process.env.QnAKnowledgebaseId,
endpointKey: process.env.QnAEndpointKey,
host: process.env.QnAEndpointHostName
});
this.qnaMaker = qnaMaker;
this.onMessage(async (context, next) => {
await this.dispatchToIntentAsync(context);
await next();
});
this.onDialog(async (context, next) => {
await this.conversationState.saveChanges(context, false);
await this.userState.saveChanges(context, false);
await next();
});
this.onMembersAdded(async (context, next) => {
const { channelId, membersAdded } = context.activity;
actid = context._activity.id;
if (channelId === 'directline' || channelId === 'webchat') {
for (let member of membersAdded) {
if (member.id === context.activity.recipient.id) {
await context.sendActivity("Hi, I´m a chatbot to guide You");
try{
var saveqna = new executeConversationStatement(context._activity.id , 'Invitado');
}
catch{
console.log('Not saved');
}
}
}
}
await next();
});
}
//Finally, here I save the interaction:
async dispatchToIntentAsync(context) {
var result = await this.qnaMaker.getAnswers(context);
// Statement to save interaction with the insertedcid obtained above
var saveqnaint = new executeInteractionStatement(insertedcid, context._activity.text, result);
}
No matter if I use thet generated Id or the databse pk, I always keep the same identifier when multiple users are chatting, how can I got a separately Id for each session ?
this is my set notification object
var sentNotificationObj = function (notification, eventid, addedorganizerpropic) {
this.notification = notification;
this.eventid = eventid;
this.addedorganizerpropic = addedorganizerpropic;
}
this is my array which is stored notification obect
var notificationSetArray2 = [];
this is my api of getnotification
router.post('/getnotification', function (req, res) {
console.log('in aside');
var id = req.body.userid;
console.log('pos id' + id);
User.findById({ _id: id }, function (err, result) {
if (err) {
console.log('err get notification');
res.statusCode = 500;
res.json({
success: false,
message: "severe error"
});
} else {
this is code fetchin data in data base
for (var i = 0; i < result.notification.length; i++) {
var addedevent = result.notification[i].addedevent;
var notification = result.notification[i].notification;
console.log('added event' + addedevent);
console.log('added noti' + notification);
User.findById({ _id: result.notification[i].addedorganizer }, function (err, addedorganizer) {
if (err) {
console.log('error get added user pofile pic');
} else {
convert image to base64
var base64str = base64_encode(addedorganizer.profileData.profileurl);
console.log(base64str);
console.log(notification);
console.log(addedevent);
var newsentNotificationObj = new sentNotificationObj(notification, addedevent, base64str);
notificationSetArray2.push(newsentNotificationObj);
console.log('succe get added user profile pic');
}
});
}
this is response
res.statusCode = 200;
res.json({
success: true,
notificationArray: notificationSetArray2
})
console.log(notificationSetArray2);
notificationSetArray2.length = 0;
}
});
});
The most simple solution in here to use async library here.
Node runs code in asynchronous way. So it is natural to send response before fetching any data from your database.
By using async you can execute this in a sequence. So it will be solved.
Use async.series method for solve this. For example
async.series(
[
function(callback){
// fetch your data here using mongo with your loop
//
//
callback(); // this will trigger next step (call this after you finished iterating array)
},
function(callback){
// after above code has been executed
// send response here
callback() // call this to proceed
}
],
function(err){
// handle any error in here
}
)
A good example of how to use asyncjs in node
I am trying to setup a stripe checkout page on a KeystoneJS webpage.
The view.on('post'... function doesn't seem to work and I am not able to find the source of the issue:
Here is my code:
public/js/stripe.js
// Handle form submission
var form = document.getElementById('payment-form');
form.addEventListener('submit', function(event) {
event.preventDefault();
stripe.createToken(card).then(function(result) {
if (result.error) {
// Inform the user if there was an error
var errorElement = document.getElementById('card-errors');
errorElement.textContent = result.error.message;
} else {
// Send the token to your server
onReceiveToken(result.token);
}
});
});
var onReceiveToken = function(token, args) {
// Submit token to server so it can charge the card
$.ajax({
url: '/checkout',
type: 'POST',
data: {
action: 'charge',
stripeToken: token.id
},
success: function(data) {
console.log('Returns the HTML content of checkout.html');
}
})
};
routes/views/checkout.js
var keystone = require('keystone');
var stripe = require('stripe')("STRIPE_KEY");
exports = module.exports = function (req, res) {
var view = new keystone.View(req, res);
var locals = res.locals;
// locals.section is used to set the currently selected
// item in the header navigation.
locals.section = 'checkout';
// Render the view
view.render('checkout');
console.log(JSON.stringify(req.body)); <-- Is rendered correctly
//Create Subscription
view.on('post', { "action":"charge" }, function (next) {
console.log(JSON.stringify(req.body)); <-- Isn't rendered
stripe.customers.create({
description: 'Customer for elizabeth.williams#example.com',
source: res.token,
}, function(err, customer) {
// asynchronously called
});
next()
});
};
I would be grateful for any help :-)
Found the answer just after writing the question:
You must render the view last.
var keystone = require('keystone');
var stripe = require('stripe')("STRIPE_KEY");
exports = module.exports = function (req, res) {
var view = new keystone.View(req, res);
var locals = res.locals;
// locals.section is used to set the currently selected
// item in the header navigation.
locals.section = 'checkout';
//Create Subscription
view.on('post', { "action":"charge" }, function (next) {
console.log(JSON.stringify(req.body)); <-- Isn't rendered
stripe.customers.create({
description: 'Customer for elizabeth.williams#example.com',
source: res.token,
}, function(err, customer) {
// asynchronously called
});
next()
});
// Render the view
view.render('checkout');
};
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 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!