Parse Cloud Code Sending Push Notification Twice - javascript

I am trying to send a push notification using cloud code to targeted channels. The Object is called Prayers. When someone saves Prayers, it is supposed to send a push notification to certain channels, if the new data in Prayers was not made anonymously. Prayers has a key of 'Anonymous' in it that is boolean. So, I have cloud code set up like this, in an effort that if the boolean value is false, it sends, it, but if it is true, it won't send the push. The issue now is that it is sometimes sending the Push through 2 times on a non-anonymous post.
Parse.Cloud.afterSave("Prayers", function(request) {
var firstName = request.object.get('FirstName');
var lastName = request.object.get('LastName');
var userId = request.object.get('UserId');
var anonymous = request.object.get('Anonymous');
var anonymousString = anonymous.toString
var pushQuery = new Parse.Query(Parse.Installation);
pushQuery.equalTo('channels', userId);
if (anonymous == false) {
Parse.Push.send({
where: pushQuery, // Set our Installation query
data: {
alert: firstName + " " + lastName + " " + "just added a prayer request."
}
}, {
success: function() {
// Push was successful
},
error: function(error) {
throw "Got an error " + error.code + " : " + error.message;
}
});
}
});

At first glance there doesn't seem anything wrong with your function, but since you always try to send a push notification after a prayer has been saved, are you sure you're not saving the object twice? That could be the reason why the afterSave is invoked twice.
One of the things I also once ran into was, that I had 2 pieces of cloud code
First one would modify an object when I tried to save it.
Second one was that I would do a push after saving the object.
In my code where I modified the object during the save process, I saved the modified object, which resulted in my Parse.Cloud.afterSave being fired twice for the same object

Server Side
Update reg_id by UUID when device registration on server.
Delete by reg_id which have return a canonical_id in the response after send a push.
Periodically send fake push using dry_run and do the same things as 2.
Client Side
Send message_id within payload, and save it in sqlite DB. And device will know that it has received it or not.

Related

Good practice for indexedDB add then get

I have the code below that inserts a todo object in an indexedDB object store - and then gets a copy of the stored object (see further down) and this works fine.
I'm concerned that I am reusing a transaction that might be unsafe to use - since the transaction has already succeeded.
Should I create another transaction for the get - or is this unnecessary?
// this is only called after the 'tasks' object store has been opened
const todoIDB = requestIDB.result
const transaction = todoIDB.transaction(["tasks"], "readwrite")
const todoStore = transaction.objectStore("tasks")
const addRequest = todoStore.add({text:txt_val})
addRequest.addEventListener("success", ()=>{
console.log("Added " + "#" + addRequest.result + ": " + txt_val)
// should I add a new transaction, etc. here?
const getRequest = todoStore.get(addRequest.result)
getRequest.addEventListener("success", ()=>{
console.log("Found " + JSON.stringify(getRequest.result))
})
})
Here is some (valid) output (from Chrome):
Added #18: aaa
Found {"text":"aaa","id":18}
Added #19: bbb
Found {"text":"bbb","id":19}
Transactions can span multiple requests, so this is fine. (Of course, if the add request fails - e.g. the record already exists - then "success" won't fire the get request won't happen.)
And to clarify a point - when you're observing the "success" event, it's the request that has succeeded, not the transaction. A "complete" or "abort" event will fire at the transaction object when the overall transaction has finished, i.e. when all of the individual requests have succeeded or one has failed and caused the transaction to fail.

Check and Increment the Version Number of an IndexedDB.

I have an IndexedDB that is storing a large amount of dynamic data. (The static data is already cached by a Service Worker)
My problem is as this data is dynamic, I need the IndexedDB to be cleared and it to be restored each time the application is opened. For this, I need the version number to be incremented so that the onupgradeneeded event is fired. I can't think of any logical way to do this, and even using the following call in the onupgradeneeded event I get an undefined answer.
e.target.result.oldversion
My IndexedDB code is as follows, with the parameteres being:
Key - The name of the JSON object to store in the database.
Value - The JSON object itself.
function dbInit(key, value) {
// Open (or create) the database
var open = indexedDB.open("MyDatabase", 1);
console.log(value);
// Create the schema
open.onupgradeneeded = function(e) {
console.log("Old Version: " + e.target.result.oldversion); //Undefined
console.log("New Version: " + e.target.result.newversion); //Undefined
var db = open.result;
var store = db.createObjectStore("Inspections", {keyPath: "id", autoIncrement: true});
var index = store.createIndex(key, key);
};
open.onsuccess = function() {
// Start a new transaction
var db = open.result;
var tx = db.transaction("Inspections", "readwrite");
var store = tx.objectStore("Inspections");
var index = store.index(key);
store.add(value);
// Close the db when the transaction is done
tx.oncomplete = function() {
db.close();
};
}
}
As this method is called several times for several 'Key' objects, I will need to work out a way to increment this Version number once per opening of the page, and then move the 'add' call to outside of the onupgradeneeded method - but for the moment the priority is making sure it runs through once - incrementing the version number, firing the onupgradeneeded, deleting the current data, storing the new data.
Thanks in advance!
The oldVersion and newVersion properties (note the capitalization) are on the IDBVersionChangeEvent, not on the IDBDatabase. In other words, this:
console.log("Old Version: " + e.target.result.oldversion); //Undefined
console.log("New Version: " + e.target.result.newversion); //Undefined
should be:
console.log("Old Version: " + e.oldVersion);
console.log("New Version: " + e.newVersion);
Now that said... you're using the schema versioning in a somewhat atypical way. If you really want to start with a fresh database each time the page is opened, just delete before opening:
indexedDB.deleteDatabase("MyDatabase");
var open = indexedDB.open("MyDatabase", 1);
// An open/delete requests are always processed in the order they
// are made, so the open will wait for the delete to run.
Note that the queued operations (delete and open) would then be blocked if another tab was holding an open connection and didn't respond to a versionchange event sent out in response to the delete request. Maybe that's a good thing in your case - it would prevent two tabs from partying on the database simultaneously.
A more typical usage pattern would be to only change the version when the web app is upgraded and the database schema is different. If you did need to wipe the data across sessions you'd do that on open, rather than on upgrade, and use things like clear() on the object store. But now we're getting into the design of your app, which it sounds like you've got a good handle on.

