beginDialogAction doesn't reprompt - node.js

I am currently using the Microsoft Bot Framework with node.js to write a bot that traverses a decision tree through an API. I want to allow the user to cancel the tree at any point in the dialog, so that he doesn't have to go through the entire (possibly huge) tree to exit. Because i need to send a message to the API if the session is closed, i am using Dialog.beginDialogAction() to start a "cancel"-Dialog, instead of Dialog.cancelAction(), so that i can prompt the user to confirm as well as close the session.
Now that i have that in place, canceling the tree works just fine, but if the user chooses to say "no" to the confirmation prompt and the bot should actually re-prompt the last question, it sometimes uses the "no" to answer the previous question automatically (or throws an error). This only appears if the valueType of the question is "string" and a Prompts.choice dialog is shown, for Prompts.number and Prompts.time the expected behaviour is produced.
I have searched any Documentation i could find, but no information about some Prompts not supporting DialogActions or anything like that. I am only using session.beginDialog, session.endDialog, session.endConversation and builder.Prompts to control the dialog stack.
Code looks like this:
//This dialog gets the next question node of the tree passed in args[1].
bot.dialog('select', [
function(session, args, next) {
//Depending on the value type of the question, different prompts are used.
switch (args[1].valueType) {
case "string":
builder.Prompts.choice(session, args[1].text, args[1].answerValues, {
listStyle: builder.ListStyle.button,
maxRetries: 0
});
break;
case "number":
builder.Prompts.number(session, args[1].text, {
integerOnly: true,
maxValue: 100,
minValue: 0
})
break;
case "datetime":
builder.Prompts.time(session, message + "\nPlease enter a date in the format 'yyyy-mm-dd'.");
break;
}
},
function(session, results) {
//Use the answer to proceed the tree.
//...
//With the new question node start over.
session.beginDialog('select', [sessionId, questionNode]);
}
]).beginDialogAction('cancelSelect', 'cancel', {
matches: /^stop$/i
});
//This dialog is used for cancelling.
bot.dialog('cancel', [
function(session, next) {
builder.Prompts.confirm(session, 'Do you really want to quit?');
},
function(session, results) {
if (results.response) {
finishSession();
session.endConversation('Session was closed.')
} else {
//Here the bot should stop this cancel-Dialog and reprompt the previous question
session.endDialog();
}
}
])
But instead of re-prompting, the bot jumps to function (session, results) in the 'select'-Dialog, where it tries to parse the answer "no" and obviously fails.
Here the full copy of my app.js. You wont be able to run it without mocking the esc-API of our product, but it shows that i am only using session.beginDialog, session.endDialog, session.endConversation and builder.Prompts. The only changes i did were to remove private information and translate messages to english.
/*---------
-- Setup --
---------*/
//esc and esc_auth are product specific, so obviously i cant share them. They handle the API of our product.
const esc = require("./esc");
const esc_auth = require("./esc_auth");
var restify = require("restify");
var builder = require("botbuilder");
var server = restify.createServer();
server.listen(process.env.PORT || process.env.port || 3978, function() {
console.log(`${server.name} listening to ${server.url}`);
});
var connector = new builder.ChatConnector({
//Cant share these as well
appId: "",
appPassword: ""
});
server.post("/api/messages", connector.listen());
var esc_header;
var esc_session;
var esc_attribs = {};
var treeNames = [];
/*---------
-- Start --
---------*/
//This function is called when no other dialog is currently running.
//It gets the authorization token from the API, reads concepts from the input and searches for matching decision trees.
//If not concepts or trees were found, a text search on the API is cone.
var bot = new builder.UniversalBot(connector, [
function(session) {
var esc_token;
esc_attribs = {};
console.log("Getting token...");
esc.escAccessToken(esc_auth.esc_system, esc_auth.esc_apiUser)
.then(function(result) {
esc_token = result;
esc_header = {
"Content-Type": "application/json",
"Authorization": "Bearer " + esc_token
};
console.log("Got token.");
//Look for concepts in the message.
esc.escAnnotateQuery(esc_header, session.message.text)
.then(function(result) {
for(i in result.concepts) {
for(j in result.concepts[i]) {
esc_attribs[i] = j;
}
}
//If concepts were found, look for trees and solutions with them
if(Object.keys(esc_attribs).length > 0) {
esc.escSearchIndexWithConcepts(esc_header, esc_attribs)
.then(function(result) {
var treeIds = [];
treeNames = [];
result = result;
result.records.forEach(function(entry) {
//Check which tree the found tree is or found solution is in.
if(entry.DecisionTree && !treeIds.includes(entry.DecisionTree)) {
treeIds.push(entry.DecisionTree);
}
})
if(treeIds.length != 0) {
esc.escSearchTrees(esc_header)
.then(function(result) {
console.log("Trees found.");
result.records.forEach(function(tree) {
if(treeIds.includes(tree.DecisionTree)) {
treeNames.push({id:tree.DecisionTree, name: tree.Label})
console.log("Tree: ", tree.DecisionTree, tree.Label);
}
})
session.beginDialog("tree", treeNames);
})
} else {
console.log("No tree found for " + session.message.text);
treeNames = [];
session.beginDialog("textSearch");
return;
}
})
} else {
console.log("No concepts found.");
session.beginDialog("textSearch");
return;
}
})
})
},
function(session, results) {
session.endConversation("You may now start a new search.");
}
]);
//Searches for trees by text.
bot.dialog("textSearch", [
function(session) {
session.send("No concepts were found in your input.");
builder.Prompts.confirm(session, "Start a text search instead?", {"listStyle": builder.ListStyle.button});
},
function(session, results) {
if(results.response) {
builder.Prompts.text(session, "Please enter your new search prompt in keywords.")
} else {
session.endDialog("Ok, back to concept search.")
}
},
function(session) {
//Search gives better results without mutated vowels
esc.escSearchIndex(esc_header, undoMutation(session.message.text))
.then(function(result) {
var treeIds = [];
treeNames = [];
result.records.forEach(function(entry) {
//Check which tree the found document is in.
if(entry.DecisionTree && !treeIds.includes(entry.DecisionTree)) {
treeIds.push(entry.DecisionTree);
}
})
if(treeIds.length != 0) {
esc.escSearchTrees(esc_header)
.then(function(result) {
console.log("Trees found.");
result.records.forEach(function(tree) {
if(treeIds.includes(tree.DecisionTree)) {
treeNames.push({id:tree.DecisionTree, name: tree.Label})
console.log("Tree: ", tree.DecisionTree, tree.Label);
}
})
session.beginDialog("tree", treeNames);
})
} else {
console.log("No tree found for " + session.message.text);
treeNames = [];
session.endConversation("No trees were found for this search.");
}
})
}
])
//The cancel dialog.
bot.dialog("cancel", [
function(session) {
builder.Prompts.confirm(session, "Do you really want to cancel?", {"listStyle": builder.ListStyle.button});
},
function(session, results) {
if(results.response) {
if(esc_session) {
esc.escFinishSession(esc_header, esc_session.sessionId)
.then(function(result) {
esc_session = undefined;
session.endConversation("Session was cancelled.")
})
} else {
session.endConversation("Session was cancelled.")
}
} else {
session.endDialog();
}
}
])
/*-------------------------
-- Decision tree dialogs --
-------------------------*/
//This dialog presents the found decision trees and lets the user select one.
bot.dialog("tree", [
function(session, treeArray) {
var opts = [];
treeArray.forEach(function(t) {
opts.push(t.name);
});
builder.Prompts.choice(session, "Following trees were found:", opts, {listStyle: builder.ListStyle.button})
},
function(session, results) {
let answer = results.response.entity;
console.log("Tree selected:", answer);
let id;
treeNames.forEach(function(t) {
if(t.name === answer && !id) {
id = t.id;
}
})
console.log("Starting session...");
esc.escStartSession(esc_header, id, esc_attribs)
.then(function(result) {
esc_session = result;
for(i in esc_session.concepts) {
for(j in esc_session.concepts[i]) {
esc_attribs[i] = j;
}
}
console.log("Started session.");
session.beginDialog(esc_session.questions[0].nodeType,[esc_session.sessionId, esc_session.questions[0]]);
})
.catch(function(err) {
console.log("Error starting ESC session.");
console.log(err);
})
}
]).beginDialogAction("cancelTree", "cancel", {matches: /^cancel$|^end$|^stop$|^halt/i});
//This dialog is for selection answers on a question node. It also saves recognized concepts within the answer.
bot.dialog("select", [
function(session, args) {
console.log("Select");
message = args[1].text;
attach(args[1].memo["Memo_URL"]);
session.userData = args;
var opts = new Array();
switch(args[1].valueType) {
case "string":
for(var i = 0; i < args[1].answerValues.length; i++) {
opts[i] = args[1].answerValues[i].value;
}
builder.Prompts.choice(session, message, opts, {listStyle: builder.ListStyle.button, maxRetries: 0});
break;
case "number":
for(var i = 0; i < args[1].answerIntervals.length; i++) {
opts[i] = args[1].answerIntervals[i].value;
}
builder.Prompts.number(session, message, {integerOnly: true, maxValue: 100, minValue: 0});
break;
case "datetime":
for(var i = 0; i < args[1].answerIntervals.length; i++) {
opts[i] = args[1].answerIntervals[i].value;
}
builder.Prompts.time(session, message + "\nPlease enter a date in format 'yyyy-mm-dd'.");
break;
}
},
function(session, results) {
let args = session.userData;
let answer;
//An answer was given.
if(results.response != null && results.response.entity != null) {
answer = results.response.entity;
} else if (results.response != null) {
answer = results.response;
} else {
//No answer (to a choice prompt) was given, check if concepts were recognized and try again.
}
esc.escAnnotateQuery(esc_header, session.message.text)
.then(function(result) {
for(i in result.concepts) {
for(j in result.concepts[i]) {
esc_attribs[i] = j;
}
}
console.log("Proceeding tree with answer %s", answer);
esc.escProceedTree(esc_header, args[0], args[1].nodeId, args[1].treeId, answer, esc_attribs)
.then(function(result) {
if(result.questions[0].nodeType === "error") {
//If no concept answers the question, ask again.
session.send("No answer was given.")
session.beginDialog("select", [esc_session.sessionId, esc_session.questions[0]])
} else {
esc_session = result;
console.log("Initiating new Dialog %s", esc_session.questions[0].nodeType);
//the nodeType is either "select", "info" or "solution"
session.beginDialog(esc_session.questions[0].nodeType, [esc_session.sessionId, esc_session.questions[0]])
}
})
.catch(function(err) {
console.log("Error proceeding tree.");
console.log(err);
});
})
}
]).beginDialogAction("cancelSelect", "cancel", {matches: /^abbrechen$|^beenden$|^stop$|^halt/i});
//This dialog is for showing hint nodes. It then automatically proceeds the tree.
bot.dialog("info", [
function(session, args) {
console.log("Info");
message = args[1].text;
attach(args[1].memo["Memo_URL"]);
session.send(message);
console.log("Proceeding tree without answer.");
esc.escProceedTree(esc_header, args[0], args[1].nodeId, args[1].treeId, "OK", esc_attribs)
.then(function(result) {
esc_session = result;
console.log("Initiating new Dialog %s", esc_session.questions[0].nodeType);
session.beginDialog(esc_session.questions[0].nodeType, [esc_session.sessionId, esc_session.questions[0]]);
})
.catch(function(err) {
console.log("Error proceeding tree.");
console.log(err);
});
}
])
//This dialog displays the reached solution. It then ends the dialog, erasing the concepts of this session.
bot.dialog("solution", [
function(session, args) {
console.log("Solution");
message = args[1].text;
attach(args[1].memo["Memo_URL"]);
session.send(message);
esc.escFinishSession(esc_header, args[0])
.then(function(result) {
console.log("Finished Session " + args[0]);
esc_session = undefined;
})
.catch(function(err) {
console.log("Error finishing session.");
console.log(err);
})
console.log("Ending dialog.");
session.endDialog("I hope i could help you.");
}
])
/*-----------
-- Manners --
-----------*/
// Greetings
bot.on("conversationUpdate", function (message) {
if (message.membersAdded && message.membersAdded.length > 0) {
// Don"t greet yourself
if(message.membersAdded[0].id !== message.address.bot.id) {
// Say hello
var reply = new builder.Message()
.address(message.address)
.text("Welcome to the Chatbot. Please enter your search.");
bot.send(reply);
}
} else if (message.membersRemoved) {
// Say goodbye
var reply = new builder.Message()
.address(message.address)
.text("Goodbye.");
bot.send(reply);
}
});
/*---------------------
-- Utility functions --
---------------------*/
//function for attached images
var attach = function(p) {
if(typeof p != undefined && p != null) {
console.log("Found attachment: %s", p);
session.send({
attachments:[{
contentUrl: p,
contentType: "image/jpeg"
}]
})
}
}
var undoMutation = function(s) {
while(s.indexOf("ä") !== -1) {
s = s.replace("ä", "ae");
}
while(s.indexOf("ö") !== -1) {
s = s.replace("ö", "oe");
}
while(s.indexOf("ü") !== -1) {
s = s.replace("ü", "ue");
}
return s;
}

