Parse Cloud Code callback not working - javascript

I am building an app with friends relations using Parse Cloud Code.
When Alice sends Bob a friend request, it is a notification of type 71.
When Bob answers he sends a notification of type 8.
On the server, when a type 8 notification is sent, first some friendship relations are proceeded. They are: removing from both users from each other "potential friend list" and adding to "friend list" instead.
Afterwards, the notification of type 71 should be changed to a type 1 notification.
For some reasons I've been struggling for 24h to make it work. I simply cant proceed those two functions one after the other: the second one is never executed. Here is my code
Parse.Cloud.afterSave("Notification", function(request, response) {
Parse.Cloud.useMasterKey();
var downQuery = new Parse.Query("Notification");
var namnamA = request.object.get('nameA');
var namnamB = request.object.get('nameB');
var tytype = request.object.get('type');
var alice = Parse.User.current();
if (tytype === 8){
var bobQuery = new Parse.Query(Parse.User);
bobQuery.equalTo("username", namnamB);
bobQuery.first().then(function(bob) {
var alicesRelation = alice.relation("friendsList");
var alicesPotRelation = alice.relation("potFriendsList");
var bobsRelation = bob.relation("friendsList");
var bobsPotRelation = bob.relation("potFriendsList");
alicesPotRelation.remove(bob);
alicesRelation.add(bob);
bobsPotRelation.remove(alice);
bobsRelation.add(alice);
return Parse.Object.saveAll([alice, bob]);
}).then(function() {
downQuery.equalTo('nameA', namnamB);
downQuery.equalTo('nameB', namnamA);
downQuery.equalTo('type', 71);
return downQuery.find();
}).then(function(notizz) {
notizz.set('type', 1);
return Parse.Object.saveAll([notizz]);
}).then(function() {
console.log("success " + arguments);
response.success(arguments);
}), function(error) {
console.log("error " + error.message);
response.error(error);
}
}
});
Any help would greatly improve the life expectancy of my computer. Thank you.

aftersave does not have a response, only a request.
https://parse.com/docs/js/guide
Therefore, you do not need to (and should not) be using a response.
Further, when you call return, it exits the function

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;
});
});
});

NodeJS Interactive Twitter Bot Issues