How do you link GCM chrome push notifications and payload data?

Push notifications in Chrome via GCM are driving me crazy.
I've got everything up and running. I serve the push using my python server to GCM. A service worker displays the push notification fine.
To my knowledge, there is NO data passed with push events. Sounds like it's coming soon but not available yet.
So just before the push notification shows, I call my server to get extra data for the push notification. But I have no information on the push notification to send to my server to match and return relevant data.
Everything I can think of to match a notification and user data is purely speculative. The closest thing I can find is a timestamp object on the PushEvent{} that roughly matches the successful return of the GCM call for each user.
So how are other people handling custom payload data to display Chrome push notifications?
The PushEvent{} does not seem to have any ID associated with it. I know the user that the push is for because I've previously stored that information at the time of registration.
But once I receive a push, I have no idea of knowing what the push was for.
I would like to avoid:
Trying to match based on timestamp (since notifications displays are not guaranteed to be instant).
Trying to pull the 'latest' data for a user because in my case, there could be several notifications that are sent for different bits of data around the same time.
How are other sites like Whatsapp and Facebook linking custom payload data with a seemingly sterile event data as a result of a push notification?
How are you doing it? Any suggestions?
Here's what my receiver code looks like:
self.addEventListener('push', function(event) {
event.waitUntil(
fetch("https://oapi.co/o?f=getExtraPushData&uid=" + self.userID + "&t=" + self.userToken).then(function(response) {
if (response.status !== 200) {
console.log('Looks like there was a problem. Status Code: ' + response.status);
throw new Error();
}
return response.json().then(function(data) {
if (data.error || !data.notification) {
console.error('The API returned an error.', data.error);
throw new Error();
}
var title = data.notification.title;
var message = data.notification.message;
var icon = data.notification.icon;
return self.registration.showNotification(title, {
body: message,
icon: icon,
});
});
}).catch(function(err) {
console.error('Unable to retrieve data', err);
var title = 'An error occurred';
var message = 'We were unable to get the information for this push message';
var icon = "https://oapi.co/occurrences_secure/img/stepNotify_1.png";
var notificationTag = 'notification-error';
return self.registration.showNotification(title, {
body: message,
icon: icon,
tag: notificationTag
});
})
);
});
I understand your problem, and i've been fiddling with the same when i wanted to use chrome notification. You can use indexedDB to save ObjectStore and retrieve data in webServices.
IndexedDB is accessible to webservices. I am using it to store user information and when the user recieves a push notification i pass the stored access key to identify the user and pass him relevent information.
Here's matt gaunt's tutorial which says indexed db is accessible to web services:
http://www.html5rocks.com/en/tutorials/service-worker/introduction/
Here's a good indexedDB tutorial:
http://blog.vanamco.com/indexeddb-fundamentals-plus-a-indexeddb-example-tutorial/
Assuming you are still in the past. That is, sending only a push trigger to your browser with no payload it's now time to move on with time. You can now send payload in your push events. Since you seem familiar with GCM, it's ok to go with that though there is now the Web Push Protocol which is browser vendor independent.
Briefly, to make that work you need to encrypt your payload with specifications found here, in your server.
There is a node by google chrome and PHP implementations for that, that I know of.
You may check out the PHP Web Push here.
In the browser you would need to provide the subscription object now with the p256dh and auth on top of the endpoint as before.
You may check this out for more details.

What would cause "Request timed out" in parse.com cloud code count?

