UNPKG

poserver

Version:
666 lines (580 loc) 24.1 kB
/** * Created by tomdaley on 9/18/16. */ /** * U S E R P R O F I L E */ /** * @namespace * @property {object} results * @property {object} results.response * @property {number} results.response.index * @property {string} results.response.entity */ var params = require("../../../poserver-configuration.json"); var prompts = require('./../const/prompts.js'); var statemaps = require('./../const/statemaps'); var Util = require('./../JdBotUtil'); var ParsedName = require('./../classes/clsParsedName'); var Person = require('./../classes/clsPerson'); var builder = require('botbuilder'); var consts = require('./../node_modules/botbuilder/lib/consts'); var authy = require('authy')(params.AUTHY_API_KEY); const library = new builder.Library('userProfile'); library.dialog('/firstRun', [ function (session) { Util.getUserProfile(session) .then(function (isLoaded) { if (!isLoaded) { session.beginDialog("userProfile:/"); } else { /** @param {Person} session.userData.userProfile */ var prompt = session.gettext(prompts.welcomeBackMessage, { uname : session.userData.userProfile.name.salutation, attorneyName: "Tom Daley" }); session.send(prompt); session.beginDialog("userProfile:/upgrade"); } }); }, function (session) { session.replaceDialog("*:/"); } ]); library.dialog('/upgrade', [ function (session, args) { args = args || {}; var fromVersion = session.userData[consts.Data.FirstRunVersion]; var toVersion = (args.hasOwnProperty("toVersion") ? args.toVersion : 0); //Only prompt if we obviously need to do an upgrade. if (toVersion > fromVersion) { var prompt = session.gettext(prompts.sayProfileUpdateRequired, {"ov": fromVersion, "nv": toVersion}); session.send(prompt); } //Check for an upgrade anyway in order to fix a corrupted userprofile. session.beginDialog('userProfile:/upgradeFields'); } ]); library.dialog('/upgradeFields', [ function (session) { //Collection of string fields to make sure we have var requiredFields = [ {name: "email", dialog: "userProfile:/Email"}, {name: "birthDate", dialog: "userProfile:/DOB"}, {name: "telephone", dialog: "userProfile:/Telephone"} ]; var field; var fieldIdx; var detectedMissingField = false; //Search through the required fields and see if we find one that is missing. for (fieldIdx in requiredFields) { field = requiredFields[fieldIdx]; if (!hasProfileField(session, field.name)) { detectedMissingField = true; break; } } //If we found a missing field, prompt for it. if (detectedMissingField) { session.beginDialog("userProfile:/"); } //Finally, if all fields are present, then save the user's profile and head to the main dialog else { Util.saveUserProfile(session.userData.userProfile, session) .then(function (result) { if (result.hasOwnProperty("insertedId")) { session.userData.userProfile.users_id = result.insertedId; } session.endDialog(); //"##### profile saved"); }) .catch(function (reason) { console.error("userProfile:/upgradeFields: Error updating userProfile"); console.error(reason); session.endDialog(); //"##### error saving profile"); }); } } ]); //Internal utility method to check for the presence of a non-null, non-blank field function hasProfileField(session, fieldName) { return session.userData.userProfile.hasOwnProperty(fieldName) && session.userData.userProfile[fieldName] !== null && session.userData.userProfile[fieldName].trim().length > 0; } library.dialog('/', [ function (session, args, next) { var hasAllFields = true; var requiredFields = [ {name: "name", dialog: "userProfile:/UserName"}, {name: "email", dialog: "userProfile:/Email"}, {name: "birthDate", dialog: "userProfile:/DOB"}, {name: "gender", dialog: "userProfile:/Gender"}, {name: "telephone", dialog: "userProfile:/Telephone"} ]; for (var p in requiredFields) { var field = requiredFields[p]; if (!hasProfileField(session, field.name)) { session.beginDialog(field.dialog); hasAllFields = false; } } if (hasAllFields) next({profileComplete: hasAllFields}); }, function (session, args) { if (args.profileComplete) session.beginDialog("userProfile:/Verify"); else session.replaceDialog("userProfile:/"); } ]); function formatDate(dateString) { var myDate = new Date(dateString); return ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"][myDate.getDay()] + ", " + ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][myDate.getMonth()] + " " + myDate.getDate() + ", " + myDate.getFullYear(); } //Called from main menu when user wants to update his/her user profile. Returns to main menu. library.dialog('/Verify', [ function (session) { session.userData.state = "userProfile/Verify"; session.userData.stateMap = statemaps.userprofile; var profile = session.gettext(prompts.userProfile, { "uname" : session.userData.userProfile.name.fullNameWithTitle(), "dob" : formatDate(session.userData.userProfile.birthDate), "uemail": session.userData.userProfile.email, "uphone": session.userData.userProfile.telephone, "gender": prompts.gender[session.userData.userProfile.gender] }); session.send(profile); builder.Prompts.confirm(session, prompts.askConfirmProfile, session.userData.config.PromptChoiceOptions); }, function (session, results) { if (results.response) { Util.saveUserProfile(session.userData.userProfile, session) .then(function (result) { if (result.hasOwnProperty("insertedId")) { session.userData.userProfile.users_id = result.insertedId; } session.endDialog(prompts.sayUserProfileUpdateOk); }) .catch(function (reason) { console.log("Error updating userProfile"); console.log(reason); session.endDialog(prompts.sayUserProfileUpdateFail); }); } else { var choices = session.gettext(prompts.userProfileChoices, { "uname" : session.userData.userProfile.name.fullNameWithTitle(), "dob" : formatDate(session.userData.userProfile.birthDate), "uemail": session.userData.userProfile.email, "uphone": session.userData.userProfile.telephone, "gender": prompts.gender[session.userData.userProfile.gender] }); builder.Prompts.choice(session, prompts.askWhichToChange, choices, session.userData.config.PromptChoiceOptions); } }, function (session, results) { var choiceIdx = results.response.index; switch (choiceIdx) { case 0: session.beginDialog('userProfile:/UserName'); break; case 1: session.beginDialog('userProfile:/DOB'); break; case 2: session.beginDialog('userProfile:/Gender'); break; case 3: session.beginDialog('userProfile:/Email'); break; case 4: session.beginDialog('userProfile:/Telephone'); break; case 5: session.endDialog(prompts.sayLetsGetStarted); //session.replaceDialog("*:/"); break; } }, function (session) { session.replaceDialog('userProfile:/Verify'); } ]); library.dialog('/Email', [ function (session) { builder.Prompts.text(session, prompts.askEmailAddress); }, function (session, results) { if (!session.userData.hasOwnProperty("userProfile")) session.userData.userProfile = new Person({}); session.userData.userProfile.email = results.response; session.endDialog(); } ]); library.dialog('/DOB', [ function (session) { builder.Prompts.time(session, prompts.askDateOfBirth); }, function (session, results) { if (!session.userData.hasOwnProperty("userProfile")) session.userData.userProfile = new Person({}); session.userData.userProfile.birthDate = (builder.EntityRecognizer.resolveTime([results.response])).toJSON(); session.endDialog(); } ]); library.dialog('/Gender', [ function (session) { var prompt = prompts.askGender; builder.Prompts.choice(session, prompt, prompts.gender, session.userData.config.PromptChoiceOptions); }, function (session, results) { if (!session.userData.hasOwnProperty("userProfile")) session.userData.userProfile = new Person({}); session.userData.userProfile.gender = results.response.index; session.endDialog(); } ]); library.dialog('/Telephone', [ //Prompt for a telephone number function (session) { builder.Prompts.text(session, prompts.askPhoneNumber); }, //Retrieve and verify the telephone number function (session, results) { if (!session.userData.hasOwnProperty("userProfile")) session.userData.userProfile = new Person({}); session.userData.userProfile.telephone = results.response.replace(/[^0-9]/g, ""); // The /lookupPhone dialog will fill in the userProfile.cellPhone property if the telephone number // provided is actually a cell phone. session.beginDialog("userProfile:/LookupPhone"); }, //See if phone registration was successful function (session, args) { if (args.success === false) { session.replaceDialog("userProfile:/Telephone"); } else { session.endDialog(); } } ]); library.dialog('/UserName', [ function (session) { builder.Prompts.text(session, prompts.askUserName); }, function (session, results) { if (results.response.indexOf(" ") === -1) { session.send(prompts.sayINeedYourFullName); session.replaceDialog("/UserName"); } else { var parsedName = new ParsedName({}); if (!session.userData.hasOwnProperty("userProfile")) session.userData.userProfile = new Person({}); session.userData.userProfile.setName(parsedName.parseName(results.response, session.message.user.name)); session.endDialog(); } } ]); /** * Determine whether phone is valid and what type of phone it is (cell, land). * * Cannot invoke /LookupPhone until userProfile has a telephone property. */ library.dialog('/LookupPhone', [ function (session, args) { args = args || {}; //Error checking if (!session.userData.userProfile.hasOwnProperty("telephone") && !args.hasOwnProperty("telephone")) { throw (new Error("userProfile must have a telephone property before /LookupPhone is invoked")); } var telephone = args.hasOwnProperty("telephone") ? args.telephone : session.userData.userProfile.telephone; //Callback executed after server looks up the cellphone var callback = function (err, res) { res = res || {success: false}; if (res.success) { if (res.type === "cellphone") { session.userData.userProfile.cellPhone = telephone; session.replaceDialog('userProfile:/VerifyPhone', { telephone: telephone, type: "cellphone", provider: res.provider }); } else { session.send(prompts.sayOnlyCellphonesSupported, { phone: telephone, type : res.type }); res.success = false; session.endDialogWithResult(res); } } else { session.send(err.message); session.endDialogWithResult(res); } }; authy.phones().info(telephone, '1', callback); } ]); /** * Cannot invoke /RegisterPhone until userProfile has an email property and a cellPhone property. */ library.dialog('/RegisterPhone', [ function (session, args) { args = args || {}; //Error checking if (!session.userData.userProfile.hasOwnProperty("email")) { throw (new Error("userProfile must have an email property before /RegisterPhone is invoked")); } if (!session.userData.userProfile.hasOwnProperty("cellPhone")) { throw (new Error("userProfile myust have a cellPhone property before /RegisterPhone is invoked")); } /** * * @param err * @param {object} res - Response from underlying Authy API * @property {object} res.user - User object from Authy Server * @property {string} res.user.id - UserId assigned by Authy Server. Save this in channelIdentities[] */ var callback = function (err, res) { if (err) { console.error("userProfile:/RegisterPhone: Error registering user."); console.error(err); console.error(res); session.endDialogWithResult({success: false, response: res, error: err}); } else { try { if (!session.userData.userProfile.hasOwnProperty("channelIdentities")) session.userData.userProfile.channelIdentities = {}; var identity = { "source" : "authy.jdbot", "id" : res.user.id, "name" : session.userData.userProfile.email, "verified": new Date() }; if (args.provider) identity.provider = args.provider; if (args.type) identity.type = args.type; //see if we have an existing channel identity from this source //NOTE: We create and save channel identities as an array, but botbuilder's //serialization converts it to an object having keys equal to the array indices. //For example, we create [{source:a,id:a,name:a}, {source:b,id:b,name:b}] but //the serialization converts it to {'0':{source:a,id:a,name:a},'1':{source:b,id:b,name:b}}. var replacedChannelIdentity = false; /** @property {number} session.userData.userProfile.channelIdentities.cidx */ for (var cidx in session.userData.userProfile.channelIdentities) { var channel = session.userData.userProfile.channelIdentities[cidx]; if (channel.source == identity.source) { session.userData.userProfile.channelIdentities[cidx] = identity; replacedChannelIdentity = true; break; } } //If we didn't update an existing channel identity, then add a new new one to the end //of the list. if (!replacedChannelIdentity) { var newIdx = session.userData.userProfile.channelIdentities.length; session.userData.userProfile.channelIdentities[newIdx] = identity; } session.endDialogWithResult({success: true}); } catch (error) { console.error("userProfile:/RegisterPhone: Error saving channel identity."); console.error(error.stack); } } }; //Here to actually register the user authy.register_user(session.userData.userProfile.email, session.userData.userProfile.cellPhone, callback); } ]); /** * Must have a cellPhone property on the userProfile object before calling this dialog. */ library.dialog('/VerifyPhone', [ //Send SMS Code to user function (session, args) { args = args || {}; if (args.provider) session.dialogData.provider = args.provider; if (args.type) session.dialogData.type = args.type; //Error checking and identity lookup if (!session.userData.userProfile.hasOwnProperty("cellPhone")) { throw (new Error("userProfile must have cellPhone property before /VerifyPhone is invoked. Did you invoke /LookupPhone already?")); } var callback = function (err, res) { res = res || {success: false}; if (res.success) { builder.Prompts.text(session, prompts.sayEnterSmsCode); } else { session.endDialogWithResult(res); } }; //Custom messages are not allowed at this time. It might be because my subscription level is too low and //Authy wants more $$ per month in order to enable this feature. Fuckers. authy.phones().verification_start(session.userData.userProfile.cellPhone, '1', /*{custom_message: "Here is your JDBOT verification code."},*/ callback); }, //Retrieve SMS Code from user function (session, results) { var token = results.response; var callback = function (err, res) { res = res || {success: false}; if (err) { console.log(err); session.endDialogWithResult({success: false, response: res, error: err}); } else { session.send(prompts.sayVerificationCodeValid); var props = {}; if (session.dialogData.provider) props.provider = session.dialogData.provider; if (session.dialogData.type) props.type = session.dialogData.type; session.replaceDialog('userProfile:/RegisterPhone', props); } }; authy.phones().verification_check(session.userData.userProfile.cellPhone, '1', token, callback); } ]); /** * Authenticate User - basically the same as /VerifyPhone except the phone # to use comes from args rather than * the user profile. I could have refactored /VerifyPhone but I am suffering from an allergy attack that prevents * me from wanting to mess with it. :-( */ library.dialog('/AuthenticateUser', [ //Send SMS Code to user function (session, args) { args = args || {}; //Error checking and identity lookup if (!args.hasOwnProperty("telephone")) { throw (new Error("Args object must have a 'telephone' property.")); } var callback = function (err, res) { res = res || {success: false}; if (res.success) { builder.Prompts.text(session, prompts.sayEnterSmsCode); } else { session.endDialogWithResult(res); } }; //Custom messages are not allowed at this time. It might be because my subscription level is too low and //Authy wants more $$ per month in order to enable this feature. Fuckers. authy.phones().verification_start(args.telephone, '1', /*{custom_message: "Here is your JDBOT verification code."},*/ callback); }, //Retrieve SMS Code from user function (session, results) { var token = results.response; var callback = function (err, res) { res = res || {success: false}; if (err) { console.log(err); session.endDialogWithResult({success: false, response: res, error: err}); } else { session.send(prompts.sayVerificationCodeValid); session.endDialogWithResult({success: true}); } }; authy.phones().verification_check(session.userData.userProfile.cellPhone, '1', token, callback); } ]); module.exports = library;