It could be the question of nested string Prompt dialogs. Currently I only find some workarounds to quick fix the issue.
Fastest: Enlarge the retires of prompt dialog, which will reprompt the question if Bot gets the invaild input.
builder.Prompts.choice(session, args[1].text, args[1].answerValues, {
listStyle: builder.ListStyle.button,
maxRetries: 2
});
As I see you will pass the treeNote to the select dialog, so you can try to use replaceDialog() instead of beginDialog() to clear nested dialog stack. In your cancel dialog, also replace endDialog() to replaceDialog()

Related

Send web push notifications to specific users conditionally

I am willing to use web-push notifications on my web app. I have already setup serviceWorkers on the front-end(React) and implemented web-push notifications on my backend(NodeJS). Now I just need to send notifications which are user specific, means only specific users should receive those notifications.
For e.g. In my web app's backend I will be receiving some live values. Say, there is a collection named
"users" where all the user's data will be stored. Now these users will have a field named "device" where the user will receive numeric values which will be updated within 40-50 seconds.
Now, their will be a threshold for these values. Say, for e.g. if the value reaches above 200 then that specific user should receive a push notification, letting them know that the device has reached it's limit.
How is it possible for me to create such user specific push notifications where the notification will be sent to only that user who's device value has reached above 200 ?. P.S I am using Mongoose for the database.
FrontEnd code(react.js)
sw.js:
self.addEventListener("notificationclick", function (event) {
// on Notification click
let notification = event.notification;
let action = event.action;
console.log("Notification====>", notification);
if (action === "confirm") {
console.log("Confirm clicked");
notification.close(); // Closes the notifcation
} else {
event.waitUntil(
clients.matchAll().then(function (clis) {
var client = clis.find(function (c) {
return c.visibilityState === "visible";
});
if (client !== undefined) {
// found open window
client.navigate("http://localhost:3000"); // means website opens on the same tab where user is on
client.focus();
} else {
// if client's window was not open
clients.openWindow("http://localhost:3000"); // when browser window is closed, open website
}
notification.close();
})
);
console.log(action); // name of action, basically id
}
});
self.addEventListener("notificationclose", function (event) {
console.log("Notification closed", event);
});
// triggers when we get an incoming push message
self.addEventListener("push", function (event) {
console.log("Push notifications recieved from eventListner", event);
var data = { title: "New!", content: "New things" };
if (event.data) {
// check if payload exists(from backend)
data = JSON.parse(event.data.text()); // recieve payload & store
}
var options = {
body: data.content,
icon: "https://iconarchive.com/download/i90141/icons8/windows-8/Cinema-Avengers.ico",
tag: "id1",
renotify: true,
};
event.waitUntil(self.registration.showNotification(data.title, options));
});
swReg.js:
if ("serviceWorker" in navigator) {
console.log("Registering service worker");
navigator.serviceWorker
.register("/sw.js")
.then(() => {
console.log("Service Worker has been registered");
})
.catch((err) => console.error(err));
}
function urlBase64ToUint8Array(base64String) {
const padding = "=".repeat((4 - (base64String.length % 4)) % 4);
const base64 = (base64String + padding).replace(/-/g, "+").replace(/_/g, "/");
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
function displayConfirmNotification() {
if ("serviceWorker" in navigator) {
const options = {
body: "After subscription managing done",
// icon: "/src/assets/img/pattern_react.png",
// tag:"" ==> in advanced options.
vibrate: [100, 50, 200],
// badge:""
tag: "confirm",
renotify: true,
actions: [
{ action: "confirm", title: "okay" }, // optnl icon:""
{ action: "cancel", title: "cancel" },
],
};
navigator.serviceWorker.ready.then(function (swreg) {
swreg.showNotification("Successfully subscribed sW", options);
});
}
}
function configPushSub() {
if (!("serviceWorker" in navigator)) {
return;
}
var reg;
navigator.serviceWorker.ready
.then(function (swreg) {
// access to sW registration
reg = swreg;
return swreg.pushManager.getSubscription(); // returns any existing subscription
})
.then(function (sub) {
// sub holds the current subscription, if subscription doesn't exist then it returns null
if (sub === null) {
// Create a new subscription
var vapidPublicKey = KEY;
var convertedPublicKey = urlBase64ToUint8Array(vapidPublicKey);
return reg.pushManager.subscribe({
userVisibleOnly: true, // for security
applicationServerKey: convertedPublicKey, // for security & server storage
}); // create new subscription
} else {
// We already have a subscription
}
})
.then(function (newSub) {
// have to pass this subscription(new one) to backend
console.log("New subb =======>", newSub);
return fetch("http://localhost:8000/subscribeToPushNotifications", {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
subscriptionObj: newSub,
}),
});
})
.then(function (res) {
if (res.ok) {
displayConfirmNotification();
}
})
.catch(function (e) {
console.log("err while subbing====>", e);
});
}
function askForNotificationPermission() {
Notification.requestPermission(function (result) {
console.log("User's choice", result);
if (result !== "granted") {
console.log("Permission rights not granted");
} else {
configPushSub();
// displayConfirmNotification();
}
});
}
if ("Notification" in window) {
askForNotificationPermission();
}
Backend:
API to subscribe:
exports.subscribeToPushNotifications = async (req, res) => {
const { subscriptionObj } = req.body;
// console.log("Subscription object=====>", subscriptionObj);
if (subscriptionObj != undefined || subscriptionObj != null) {
let newSubscription = new Subscription({
pushSubscription: subscriptionObj,
});
await newSubscription.save();
if (newSubscription) {
console.log(newSubscription);
return res.status(200).send("Subscription made");
} else {
console.log("Not subbed");
return res.status(400).send("Subscription not made");
}
} else {
console.log("Sub obj is null");
return res.status(400).send("Sub obj was null");
}
};
Checking if values are more than the threshold and then sending notification:(For example purposes). This is an example for single user only.
exports.checkStatus = async () => {
schedule.scheduleJob("*/10 * * * * *", async () => {
let subscriptions = await Subscription.find({});
let Email = "james#mail.com";
let findUser = await User.findOne({ Email });
if (findUser) {
if (findUser.device > 200) // findUser.device contains the value
{
for (let i = 0; i < subscriptions.length; i++) { //Notification will be sent to all users which I don't want.
webpush.sendNotification(
subscriptions[i].pushSubscription,
JSON.stringify({
title: "Alert",
content: "Value has reached it's limit",
})
);
}
}
}
});
};
How can I make this work such that only those users who's device's value has gone above 200 will only receive the notification and not all the subscribed users.