One of my cloud functions is timing out occasionally. It seems to have trouble with counting, although there are only around 700 objects in the class. I would appreciate any tips on how to debug this issue.
The cloud function works correctly most of the time.
Example error logged:
E2015-02-03T02:21:41.410Z] v199: Ran cloud function GetPlayerWorldLevelRank for user xl8YjQElLO with:
Input: {"levelID":60}
Failed with: PlayerWorldLevelRank first count error: Request timed out
Is there anything that looks odd in the code below? The time out error is usually thrown in the second count (query3), although sometimes it times out in the first count (query2).
Parse.Cloud.define("GetPlayerWorldLevelRank", function(request, response) {
var query = new Parse.Query("LevelRecords");
query.equalTo("owner", request.user);
query.equalTo("levelID", request.params.levelID);
query.first().then(function(levelRecord) {
if (levelRecord === undefined) {
response.success(null);
}
// if player has a record, work out his ranking
else {
var query2 = new Parse.Query("LevelRecords");
query2.equalTo("levelID", request.params.levelID);
query2.lessThan("timeSeconds", levelRecord.get("timeSeconds"));
query2.count({
success: function(countOne) {
var numPlayersRankedHigher = countOne;
var query3 = new Parse.Query("LevelRecords");
query3.equalTo("levelID", request.params.levelID);
query3.equalTo("timeSeconds", levelRecord.get("timeSeconds"));
query3.lessThan("bestTimeUpdatedAt", levelRecord.get("bestTimeUpdatedAt"));
query3.count({
success: function(countTwo) {
numPlayersRankedHigher += countTwo;
var playerRanking = numPlayersRankedHigher + 1;
levelRecord.set("rank", playerRanking);
// The SDK doesn't allow an object that has been changed to be serialized into a response.
// This would disable the check and allow you to return the modified object.
levelRecord.dirty = function() { return false; };
response.success(levelRecord);
},
error: function(error) {
response.error("PlayerWorldLevelRank second count error: " + error.message);
}
});
},
error: function(error) {
response.error("PlayerWorldLevelRank first count error: " + error.message);
}
});
}
});
});
I don't think the issue is in your code. Like the error message states: the request times out. That is, the Parse API doesn't respond within the period of the timeout or the network causes it to timeout. As soon as you do .count some API call is probably done, which then can't connect or times out.
Apparently more people have this issue: https://www.parse.com/questions/ios-test-connectivity-to-parse-and-timeout-question. It doesn't seem possible to increase the timeout, so the suggestion in this post states:
For that reason, I suggest setting a NSTimer prior to executing the
query, and invalidating it when the query returns. If the NSTimer
fires before being invalidated, ask the user if they want to keep
waiting for the results to come back, or show them a message
indicating that the request is taking a long time to complete. This
gives the user the chance to wait more if they know their current
network conditions are not ideal.
In case you are dealing with networks, and especially on the mobile platform, you need to prepare for network hickups. So like the post suggests: offer the option to user to try again.

Passing Query Parameters from HTML/JS App to Azure Server Script

I am currently working on an HTML/JS App using Windows Azure Mobile Service.
I have two tables, one storing information about an attraction [attractionTable(attractionId, address,...)] and one keeping track of which user likes which attraction [favoriteAttractionTable(attractionId, userId, fav(bool))].
If the client wants to have a list of his favorite attractions there should only be one request to the server. On the server side I edited the read script of the favoriteAttractionTable to this:
function read(query, user, request) {
var sql =
"FROM routesTable rt, favoriteAttractionTable ft " +
"WHERE rt.id = ft.routeId;";
mssql.query(sql, {
success: function(results) {
request.respond(statusCodes.OK, results);
}});
}
In my JavaScript Code I am using the following request
function loadFavoriteAttractions(){
favTable = client.getTable("favoriteAttractionTable");
var query = favTable.where({
fav: true,
userId : 0
}).read().done(function (results) {
console.log(results);
}, function (err) {
alert("Error: " + err);
});
}
I basically get the information of all attractions that any user has added to his favorites but I want to modify the var sql in a way to receive only the ones related to the appropriate userId.
query.getComponents();
shows queryString: '((fav eq true) and (userId eq 0))' in my server script.
But I am wondering how I can access this information?!
request.parameters.fav
is supposedly undefined.
Any advice is highly appreciated :)
If you want to access request parameters in the scripts, your best bet is to use the custom query string parameters, something like the code below:
function loadFavoriteAttractions(){
favTable = client.getTable("favoriteAttractionTable");
var query = favTable.read({
fav: true,
userId : 0
}).done(function (results) {
console.log(results);
}, function (err) {
alert("Error: " + err);
});
}
Those parameters will be accessed via the request.parameters object on the server side.
If you're interested, here's more details: when you use the where method, you're passing some template object which will be converted into an OData $filter expression that is passed to the server. The server will then parse that and convert it into the appropriate SQL WHERE clause to be sent to the database. So your original code will send a request similar to this one (spaces will be replaced with %20, but left them here for clarity):
GET .../tables/favoriteAttractionTable?$filter=((fav eq true) and (userId eq 0))
As you found out, you can access it via the getComponents function, but at that point you'll have to parse the expression yourself to retrieve the values of the comparisons. If you want to access some values passed by the client, as I mentioned the easiest way is to pass them as "normal" query-string parameters. In the code I sent, the request that will be sent will be similar to
GET .../tables/favoriteAttractionTable?fav=true&userId=0
And those values you can retrieve from the request.parameters directly (the type of the values will be strings, so if you want to treat them as numbers of Boolean values you'll need to do the appropriate conversion).

Categories

Resources