Meteor Promises in Accounts.onCreateUser - meteor

I would like to create a stripe account during the user registration in meteor and adjusted Accounts.onCreateUser for that purpose with a promise.
Accounts.onCreateUser((options, user) => {
if (user.services.facebook) {
const { first_name, last_name, email } = user.services.facebook;
user.profile = {}
user.profile.first_name = first_name
user.profile.last_name = last_name
}
else{
user.profile = options.profile
}
user.stripe = {}
return new Promise((resolve,reject) => {
stripe.customers.create({
description: user.profile.first_name + ' ' + user.profile.last_name
},function(err,response){
if (!err) {
user.stripe.id = response.id
resolve(user);
} else {
reject('Could not create user');
}
});
})
});
While the user gets properly created in stripe, the user document in the meteor mongo database only contains the userid but no other field.
Am I using the promise wrong? Any help would be appreciated!

Because onCreateUser runs on the server, we can wrap the Stripe call in a Fiber using Meteor.wrapAsync.
Fibers allow async code to run as though it was synchronous, but only on the server. (Here's a great presentation on what Fibers are and why Meteor uses them)
With wrapAsync the code looks like this:
Accounts.onCreateUser((options, user) => {
if (user.services.facebook) {
const { first_name, last_name, email } = user.services.facebook;
user.profile = {}
user.profile.first_name = first_name
user.profile.last_name = last_name
} else {
user.profile = options.profile
}
user.stripe = {};
const createStripeCustomer = Meteor.wrapAsync(stripe.customers.create,stripe.customers);
const response = createStripeCustomer({
description: user.profile.first_name + ' ' + user.profile.last_name
});
user.stripe.id = response.id
return user;
});

Yes, you create the promise but you don't wait until it resolves. By the time it does onCreateUser has long returned. Try this:
function createStripeUser(user) {
return new Promise((resolve,reject) => {
stripe.customers.create({
description: user.profile.first_name + ' ' + user.profile.last_name
},function(err,response){
if (!err) {
user.stripe.id = response.id
resolve(user);
} else {
reject(err);
}
});
})
}
Accounts.onCreateUser(async (options, user) => {
if (user.services.facebook) {
const { first_name, last_name, email } = user.services.facebook;
user.profile = {}
user.profile.first_name = first_name
user.profile.last_name = last_name
}
else {
user.profile = options.profile
}
user.stripe = {}
try {
return await createStripeUser(user);
} catch(e) {
// handle your error
}
});

Related

Firebase https cloud function to send notifications always ends in timeout

Function execution took 60002 ms, finished with status: 'timeout'
I have cloud function that collects users product & price data, hits an external API to get the latest prices and sends a notification if the price has changed. I've read several similar questions like this but I am in fact sending a response back to the client. I've tried increasing the timeout to 120 seconds, but it still times out. So, I think there might be an issue with how I'm returning promises? Any suggestions?
Here is my code:
exports.pushTestWithRP = functions.https.onRequest((req, res) => {
var uidsAndTokens = [];
ref.child('tokens').once('value').then(snap => {
snap.forEach(childSnap => {
uidsAndTokens.push({
uid: childSnap.key,
deviceToken: childSnap.val()
});
});
return uidsAndTokens;
}).then((uidsAndTokens) => {
var uidsAndCruises = [];
ref.child('watchlist-items/users').once('value').then(snap => {
snap.forEach(childSnap => {
var uid = childSnap.key;
childSnap.forEach(childChildSnap => {
var product = childChildSnap.key;
var productWatchInfo = childChildSnap.val();
uidsAndProducts.push({
uid: uid,
product: product,
watchInfo: productWatchInfo
});
}); // end childChildSnap
}); // end childSnap
return uidsAndProducts;
}).then((uidsAndProducts) => { // end snap watchlist-items/users
var uidsOnly = [];
for (var i=0; i<uidsAndTokens.length; i++) {
uidsOnly.push(uidsAndTokens[i].uid);
}
// user has a FCM token
var uidsAndProductsWithTokens = [];
for (var i=0; i<uidsAndProducts.length; i++) {
//check if exists in tokens array
var currUid = uidsAndProducts[i].uid;
if (uidsOnly.includes(currUid)) {
//console.log('this uid has a token: ', currUid);
uidsAndProductsWithTokens.push(uidsAndProducts[i]);
} else {
//this uid does NOT have a token
}
}
function getTokenForUid(uid) {
for (var i in uidsAndTokens) {
if (uidsAndTokens[i].uid == uid) {
var deviceToken = uidsAndTokens[i].deviceToken;
break;
}
}
return deviceToken;
}
var allPromises = [];
// call API only for uids with tokens
for (var i=0; i<uidsAndProductsWithTokens.length; i++) {
const product = uidsAndProductsWithTokens[i].product;
const uid = uidsAndProductsWithTokens[i].uid;
const deviceToken = getTokenForUid(uid);
const pDates = uidsAndProductsWithTokens[i].watchInfo.pDates;
const pName = uidsAndProductsWithTokens[i].watchInfo.pName;
getCurrentPricesFromAPI(product).then((response) => {
if (typeof response.response != 'undefined') {
const productId = response.response.product.product_id;
const allPrices = response.response.prices;
const promises = [];
// parse thru prices and send notifications
for (var date in pDates) {
// get all current prices and sort by price to get cheapest
var cheapest = [];
for (var i = 0; i < allPrices.length; i++) {
if (allPrices[i].data[productId][date] && allPrices[i].data[productId][date].hasOwnProperty('Inside')) {
const iPrice = allPrices[i].data[productId][date].Inside;
cheapest.push(iPrice);
}
}
if (cheapest[0] > 0) {
cheapest = cheapest.sort(function (a, b) { return a - b; });
if (sDates[date].hasOwnProperty('Inside')) {
const priceDiff = cheapest[0] - sDates[date].Inside.price;
if (priceDiff < -10) {
const payload = {
notification: {
title: pName + ' Price DROP Alert!',
body: 'prices for the ' + date + ' are $' + cheapest[0] + ' (DOWN $' + Math.abs(priceDiff) + ')',
sound: 'default'
}
};
promises.push(admin.messaging().sendToDevice(deviceToken, payload));
}
else if (priceDiff > 10) {
const payload = {
notification: {
title: pName + ' Price Hike Alert',
body: 'prices for the ' + date + ' are $' + cheapest[0] + ' (UP $' + priceDiff + ')',
sound: 'default'
}
};
promises.push(admin.messaging().sendToDevice(deviceToken, payload));
}
}
}
}
allPromises = allPromises.concat(promises);
}
}) // end handle API response
} // end for loop
return allPromises;
}).then((allPromises) => {
return Promise.all(allPromises);
res.send('got tokens and ids');
}).catch(error => {
res.send('there was an error');
});
}); // end uidsAndTokens
}); // end function
I can't figure this out and would appreciate any help!

Cordova SQLite class - cannot return value from database

So, I'm building an app with Cordova and SQLite and below is the object Storage I created to handle the database properties and methods.
function Storage(connection, platform)
{
if (platform == 'mobile') {
this.db = window.sqlitePlugin.openDatabase({name: connection.name, location: connection.location});
} else if (platform == 'web') {
this.db = window.openDatabase(connection.name, connection.version, connection.mode, -1);
}
this.read = function(sql, vals) {
var rows = null;
this.db.transaction(function(tx) {
tx.executeSql(sql, vals, function(tx, res) {
rows = res.rows;
});
}, function(error) {
console.log('Transaction error: '+error.message);
});
return rows;
};
};
var connection = {
name: 'database.db',
version: '1.0',
mode: 'Development'
};
var db = new Storage(connection, 'web');
var sql = '';
sql += 'SELECT *';
sql += ' FROM countdown';
sql += ' WHERE countdown_status != ?;';
var rows = db.read(sql, [1]);
console.log(rows);
This code logs 'null' and I don't know why since I have data into my database. And when I try to log from within the tx.executeSql method, it logs the data from database.
Any ideas on why I cannot get this data out of that function?
Thanks a lot.
the db read is asynchronous - it needs to return the data to a callback function like so:
getHighScore: function (type,callback) {
var query = "SELECT Value FROM HighScore where Type = '" + type + "';";
playedDb.transaction(function (tx) {
tx.executeSql(query, [], function (tx, res) {
var out;
if (typeof res.rows.item(0) === "undefined") {
if (typeof callback === "function") {
callback(-1);
return;
};
} else {
out = res.rows.item(0).Value
//task 301 only allow alphanumeric and decimal point (for version)
out = String(out).replace(/[^0-9a-z\.]/gi, '');
if (typeof callback === "function") {
callback(out);
return;
};
}
}, function (e) {
console.log("getHighScore FAILED: " + e.message);
if (typeof callback === "function") {
callback(-2);
return;
};
});

Meteor.call and server methods not working properly

I have this code on my meteor app:
// client side
Template.lead.events({
'submit .insertExternalAccountForm': function (event) {
event.preventDefault();
Session.set('mcsStatus', 'Creating external account ...');
var target = {
code: event.target.code.value,
leadId: event.target.leadId.value,
name: event.target.name.value,
username: event.target.username.value,
password: event.target.password.value,
searchSourceId: event.target.searchSourceId.value,
clientId: event.target.clientId.value,
clientUserId: event.target.clientUserId.value
};
var noFormError = true;
if (target.username.length === 0) {
Session.set("username_error", "Field must not be empty");
noFormError = false;
} else {
Session.set("username_error", null);
}
if (target.password.length === 0) {
Session.set("password_error", "password must not be empty");
noFormError = false;
} else {
Session.set("password_error", null);
}
if (!noFormError) {
return noFormError;
}
Meteor.call('createExternalAccount', target, function (err, res) {
if (err) {
console.error(err);
}
console.log('in meteor call');
Router.go('/' + res.domain + '/' + res.externalId);
});
}
});
//server side
var createExternalAccountSync = function (query, external) {
return models.SearchSources.findOne(query).exec()
.then(function (searchsource) {
external.domain = searchsource.source;
var emr = searchsource.source.split('-');
return models.Organization.findOne({externalId: emr[2]}).exec();
}).then(function (org) {
console.log('after org');
external.organizationId = org._id;
return models.AppUser.findOne({clientId: external.clientId, externalId: external.clientUserId }).exec();
}).then(function (user) {
console.log('after app user');
external.userId = user._id;
external.userIds = [user._id];
return new Promise(function (resolve,reject) {
console.log('saveOrUpdate');
models.ExternalAccount.saveOrUpdate(external, function (err, newE) {
if (err) {
console.error(err)
reject(err);
}
resolve(newE)
});
});
})
.catch(function (e) {
console.error(e);
throw new Meteor.Error(e);
});
};
Meteor.methods({'createExternalAccount': function (data) {
var query = {};
var newExternalAccount = new models.ExternalAccount();
newExternalAccount.username = data.username;
newExternalAccount.password = data.password;
newExternalAccount.externalId = data.username;
newExternalAccount.name = data.name;
newExternalAccount.clientId = data.clientId;
newExternalAccount.clientUserId = data.clientUserId;
newExternalAccount._metadata = { leadId: data.leadId };
if (data.code === 'f') {
query.searchSourceId = '5744f0925db77e3e42136924';
} else {
query.searchSourceId = data.searchSourceId;
}
newExternalAccount.searchSourceId = query.searchSourceId;
console.log('creating external account')
createExternalAccountSync(query, newExternalAccount)
.then(function (external) {
console.log('should return to meteor call');
return external;
})
.catch(function (e) {
console.error(e);
throw new Meteor.Error(e);
});
}
});
The problem that I'm having is that the code on the server side, while it's being called properly, is not triggering the client side meteor.call, there's no console.log output or anything. I believe that the Meteor.wrapAsync method is properly used, but still not showing anything on the client side, and not in fact redirecting where I want the user to go after form submission.
UPDATE
The code has being updated to the newest form, but now I'm getting a weird error on the client, and its actually because the meteor.call method on the template returns neither error or result
Exception in delivering result of invoking 'createExternalAccount': http://localhost:3000/app/app.js?hash=c61e16cef6474ef12f0289b3f8662d8a83a184ab:540:40
http://localhost:3000/packages/meteor.js?hash=ae8b8affa9680bf9720bd8f7fa112f13a62f71c3:1105:27
_maybeInvokeCallback#http://localhost:3000/packages/ddp-client.js?hash=27502404fad7fc072e57e8b0b6719f40d92709c7:3557:21
receiveResult#http://localhost:3000/packages/ddp-client.js?hash=27502404fad7fc072e57e8b0b6719f40d92709c7:3577:30
_livedata_result#http://localhost:3000/packages/ddp-client.js?hash=27502404fad7fc072e57e8b0b6719f40d92709c7:4742:22
onMessage#http://localhost:3000/packages/ddp-client.js?hash=27502404fad7fc072e57e8b0b6719f40d92709c7:3385:28
http://localhost:3000/packages/ddp-client.js?hash=27502404fad7fc072e57e8b0b6719f40d92709c7:2736:19
forEach#[native code]
forEach#http://localhost:3000/packages/underscore.js?hash=27b3d669b418de8577518760446467e6ff429b1e:149:18
onmessage#http://localhost:3000/packages/ddp-client.js?hash=27502404fad7fc072e57e8b0b6719f40d92709c7:2735:15
dispatchEvent#http://localhost:3000/packages/ddp-client.js?hash=27502404fad7fc072e57e8b0b6719f40d92709c7:175:27
_dispatchMessage#http://localhost:3000/packages/ddp-client.js?hash=27502404fad7fc072e57e8b0b6719f40d92709c7:1160:23
_didMessage#http://localhost:3000/packages/ddp-client.js?hash=27502404fad7fc072e57e8b0b6719f40d92709c7:1218:34
onmessage#http://localhost:3000/packages/ddp-client.js?hash=27502404fad7fc072e57e8b0b6719f40d92709c7:1365:28
By the code you provided,it could be because you are calling different method.
You defined 'createAccount' but on client side you are calling 'createExternalAccount'

Weird undefined error on server

I have the following meteor method
hasNoPendingPayments: function() {
var userId = Meteor.userId();
console.log(userId); <---------------------- correctly logs userId
var user = Users.findOne({_id: userId }, { fields: { services: 0 } });
console.log(user); <-------------------------- logs 'undefined'
return hasNoPendingPayments(user);
},
This private helper I call from the above
hasNoPendingPayments = function(user) {
// console.log('hasNoPendingPayments ');
// console.log(user);
var payments = Payments.find({ userId: user._id, status: {
$in: [Payments.States.PENDING, Payments.States.PROCESSING]}
});
return payments.count() === 0;
};
And I call it from the client here
Template.payments.created = function() {
this.hasNoPendingPayments = new ReactiveVar(false);v
};
Template.payments.rendered = function () {
Session.set('showPaymentRequestForm', false);
var self = this;
Meteor.call('hasNoPendingPayments', function(error, result) {
if (result === true) { self.hasNoPendingPayments.set(true); }
});
...
However, I get an undefined error on the server when I load the template initially (I marked where in code). Although, when I try call the same query on the client with the same userId, i correctly gets the user record
Any idea as to why this is?
Try with this.
Template.payments.rendered = function () {
Session.set('showPaymentRequestForm', false);
var self = this;
if(Meteor.userId()){
Meteor.call('hasNoPendingPayments', function(error, result) {
if (result === true) { self.hasNoPendingPayments.set(true); }
});
}else{
console.log("Seems like user its not logged in at the moment")
}
Maybe when you make the Meteor.call, the data its not ready
Also just to be sure, when you run Users.findOne({_id: userId }, { fields: { services: 0 } }); on console.log what you get?
Maybe the find is wrong or have some typo
update
Router.map(function()
{
this.route('payments',
{
action: function()
{
if (Meteor.userId())
this.render();
} else{
this.render('login') // we send the user to login Template
}
}
}
or waitOn
Router.map(function () {
this.route('payments', {
path: '/payments',
waitOn: function(){
return Meteor.subscribe("userData"); //here we render template until the subscribe its ready
}
});
});
Meteor stores all the user records in Meteor.users collection
so try Meteor.users.findOne({_id: userId }....)
Instead of Users.findOne({_id: userId }, { fields: { services: 0 } });
in your server method

LoginHandler with ldapjs and Meteor.methods

I try to implement a logIn in Meteor 0.9.2.1 with LDAPJS and Meteor methods. The code for the server-side is:
var Future = Meteor.npmRequire('fibers/future');
var ldap = Meteor.npmRequire('ldapjs');
LDAP = {};
LDAP.ldap = ldap;
LDAP.serverIP = 'xxx';
LDAP.serverPort = 'xxx';
LDAP.searchOu = 'ou=xxx,dc=xxx,dc=xxx';
LDAP.searchQuery = function(user) {
return{
filter: '(uid=username)',
scope: 'sub'
}
};
LDAP.checkAccount = function (options) {
LDAP.client = ldap.createClient({
url: 'ldap://' + LDAP.serverIP + ':' + LDAP.serverPort
});
options = options || {};
var dn = [];
future = new Future;
if (options.hasOwnProperty('username') && options.hasOwnProperty('password')) {
LDAP.client.search(LDAP.searchOu, LDAP.searchQuery(options.username), function (err, search) {
search.on('searchEntry', function(entry){
//console.log('entry: ' + JSON.stringify(entry.object));
dn.push(entry.object.uid);
dn.push(entry.object.userPassword)
});
search.on('error', function (err) {
throw new Meteor.Error(500, "LDAP server error");
});
search.on('end', function () {
if (dn.length === 0) {
future['return'](false);
return false;
}
var testBind = LDAP.ldap.createClient({
url: 'ldap://' + LDAP.serverIP + ':' + LDAP.serverPort
});
testBind.bind(dn[10], options.password, function (err) {
future['return'](!err);
});
client.unbind(function (err) {
assert.ifError(err);
future['return'](!err);
});
});
});
} else {
throw new Meteor.Error(400, "Missing Parameter");
}
};
var loginHandler = function (username, password) {
Accounts.registerLoginHandler("ldapjs",function(loginRequest) {
if (LDAP.checkAccount(loginRequest)) {
var user = Meteor.users.findOne({ username: loginRequest.username });
if(err){
console.log(err)
}
return {
userId: uid
}
}
});
};
Meteor.methods({
setSignIn: function(username, password) {
loginHandler(username,password)
}
});
My Problem is, that when I want to log in it starts with the loginHandler. But than the console throws back that Object has no method checkAccount. I changed today a lot and I'm already totally confused.
You need to instantiate the empty object as var LDAP = {}. Rest will be solved magically :)
I finally got to work it. Referneces:
http://notjoshmiller.com/using-ldaps-in-meteor/, https://github.com/emgee3/meteor-accounts-ldap
server-side:
var Future = Meteor.npmRequire('fibers/future');
var ldap = Meteor.npmRequire('ldapjs');
var LDAP = {};
LDAP.ldap = ldap;
//provides the variables, needed for the connection
LDAP.serverIP = 'xxx';
LDAP.serverPort = 'xxx';
LDAP.searchOu = 'ou=xxx,dc=xxx,dc=xxx';
//is needed for the searchQuery, which delivers the Filter so that only the uid with
//the given username get searched
LDAP.searchQuery = function(username) {
return{
filter: '(uid=' + username + ')',
scope: 'sub'
}
};
LDAP.checkAccount = function (options) {
//connects the client, nginx is here not necessary
LDAP.client = ldap.createClient({
url: 'ldap://' + LDAP.serverIP + ':' + LDAP.serverPort
});
options = options || {};
var dn = [];
future = new Future;
if (options.hasOwnProperty('username') && options.hasOwnProperty('password')) {
//create the connection
LDAP.client.search(LDAP.searchOu, LDAP.searchQuery(options.username), function (err, search) {
if(err){
console.log(err)
}
//uses the class searchEntry, which is node-specific
search.on('searchEntry', function (entry) {
dn.push(entry.objectName);
LDAP.displayName = entry.object.displayName
});
search.on('error', function (err) {
throw new Meteor.Error(500, "LDAP server error");
});
//uses the end class to 'fulfill' the connection by binding
search.on('end', function () {
if (dn.length === 0) {
future['return'](false);
return false;
}
LDAP.client.bind(dn[0], options.password, function (err) {
future['return'](!err);
});
});
});
return future.wait();
} else {
throw new Meteor.Error(400, "Missing Parameter");
}
};
Meteor.startup(function(){
Accounts.registerLoginHandler("ldapjs", function (loginRequest) {
if (LDAP.checkAccount(loginRequest)) {
var userId;
var user = Meteor.users.findOne({
username : loginRequest.username
//'profile.name': LDAP.displayName
});
if (user) {
userId = user._id;
} else {
// If no Meteor Account is found for a valid LDAP logon,
// you can either prevent logon by passing 'undefined' or
// you can automatically create the new account.
// return undefined;
userId = Meteor.users.insert({ username : loginRequest.username });
}
return {
userId: userId
}
}
return undefined;
});
});
client side:
Meteor.ldapLogin = function (username, password, callback) {
var loginRequest = {
username: username,
password: password
};
Accounts.callLoginMethod({
methodArguments: [loginRequest],
userCallback: function (err) {
if (err) {
console.log(err);
Session.set('alert', 'No valid inputs!');
} else {
Router.go('/Home');
}
}
});
};
//handles LogIn-Button, by using LDAPJS
Template.signIn.events({
"submit #box-login": function (e, t) {
e.preventDefault();
var signInForm = $(e.currentTarget),
username = trimInput(signInForm.find('#emailSignIn').val().toLowerCase()),
password = signInForm.find('#passwordSignIn').val();
if(isNotEmpty(username)&& isNotEmpty(password)) {
Meteor.ldapLogin(username, password, function (err) {
if (err) {
console.log(err)
Session.set('alert', 'Sorry, something went wrong.');
}
});
} else {
Session.set('alert','Please insert your username and password!')
}
return false;
}
});
PS: No Meteor.methods and Meteor.call is needed! It might change with every new Meteor version and package, but I guess u're aware of that ;)

Resources