use async - await with socket.io nodejs

I'm developing a web application using nodejs socket.io and angular 9. In my backend code I have written sockets in socket.connect.service.
Follows is a socket I'm using
socket.on('request-to-sit-on-the-table', async function (data, callback) { //Previously Register
let table = persistence.getTable(tableToken);
if (typeof table === 'undefined') {
let errMsg = 'This table does not exists or already closed.'
callback(prepareResponse({}, errMsg, new Error(errMsg)));
return;
}
//TODO: Get the displayName from the token.
let guest = await guestUserService.getGuestUserByPlayerToken(JSON.parse(data.userToken));***//Here is the issue***
// let displayName = 'DisplayName-' + guest;
let displayName = 'DisplayName-' + Math.random();
//TODO: Check whether the seat is available
// If the new screen name is not an empty string
let isPlayerInCurrentTable = persistence.isPlayerInCurrentTable(tableToken, userToken);
if (displayName && !isPlayerInCurrentTable) {
var nameExists = false;
let currentPlayersTokenArr = persistence.getTableObjectPlayersToken(table)
for (var token in currentPlayersTokenArr) {
let gamePlayer = persistence.getPlayerPlayer(currentPlayersTokenArr[token])
if (typeof gamePlayer !== "undefined" && gamePlayer.public.name === displayName) {
nameExists = true;
break;
}
}
if (!nameExists) {
//Emit event to inform the admin for requesting to sit on the table.
let ownerToken = persistence.getTableObjectOwnerToken(table);
let ownerSocket = persistence.getPlayerSocket(ownerToken);
ownerSocket.emit('requested-to-sit', {
seat: data.seat,
secondaryUserToken: userToken,
displayName,
numberOfChips: envConfig.defaultNumberOfChips
});
callback(prepareResponse({userToken}, 'Player connected successfully.'));
} else {
callback(prepareResponse({}, 'This name is already taken'));
}
} else {
callback(prepareResponse({}, 'This user has already joined to a game. Try clear caching'));
}
});
In my code I'm getting data from another code in guest.user.service. But I get undefined to the value of "guest"
Follows are the methods I have used in guest.user.service
exports.findById = (id) => {
return new Promise(function(resolve, reject) {
guestUserModel.findById(id, (err, data) =>{
if(err){
reject(err);
} else {
resolve(data);
}
});
});
};
exports.getGuestUserByPlayerToken = (playerToken) => {
var player = playerService.findOne({ token: playerToken })
.then(function (data) {
return self.findById(data.guestUser._id.toString());
})
.then(function (guestUser) {
return guestUser.displayName;
})
.catch(function (err) {
throw new Error(err);
})
};
Although I get my displayName for the return value It is not passed to the "guest" in my socket.Is there any syntax issue to get data as I'm using promises.please help
exports.getGuestUserByPlayerToken = async playerToken => {
try {
let player = await playerService.findOne({token:playerToken});
return playerService.findById(player.guestUser._id)
} catch(error) {
console.log(error);
return null;
}
};
This is just handle error on awaited promise not returned one. You need to handle that in caller side.

