Parse JavaScript Promises within Loop not Completing - javascript

I just can't seem to get a hang of JavaScript promises.
In Parse, I have an attribute on a user that stores an array of Facebook friends IDs. When a new user logs in through Facebook for the first time I want to iterate through their Facebook friends that are using the app, then update those user's Facebook friends array to include this new user.
So... in user before_save I have this:
var friendsArray = user.get("facebookFriends");
// Iterate through all Facebook friends
_.each(friendsArray, function(facebookId){
// Retrieve the Facebook friend user
var query = new Parse.Query(Parse.User);
query.equalTo("facebookId", facebookId);
console.log("this executes");
query.find().then( function(){
var user = results[0];
// This does not execute
console.log("Need to update " + user.get("displayName") + "'s facebook friends");
return Parse.Promise.as();
});
}
My problem is not that different than another previous problem I encountered (Parse JavaScript SDK and Promise Chaining), except that this time I need the results of the async call before I can begin updating the user's Facebook friends array.

The way to accomplish this is with Parse.Promise.when() which is fulfilled when array of promises passed to it are fulfilled. And the loop can be made prettier as _.map().
var friendsArray = user.get("facebookFriends");
var findQs = _.map(friendsArray, function(facebookId){
// Retrieve the Facebook friend user
var query = new Parse.Query(Parse.User);
query.equalTo("facebookId", facebookId);
console.log("this executes");
return query.find();
});
return Parse.Promise.when(findQs).then(function(){
var user = results[0];
// This will work now
console.log("Need to update " + user.get("displayName") + "'s facebook friends");
return Parse.Promise.as();
});
The results will be an array of arrays -- since find returns an array --passed as var-args, and underscore _.toArray() is useful here, i.e.
return Parse.Promise.when(finds).then(function() {
var individualFindResults = _.flatten(_.toArray(arguments));
// and so on

Related

How do I Collect User IDs + Retrieve Corresponding Tokens + Send a Push Notification Via Firebase Cloud Function (JS)

The Problem:
I have been unable to use Firebase (Google) Cloud Functions to collect and utilize device tokens for the cloud messaging feature.
Context:
I am a self-taught android-Java developer and have no JavaScript experience. Despite that, I believe I have code that should work and am not sure what the problem is. To my understanding, it could be one of three things:
Somehow my Firebase Realtime Database references are being called incorrectly and I am not retrieving data as expected.
I may need to use Promises to wait for all calls to be made before proceeding, however I don't really understand how I would incorporate that into the code I have.
I may be using multiple return statements incorrectly (which I am also fuzzy on).
My error message on the Firebase Realtime Database console is as follows:
#firebase/database: FIREBASE WARNING: Exception was thrown by user callback. Error: Registration token(s) provided to sendToDevice() must be a non-empty string or a non-empty array.
at FirebaseMessagingError.FirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:42:28)
at FirebaseMessagingError.PrefixedFirebaseError [as constructor] (/srv/node_modules/firebase-admin/lib/utils/error.js:88:28)
at new FirebaseMessagingError (/srv/node_modules/firebase-admin/lib/utils/error.js:254:16)
at Messaging.validateRegistrationTokensType (/srv/node_modules/firebase-admin/lib/messaging/messaging.js:729:19)
at Messaging.sendToDevice (/srv/node_modules/firebase-admin/lib/messaging/messaging.js:328:14)
at admin.database.ref.once.snapshot (/srv/index.js:84:12)
at onceCallback (/srv/node_modules/#firebase/database/dist/index.node.cjs.js:4933:51)
at /srv/node_modules/#firebase/database/dist/index.node.cjs.js:4549:22
at exceptionGuard (/srv/node_modules/#firebase/database/dist/index.node.cjs.js:698:9)
at EventList.raise (/srv/node_modules/#firebase/database/dist/index.node.cjs.js:9684:17)
The above indicates I am not retrieving data either at all or by the time the return is called. My JavaScript function code is:
'use strict';
const admin = require('firebase-admin');
admin.initializeApp();
exports.pushNotification = functions.database.ref('/Chat Messages/{chatId}/{pushID}').onCreate((snapshot, context) => {
const valueObject = snapshot.after.val();
return admin.database().ref(`/Chat Basics/${valueObject.chatKey}/Chat Users`).once('value', statusSnapshot => {
var index = 0;
var totalkeys = statusSnapshot.numChildren();
var msgIDs = [];
statusSnapshot.forEach(msg=>{
msgIDs.push(msg.key.toString());
if(index === totalkeys - 1){
const payload = {
notification : {
title: valueObject.userName,
body: valueObject.message,
sound: "default"
}
}
sendNotificationPayload(valueObject.uid, payload);
}
index++;
});
});
});
function sendNotificationPayload(uid, payload){
admin.database()
.ref(`/User Token Data/${uid}`)
.once('value', snapshot=> {
var tokens = [];
//if(!snapshot.exists())return;
snapshot.forEach(item =>{
tokens.push(item.val())
});
admin.messaging()
.sendToDevice(tokens, payload)
.then(res => {
return console.log('Notification sent')
})
.catch(err => {
return console.log('Error in sending notification = '+err)
});
});
}
This code is mostly inspired by what was said to be a working example here from another Stack Overflow question here. I have successfully tested sending a notification to a single device by manually copying a device token into my function, so the function does run to completion. My Java code seems to be irrelevant to the problem, so I have not added it (please ask in the comments if you would like it added for further context).
What I Have Tried:
I have tried implementing promises into my code, but I don't think I was doing it properly. My main reference for this was here. I have also looked at the documentation for literally everything related to this topic, however my knowledge of JS is not sufficient to really apply barebones examples to my code.
My Firebase Realtime Database Nodes:
#1: Loop through chat members to collect user IDs:
"Chat Basics" : {
"1607801501690_TQY41wIfArhHDxEisyupZxwyHya2" : {
"Chat Users" : {
"JXrclZuu1aOwEpCe6KW8vSDea9h2" : true,
"TQY41wIfArhHDxEisyupZxwyHya2" : true
},
#2: Collect user tokens from collected IDs (ignore that tokens are matching):
"User Token Data" : {
"JXrclZuu1aOwEpCe6KW8vSDea9h2" : "duDR3KH3i3I:APA91bH_LCeslZlqL8akYw-LrM9Dv__nx4nU1TquCS0j6bGF1tlIARcheREuNdX1FheC92eelatBC8LO4t6gt8liRdFHV-NDuNLa13oHYxKgl3JBPPlrMo5rB5XhH7viTo4vfYOMftRi",
"TQY41wIfArhHDxEisyupZxwyHya2" : "duDR3KH3i3I:APA91bH_LCeslZlqL8akYw-LrM9Dv__nx4nU1TquCS0j6bGF1tlIARcheREuNdX1FheC92eelatBC8LO4t6gt8liRdFHV-NDuNLa13oHYxKgl3JBPPlrMo5rB5XhH7viTo4vfYOMftRi"
}
Conclusion:
Concrete examples would be much appreciated, especially since I am crunching right now. Thanks for your time and help!
Update:
After some more testing, it looks like the problem is definitely due to my lack of understanding of promises in two areas. Firstly, only one user is collected before the final return is called. Secondly, the final return is called before the 2nd forEach() loop can store snapshot data to an array.
For this code then, how may I modify (or rebuild) it so that it collects all keys before proceeding to retrieve token data from all keys - ultimately before returning the notification?
Just as with every question I post, I managed to figure out how to do it (tentatively) a few hours later. Below is a full example of how to send a notification to chat users based on a message sent (although it does not yet exclude the sender) to a given chat. The order of operations are as such:
User message is saved and triggers event. Relevant data the message contains are:
username, chat key, message
These are retrieved, with (username + message) as the (title + body) of the
notification respectively, and the chat key is used for user id reference.
Loop through chat user keys + collect.
Loop through array of chat user keys to collect array of device tokens.
Send notification when complete.
The code:
//Use firebase functions:log to see log
exports.pushNotification = functions.database.ref('/Chat Messages/{chatId}/{pushId}').onWrite((change, context) => {
const valueObject = change.after.val();
return admin.database().ref(`/Chat Basics/${valueObject.chatKey}/Chat Users`).once('value', statusSnapshot => {
var index = 0;
var totalkeys = statusSnapshot.numChildren();
var msgIDs = [];
statusSnapshot.forEach(msg=>{
msgIDs.push(msg.key.toString());
if(index === totalkeys - 1){
const payload = {
notification : {
title: valueObject.userName,
body: valueObject.message,
sound: "default"
}
}
let promises = [];
var tokens = [];
for(let i=0; i < msgIDs.length; i++){
let userId = msgIDs[i];
let promise = admin.database().ref(`/User Token Data/${userId}`).once('value', snapshot=> {
tokens.push(snapshot.val());
})
promises.push(promise);
}
return Promise.all(promises).then(() => {
return admin.messaging().sendToDevice(tokens, payload);
});
}
index++;
return false;
});
});
});

Parse.com - secure sending data with javascript SDK

I'm building right now simple game with Angular JS and Parse.com cloud as my database.
My goal is in the and of the game, to store user score inside Parse cloud.
But how can i do this securly, when anyone can get access to my Parse keys, becouse they are visible in my js file, and simply recreate Parse Object with some fake data, and then store it in my database ?
ACL's it's not the point in this particular case - right now i just turn of write acl before save, to prevent users from changing they scores before save.
In my game i don't have any Parse Users - i want to all peaople play my game, without logging in.
What do you think about idea to make 'fake' user like in first answer in this post ( becouse Anonymous anonymous can't be create in JS parse SDK ), and then track the session and the user ?
Is it even helpful in my case ?
Maybe i should make some check in Cloude Code - like comparison Cookies or local storage data before saving in Parse ( it will make cheating in game harder but still possible ) ?
Below i present my whole service to show you what is all about:
angular.module('Parsedb', [])
.provider('Parsedbmanager', function() {
this.$get = function($q, $http) {
// new Parse constructor
var ParseHighScore = Parse.Object.extend('ParseHighScore');
// create new obj
var parseHighScore = new ParseHighScore();
this.parseInit = function() {
Parse.initialize('myKey', 'myKey');
};
this.setParsedb = function(newScore) {
// set val
parseHighScore.set('HighScore', newScore);
// save score to cloud
parseHighScore.save(null, {
success: function (parseHighScore) {
// protect from change saved obj
var acl = new Parse.ACL();
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(false);
parseHighScore.setACL(acl);
return parseHighScore.save();
},
error: function (parseHighScore, error) {
console.log('Failed to create new object, with error code: ' + error.message);
}
});
};
this.getParsedb = function() {
// need q to get this asynch
var deferred = $q.defer();
var query = new Parse.Query(ParseHighScore);
query.limit(5);
query.descending("HighScore");
query.find({
success: function(results) {
console.log("Successfully retrieved " + results.length + " scores.");
// resolve, if you have results
deferred.resolve(results);
},
error: function(error) {
deferred.reject(error.message);
}
});
return deferred.promise;
};
return this;
};
});
If you let the user to write to db then there will always be a situation where user can change data .. i think all you can do is, to abstract it from user

Using Pointers for ios App & Javascript website

I have an iphone app created as a multiplication game and saves the following into a class called 'Results'.
PFObject *Results = [PFObject objectWithClassName:#"Result"];
[Results setObject:levelNumberLabel.text forKey:#"LevelNumber"];
[Results saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) etc.
I also have a class called 'currentUser' that stores the following:
PFObject *currentUser = [PFObject objectWithClassName:#"currentUser"];
[currentUser setObject:_firstnameLabel.text forKey:#"SFirstName"];
I want to run a javascript query that 'links' these two tables. I have a point
er set up called 'currentuser' in the currentUser class and a pointer called 'currentResult'.. These hold no value (undefined).
How am I able to merge these classes together? Will it be a case of linking it through the app or using javascript? I'm slightly confused. Here is my javascript code
var Show = Parse.Object.extend("Result");
var query = new Parse.Query(Show);
query.include('currentUser');
query.find({
success: function(shows) {
for(var i = 0; i < shows.length; i++) {
var show = results[i];
var surname = results.get('SSurname');
console.log(i);
var users = show.get('currentUser');
var username = users.get("SFirstName");
$(".success").append(username + " on " + surname + "<br/>");
Any questions/help please let me know.
Any help on the subject would be greatly appreciated
Parse already gives us a User class (PFUser). Consider starting with that one.
Next, create a class that represents the result of play. "Result" (singular) is an okay name for that. It can have string and number attributes as you see fit. (name those starting with lower case, e.g. "levelNumber"). To associate a result with a user, the Result table must also have a pointer attribute to the User, call it "user".
In Objective-C, when its time to save the result of play:
PFUser *user = [PFUser currentUser]; // this is the signed in PFUser
PFObject *result = [PFObject objectWithClassName:#"Result"];
result[#"user"] = user;
result[#"some_other_col_name"] = some_other_value;
[result saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
// ...
}];
In JS, to find the all of the results created by the play of some user:
// if this is called in a cloud function, the request.user will be
// the PFUser on the client that made the request
function resultsForUser(user) {
var query = new Parse.Query("Result");
query.equalTo("user" user);
return query.find().then(function(results) {
// results are the Result objects whose user is the given user
}, function (error) {
});
}

Discover meteor-Unsubscribe?

I am currently working on my own project, based on the discover meteor book.
I have subscriptions of my collection 'posts'.
I am using easy-search (a search package), and currently having some troubles.
I have easy search on a overlay called in with javascript.
When I search, it will always return the posts included in the subscriptions + search result as the result.
For example, if I'm in the post lists page, if I search for Chocolate, the result would be every posts in the post list page + chocolate keyword posts.
It goes the same for single post pages.
I was wondering if I could unsubscribe temporarily with a click event. In this case, would be the search button.
Don't use Meteor.publish for searching.
Create a Meteor.method on the server instead to find the search results.
Create a client-only (unmanaged) collection var results = new Mongo.Collection(null)
When you perform the search, remove all results results.remove({}) and then insert the results from the Meteor.method callback.
Then, to stop each search waiting until the next one completes (bad for autocomplete), you can look at calling the Meteor.method with wait: false eg.
Meteor.apply('mySearchMethod',[parameters], {wait: false}, function(err, res){});
To make this work, you need to call this.unblock() inside the search method.
Example Code
var searching = new ReactiveVar(false);
var currentSearch = "";
var results = new Mongo.Collection(null);
var search = function(searchText){
searchText = searchText.trim();
if (searchText === currentSearch){
// abort search if query wasn't different
return;
}
// clear results immediately (don't show expired results)
// NOTE: this can cause "flicker" as results are removed / re added
results.remove({});
if (searchText === ""){
return;
}
searching.set(true);
performSearch(searchText)
};
var performSearch = _.debounce(function(searchText){
currentSearch = searchText;
Meteor.apply('mySearchMethod', [searchText], {wait: false}, function(err, res){
if (err){
console.error(err);
}
if (currentSearrch !== searchText){
// query changed, results aren't relevant
return;
}
for (var i = 0; i < res.length; i++){
results.insert(res[i]);
}
searching.set(false);
})
}, 300);
Template.myTemplate.events({
'input #mySearchBox': function(e){
search($(e.currentTarget).val());
}
});
Template.myTemplate.heplers({
'searchResults': function(){
return results.find();
},
'showSpinner': function(){
return searching.get();
}
})
if (Meteor.isServer){
Meteor.methods({
'mySearchMethod': function(searchText){
check(searchText, String);
this.unblock();
var searchExp = new RegExp(RexExp.escape(searchText), 'i');
return myCollection.find({myField: searchExp}).fetch();
}
});
}

Serial Promises and Response.Success in Cloud Code

I'm a little confused about where to place a response.success() when using serial Promises.
Here's the situation: I've got a cloud function that accepts an array of email addresses and the current user. The function does the following:
Finds the current user based upon it's user object id.
Iterates over the array of emails addresses
Find if there is an existing user for each given email address
If there is an existing user, we check to see if the existing user and the current user are friends
If they are not friends, it creates a friendship.
Now when I run this function without a response.success(), it does exactly what I expect it to and the friendships entries are created. But no matter where I place the response in the code, I get the resulting response.success message and none of the serialized promises execute.
Why the resulting success/failure matters: I'm executing this function from an iOS app and I'd like to properly handle the success or failure cases correctly on the iOS side.
Here is the cloud function:
Parse.Cloud.define("friendExistingUsers", function(request, response) {
// Get our parameters
var addresses = request.params.emailAddresses;
var userId = request.params.user;
// Query for our user
var userQuery = new Parse.Query("User");
userQuery.equalTo("objectId", userId)
userQuery.first().then(function(currentUser) {
// if we find the user, walk the addresses
var promise = Parse.Promise.as("success");
_.each(addresses, function(address) {
console.log(address);
// add a then to our promise to handle whether a relationship is
// being created.
promise = promise.then(function() {
// find if there is a user for that address
var emailQuery = new Parse.Query("User");
emailQuery.equalTo("email", address);
emailQuery.first().then(function(addressUser) {
if (typeof addressUser != 'undefined') {
// found one.
console.log(addressUser);
// figure out if our current user and this user are
// friends.
var friendQuery = new Parse.Query("FVFriendship");
friendQuery.equalTo("from", currentUser);
friendQuery.equalTo("to", addressUser);
friendQuery.first().then(function(relationship) {
if (typeof relationship != 'undefined') {
// if they are, we need to pass.
console.log("Found a relationship: " = relationship)
} else {
// They are not. Add the friendship
var Friendship = Parse.Object.extend("FVFriendship");
var friendship = new Friendship();
friendship.set("from", currentUser);
friendship.set("to", addressUser);
friendship.save().then(function(result) {
console.log("Created a friendship: " + result)
});
};
});
} else {
// we did not find a user for that address
console.log("No user for " + address);
};
});
});
});
console.log(promise);
return promise;
}).then(function() {
response.success("success");
});
});
Thanks in Advance. Let me know if there's anything else I can add.
Your .then callback function attached to promise should return a promise. Missing this is a common mistake when using promises.
Also Parse doesn't seem to show objects with console.log as browsers do, so I wrap them into JSON.stringify().

Categories