Hello all I am trying to create an interactive Twitter bot that can fetch and post tweets at the users demand. Here is the code I have written thus far...
console.log("The bot is starting...");
var Twit = require('twit');
var config = require('./config')
var prompt = require('prompt');
prompt.start()
var T = new Twit(config);
console.log("Bot is ready to roll!");
var tweet_terms = "";
var tweet_count = 0;
var tweet_command = 0;
console.log("Choose a command...\n1. Get tweets \n2. Post tweet");
prompt.get(['command'], function(err, result) {
tweet_command = result.command
if (tweet_command == 1) {
console.log("You've chosen to get tweets.");
console.log("Enter in terms you want to search for seperated by commas, \
\nand also enter in the amount of tweets you want to receive back.");
prompt.get(['terms', 'count'], function(err, result) {
tweet_terms = result.terms;
tweet_count = result.count;
});
}
});
var params = {
q: tweet_terms,
count: tweet_count
}
T.get('search/tweets', params, gotData);
function gotData(err, data, response) {
var tweets = data.statuses;
for (var i = 0; i < tweets.length; i++) {
console.log(tweets[i].text);
}
}
I am trying to ask the user for input on what terms to search and how many tweets to gather. However my program is stopping before even the user input is prompted. Here is how the program is executing..
The bot is starting...
Bot is ready to roll!
Choose a command...
1. Get tweets
2. Post tweet
prompt: command: C:\Users\Kevin\Desktop\MERN Tutorials\Twit Twitter Bot\bot.js:42
for (var i = 0; i < tweets.length; i++) {
It looks like my gotData function is causing the issue but I don't understand exactly why my program is executing in this fashion.. My prompt isn't even allowing for user input.
TypeError: Cannot read property 'length' of undefined at gotData (C:\Users\X\Desktop\MERN Tutorials\Twit Twitter Bot\bot.js:42:31)
I do not understand why this function is even being called before the user input is handled.. I am new to NodeJS and am very confused why it is acting this way.
Any help would be much appreciated, thanks.
This line:
T.get('search/tweets', params, gotData);
is being called immediately after the application runs. After it's complete, you run a bunch of console.log() that appears to be providing the responses to the prompt. You wouldn't want to run this until after the user has input their choices (otherwise how could you know the params?).
Move the get call inside the callback of your last prompt:
prompt.get(['command'], function(err, result) {
tweet_command = result.command
if (tweet_command == 1) {
console.log("You've chosen to get tweets.");
console.log("Enter in terms you want to search for seperated by commas, \
\nand also enter in the amount of tweets you want to receive back.");
prompt.get(['terms', 'count'], function(err, result) {
tweet_terms = result.terms;
tweet_count = result.count;
T.get('search/tweets', params, gotData);
// ^ here!
});
} else {
// post a tweet code goes here
}
});
Now, while this works, it's not particularly flexible. You could probably rewrite this whole thing a little cleaner so that you can retrieve all the inputs from the user and then pass them as params to a single handler function.

Parse send notification when object modified

I am trying to send a Push Notification through Parse Cloud Code when a certain object has been modified - "dirty"
I think I am almost there, but received an error because I believe am creating a new user instead of querying for one.
Parse.Cloud.beforeSave("Fact", function(request, response) {
var dirtyKeys = request.object.dirtyKeys();
for (var i = 0; i < dirtyKeys.length; ++i) {
var dirtyKey = dirtyKeys[i];
if (dirtyKey === "isValid") {
//send push
// Creates a pointer to _User with object id of userId
var targetUser = new Parse.User();
// targetUser.id = userId;
targetUser.id = request.object.userID;
var query = new Parse.Query(Parse.Installation);
query.equalTo('user', targetUser);
Parse.Push.send({
where: query,
data: {
alert: "Your Fact was approved :)"
}
});
return;
}
}
response.success();
});
I found this post related to my problem. My question now is how to integrate the user query in my beforeSave block. Ideally I would create another function for the user query and place that in my beforeSave block.
**5/14 Update
I took #toddg's advice and fixed the before save. Here is a clearer picture of what I am trying to do and the new error.
A couple points (as #Subash noted in the comments) before I get into the code:
Parse.Push.send is an async operation, so you'll want to ensure you call response.success() after your push send completes. I'm going to deal with this using Promises, as I think they are more flexible than callbacks. If you're not familiar, read about them here
The return in your if statement will likely prevent the response.success() from being called.
Here's my recommended way of doing it:
Parse.Cloud.beforeSave("Fact", function(request, response) {
// Keep track of whether we need to send the push notification
var shouldPushBeSent = false;
var dirtyKeys = request.object.dirtyKeys();
for (var i = 0; i < dirtyKeys.length; ++i) {
var dirtyKey = dirtyKeys[i];
if (dirtyKey === "isValid") {
shouldPushBeSent = true;
}
}
if (shouldPushBeSent) {
//send push
// Creates a pointer to _User with object id of userId
var targetUser = new Parse.User();
// targetUser.id = userId;
targetUser.id = request.object.userId;
var query = new Parse.Query(Parse.Installation);
// We want to pass the User object to the query rather than the UserId
query.equalTo('user', targetUser);
Parse.Push.send({
where: query, // Set our Installation query
data: {
alert: "Your fact was approved"
}
}).then(function(){
// Now we know the push notification was successfully sent
response.success();
}, function(error){
// There was an error sending the push notification
response.error("We had an error sending push: " + error);
});
} else {
// We don't need to send the push notification.
response.success();
}
});
By the way, I'm assuming that you have a column on your Installation class that tracks which user is associated with each Installation.

Failure to send more than 9 emails through Mandril & Parse-Cloud-Code

I've been having a problem with sending email templates with mandrill. The set up that I have right now is javascript on a parse server which is setup with a background job to send emails once a day to each of the users using mandrill's api.
I came across an issue when sending large amount of emails, for some reason I can’t get mandrill to send any more than 9 email templates at a time, with over 400 needing to be sent. This shouldn't be a problem for I've been sending emails through this account for some time and have good reputation.
I tried putting a delay in the email, and changing options in the sending of templates, such as making them send async. But to no avail only 9 at maximum are to be sent.
exports.FunctionSentDripEmailTemplateFour = function (user,TempateNum,isLastEmail,status,testing){
var mandrillKey = 'my-key';
var Mandrill = require('mandrill');
if(testing){ // send the email to me if testing
EmailParams.message.to[0].email = 'test#email.com';
EmailParams.message.merge_vars[0].rcpt = 'test#email.com';
}else{
EmailParams.message.to[0].email = user.get('email');
EmailParams.message.merge_vars[0].rcpt = user.get('email');
}
EmailParams.message.subject="Subject";
EmailParams.template_name = "Template name";
EmailParams.template_content[0].name = "Template content name";
//console.log("T: " + TempateNum + " M: " + EmailParams.message.to[0].email);
Mandrill.initialize(mandrillKey);
Mandrill.sendTemplate(EmailParams, function(res) {
console.log(res);
}, function(err) {
console.log(err);
});
if(isLastEmail == true){
//(finishes the parse job function)
if(testing){
status.success("Test Email Job completed successfully.");
}else{
status.success("Email Job completed successfully.");
}
}
return new Parse.Promise();
}
Also, I only want these emails to be sent personally, I don't want to use carbon copies for I am using this elsewhere with making changes to each and every email.
Do you have any ideas? It would mean a lot to me.
Your function is performing an asynchronous network request (Mandrill.sendTemplate()) but it is immediately returning without waiting for this call to complete.
It looks like you're halfway there towards making this function perform as expected. You should initialize the Parse.Promise first, and return that promise object at the end of the function. This promise, in turn, should be resolved when the Mandrill code finishes.
exports.FunctionSentDripEmailTemplateFour = function (user,TempateNum,isLastEmail,status,testing){
var mandrillKey = 'my-key';
var Mandrill = require('mandrill');
if(testing){ // send the email to me if testing
EmailParams.message.to[0].email = 'test#email.com';
EmailParams.message.merge_vars[0].rcpt = 'test#email.com';
}else{
EmailParams.message.to[0].email = user.get('email');
EmailParams.message.merge_vars[0].rcpt = user.get('email');
}
EmailParams.message.subject="Subject";
EmailParams.template_name = "Template name";
EmailParams.template_content[0].name = "Template content name";
var promise = new Parse.Promise()
//console.log("T: " + TempateNum + " M: " + EmailParams.message.to[0].email);
Mandrill.initialize(mandrillKey);
Mandrill.sendTemplate(EmailParams, function(res) {
console.log(res);
promise.resolve(res);
}, function(err) {
console.log(err);
promise.reject(err);
});
// commented out isLastEmail block - I am not sure this is the right place for that functionality
return promise;
}
You will now be able to do the following:
FunctionSentDripEmailTemplateFour(user,templateNum,isLastEmail,status,testing).then(function(result) {
status.success("One email sent successfully");
}, function(err) {
status.error("Failed to send email");
});
Rewriting this code to send more than one email is left as an exercise to the reader. You should be able to follow Parse's Promises guide to rewrite this into a set of parallel promises.

Strophe.js client connecting to server, disconnect/timeout

I have a little JavaScript XMPP client, written with Strophe, that connects to a server hosted on hosted.im. I think hosted.im uses ejabberd on their backend.
I establish the connection using
Strophe.Connection(myBoshService), and am able to send chat messages back and forth. However, after a certain time, it seems, there is an automatic disconnect if there is no activity.
Now, my question is, what would be a good way to keep the session active, so that it does not disconnect. Disconnect time seems to be very short, about 60 seconds or so.
Should I send some kind of activity back and forth to keep it open? Or, which seems simpler to me, should I somehow change the timout of the session. If so, where can I change this? Is this a server-setting, irregardless of the Strophe.Connection object, or can I set the timeout when initializing Strophe.Connection?
Thanks for any and all help.
Best regards,
Chris
Edit: Here is the code I use for connecting:
I manage the connection through a global variable Hello (yes, name is awkward, I took it from an example):
var Hello = {
connection: null,
start_time: null,
partner: {
jid: null,
name: null
},
log: function (msg) {
$('#log').append("<p>" + msg + "</p>");
},
send_ping: function (to) {
var ping = $iq({
to: to,
type: "get",
id: "ping1"}).c("ping", {xmlns: "urn:xmpp:ping"});
Hello.log("Sending ping to " + to + ".");
console.log("Sending ping to " + to + ".");
Hello.start_time = (new Date()).getTime();
Hello.connection.send(ping);
},
handle_pong: function (iq) {
var elapsed = (new Date()).getTime() - Hello.start_time;
Hello.log("Received pong from server in " + elapsed + "ms.");
console.log('Received pong from server in " + elapsed + "ms.');
$('#login').hide();
$('#chat').show();
//window.location = "chat.html";
//Hello.connection.disconnect();
return true;
},
//"<active xmlns="http://jabber.org/protocol/chatstates"/><body xmlns="http://jabber.org/protocol/httpbind">tuiuyi</body>"
displayIncomingText: function (text) {
var body = $(text).find("xml > body");
if (body.length === 0)
{
body = $(text).find('body');
if (body.length > 0)
{
body = body.text();
$('#chattext').append("<p>"+ body + "</p>");
}
else
{
body = null;
}
}
return true;
},
readRoster: function (iq) {
$(iq).find('item').each(function () {
var jid = $(this).attr('jid');
var name = $(this).attr('name') || jid;
Hello.partner.name = name;
Hello.partner.jid = jid;
});
return true;
}
};
The main relevant objects here are Hello.connect and Hello.partner, which stores the jid of the only person on the accounts roster, as this is a one to one chat.
Then, in $(document).ready, I bind two buttons to connect and send messages respectively:
$(document).ready(function () {
$('#chat').hide();
$('#chatSend').bind('click', function () {
Hello.connection.send(
$msg(
{to : Hello.partner.jid, type : 'chat'}
).c('body').t($('#chattextinput').val())
);
$('#chattext').append("<p align='right'>" + $('#chattextinput').val() + "</p>");
});
$('#SignIn').bind('click', function () {
$(document).trigger('connect', {
jid: $('#eMail').val(), password: $('#password_f').val()
}
);
});
});
Clicking the sign-in button triggers the event "connect":
$(document).bind('connect', function (ev, data) {
console.log('connect fired');
var conn = new Strophe.Connection("http://bosh.metajack.im:5280/xmpp-httpbind");
conn.connect(data.jid, data.password, function (status) {
console.log('callback being done');
if (status === Strophe.Status.CONNECTED) {
alert('connected!');
$(document).trigger('connected');
alert('Connected successfully');
} else if (status === Strophe.Status.DISCONNECTED) {
$(document).trigger('disconnected');
}
else
{
Hello.log("error");
console.log('error');
}
});
Hello.connection = conn;
});
This creates the Strophe.Connection and stores it in Hello.connection. Also, it sets the callback function of the connection object. This code is taken straight from an example in a Strophe.js book. Anyway, the callback checks the status of the connection, and if status === Strophe.Status.DISCONNECTED, triggers "disconnected", which only does this:
$(document).bind('disconnected', function () {
Hello.log("Connection terminated.");
console.log('Connection terminated.');
// remove dead connection object
Hello.connection = null;
});
Anyway, what is happening is that, for some reason, in the callback set with conn.connect, after a short time, the status evaluates to Strophe.Status.DISCONNECTED, and I am not sure why, unless somewhere, either in the server or in the connection object, there is a timeout specified which seems to be ca. 60 seconds.
As to a log of the stanzas going back and forth, I guess I would need to quickly write a handler to see all incoming stanzas, or is it possible to see a log of all stanzas between the client and server in ejabberd?
For the sake of other people who come upon this and have a similar problem, the solution in this case was that the servers at hosted.im send a ping request every 60 seconds to check if the client is still online.
This ping request looks like this:
<iq from="testserver.p1.im" to="chris#testserver.p1.im/23064809721410433741569348" id="164323654" type="get"> <ping xmlns="urn:xmpp:ping"></ping> </iq>
What is needed, of course, is to form a response, which will look something like this:
<iq from="chris#testerver.p1.im" to="testserver.p1.im" id="164323654" type="result" xmlns="jabber:client"><ping xmlns="urn:xmpp:ping"/></iq>
Note the "to"-attribute. I omitted it at the beginning as I was under the assumption a message sent with no to-attribute is automatically assumed to be a client->server message. Not in this case however. Not sure if this is the case in general, or whether it is an oddity of servers at hosted.im.
Thanks to everyone for their comments and suggestions!
Best regards,
Chris

Categories