The ultimate way to prevent duplication in Parse Server once and for all

One of the biggest issue we face now with parse-server is duplication. Although we have implemented a Parse cloud code to prevent such event through beforeSave and afterSave methods at the same time added external middleware to check for existing object before saving still we face duplication over and over specially on concurrent operations.
Here is our code to prevent duplication for a specific class:
Parse.Cloud.beforeSave("Category", function(request, response) {
var newCategory = request.object;
var name = newCategory.get("name");
var query = new Parse.Query("Category");
query.equalTo("name", name);
query.first({
success: function(results) {
if(results) {
if (!request.object.isNew()) { // allow updates
response.success();
} else {
response.error({errorCode:400,errorMsg:"Category already exist"});
}
} else {
response.success();
}
},
error: function(error) {
response.success();
}
});
});
Parse.Cloud.afterSave("Category", function(request) {
var query = new Parse.Query("Category");
query.equalTo("name", request.object.get("name"));
query.ascending("createdAt");
query.find({
success:function(results) {
if (results && results.length > 1) {
for(var i = (results.length - 1); i > 0 ; i--) {
results[i].destroy();
}
}
else {
// No duplicates
}
},
error:function(error) {
}
});
});
This code above is able to prevent some duplicate but most still goes through, example:
What is the "ultimate way" to prevent duplication with Parse server?
You can always create a unique index in mongodb for the field that should be unique in your document.
This way any save that conflicts with that index, will be aborted
Maybe you should write something with Promises like :
Parse.Cloud.beforeSave("Category", function (request, response) {
return new Promise((resolve, reject) => {
var query = new Parse.Query("Category");
query.equalTo("name", "Dummy");
return query.first().then(function (results) {
resolve(); // or reject()
});
})
});
Parse.Cloud.beforeSave("Category", async (request) => {
(...)
await results = query.first();
// then your logic here
response.success();
response.error({ errorCode: 400, errorMsg: "Category already exist" })
})
Here is my Solution:
Parse.Cloud.beforeSave( 'ClassName', async ( request ) => {
const columnName = 'columnName'
const className = 'ClassName'
if( request.object.isNew() ) {
var newCategory = request.object
var name = newCategory.get( columnName )
var query = new Parse.Query( className )
query.equalTo( columnName, name )
const results = await query.count()
if( results === 0 ) {
// no response.success needed
// https://github.com/parse-community/parse-server/blob/alpha/3.0.0.md
} else {
throw 'Is not unique';
}
}
} )

How can I write sequential execution in foreach loop of nodejs?

I am new to nodejs. please help me how to save the data in foreach loop and send response back to controller.it returns false not return object. Thanks in advance
Here is my code like
var rideStatus = require('../models/ridestatus');
module.exports = {
invite_ride: function* (body) {
var saved = false;
var receivers = body.uids;
receivers = receivers.split(',').filter(function(n) { return n != 'null'; });
receivers.forEach(function* (n) {
yield rideStatus.findOne(
{$and: [{ride_id: body.rideid}, {receiver_id: receivers}]},
function(err, doc) {
if (doc === null) {
new rideStatus({
ride_id: body.rideid,
invited_id: body.userId,
receiver_id: receivers,
}).save(function(err1, ridestatus) {
if (!err1) {
rideStatus.findOne({_id: ridestatus._id}).
populate('ride_id').
populate('invited_id').
populate('receiver_id').
exec(function(err2, user) {
// console.log(user);
if (user != null) {
saved = user;
}
});
}
});
}
});
});
return saved;
},
};
because of the asynchronous nature of node.js your "return save" statement won't wait for your i/o to complete his work. and also note that if you have multiple values to be saved, you should return an array of saved Users objects.
var rideStatus = require('../models/ridestatus');
module.exports = {
invite_ride: function* (body) {
var savedUsers = [];
var receivers = body.uids;
receivers = receivers.split(',').filter(function(n) { return n != 'null'; });
var len = receivers.length
var i=0
receivers.forEach(function* (n) {
yield rideStatus.findOne(
{$and: [{ride_id: body.rideid}, {receiver_id: receivers}]},
function(err, doc) {
if (doc === null) {
new rideStatus({
ride_id: body.rideid,
invited_id: body.userId,
receiver_id: receivers,
}).save(function(err1, ridestatus) {
if (!err1) {
rideStatus.findOne({_id: ridestatus._id}).
populate('ride_id').
populate('invited_id').
populate('receiver_id').
exec(function(err2, user) {
// console.log(user);
if (user != null) {
savedUsers.push(user);
}
if(i==len-1)
return savedUsers;
else
i++
});
}else{
i++
}
});
}else{
i++
}
});
});
},
};
Look at library on the link: async

access values after authentication in node js

I've a program that does the below.
Look into a DynamoDB table.
Get the data from the table.
Save the variables in session
After the process, print the values in console.
My code is as below.
intentHandlers['GetMYBusinessInfo'] = function (request, session, response, slots) {
console.log('entered ext bloxk');
if (!session.attributes.userName) {
console.log('eneterd the user entered the block');
var userName = 'jim';
isUserRegistered(userName.toLowerCase(), function (res, err) {
if (err) {
response.fail(err);
console.log(err);
}
else if (!res) {
response.shouldEndSession = true;
}
else {
console.log(res);
var countRes = JSON.stringify(res.Count);
var unpUserRegion = JSON.stringify(res.Items[0].Region);
var unpUserCity = JSON.stringify(res.Items[0].State);
var userRegion = JSON.parse(unpUserRegion);
var userCity = JSON.parse(unpUserCity);
session.attributes.city = userCity;
session.attributes.region = userRegion;
console.log("parsed " + countRes + "\t region is " + userRegion);
session.attributes.userName = true;
}
});
}
console.log(`session values after authentication are user city is ${session.attributes.city}`);
}
The method to check if the value is in DynamoDb or not.
function isUserRegistered(userName, callback) {
var params = {
TableName: "usersTable",
FilterExpression: "#nme = :nme",
ExpressionAttributeNames: {
"#nme": "Name",
},
ExpressionAttributeValues: {
":nme": userName
}
};
var count = 0;
docClient.scan(params, function (err, data) {
if (err) {
console.error("Unable to scan the table. Error JSON:", JSON.stringify(err, null, 2));
callback(false, err);
} else {
console.log("Scan succeeded." + data.Items.length);
if (data.Items.length === 0) {
callback(false);
}
else {
data.Items.forEach(function (itemData) {
console.log("Item :", ++count, JSON.stringify(itemData));
});
callback(data);
}
}
});
}
when I run this, the output that I get is:
session values after authentication are user city is undefined
Scan succeeded.1
Item : 1
{
"ID": "3",
"State": "wisconsin",
"Region": "midwest",
"Name": "jim"
}
{ Items: [ { ID: '3', State: 'wisconsin', Region: 'midwest', Name: 'jim' } ],
Count: 1,
ScannedCount: 1 }
parsed 1 region is midwest
Here I know that Node js being Non-blockable process, the above output is correct, but I want to get the value of city printed in session values after authentication are user city is {HereTheCityComes} instead of session values after authentication are user city is undefined.
I'm sure that placing the console.log(session values after authentication are user city is ${session.attributes.city}); in the last else block(place where the data is returned).
But I need this type of functionality(Get data as shown in my current scenario), as there is some other things to be done after checking if the user is available in database.
please let me know where am I going wrong and how can I fix this.
You can't synchronously expect async result.
What you can do here is solve your problem with promises.
Here is a solution:
intentHandlers['GetMYBusinessInfo'] = function(request, session, response, slots) {
console.log('entered ext bloxk');
var userPromise = Promise.resolve();
if (!session.attributes.userName) {
console.log('eneterd the user entered the block');
var userName = 'jim';
userPromise = new Promise(function (resolve, reject) {
isUserRegistered(userName.toLowerCase(), function (res, err) {
if (err) {
response.fail(err);
reject(err);
}
var countRes = JSON.stringify(res.Count);
var unpUserRegion = JSON.stringify(res.Items[0].Region);
var unpUserCity = JSON.stringify(res.Items[0].State);
var userRegion = JSON.parse(unpUserRegion);
var userCity = JSON.parse(unpUserCity);
session.attributes.city = userCity;
session.attributes.region = userRegion;
console.log("parsed " + countRes + "\t region is " + userRegion);
resolve(res);
});
});
}
userPromise.then(function () {
console.log(`session values after authentication are user city is ${session.attributes.city}`);
});
}
If you are not using ES6, then just install bluebird and use var Promise = require('bluebird')

Resources