App Store Server Notifications and Firebase Cloud Functions - ios

When I explored different options for my lockdown-development project, I considered using App Store Server Notifications to inform my Firebase data base about subscriptions purchased in the App. Cloud Functions were supposed to receive the notification JSONs from Apple, interpret them, update the data base, and to respond to Apple. Apple and Google provide a lot of information about App Store Server Notifications and Cloud Functions, but linking them together can still be time consuming. Both companies describe well how to set their services up, and you will find an example of my code below. I ended up not using it, and Google migrated Cloud Functions to Node.js 10 in the meantime, but my code might still be useful to somebody:
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access the Firebase Realtime Database.
const admin = require('firebase-admin');
admin.initializeApp();
// The Express Functions to create an Express application.
const express = require('express');
const app = express();
// Add a generic JSON and URL-encoded parser as top-level middleware.
const bodyParser = require('body-parser');
// Parse application/x-www-form-urlencoded.
app.use(bodyParser.urlencoded({ extended: false }));
// Parse application/json.
app.use(bodyParser.json());
// Empty function for Timeout
function wait(ms) {
var d = new Date();
var d2 = null;
do { d2 = new Date(); }
while(d2-d < ms);
}
// Parse JSON and return relevant notification data.
function parseJSON(request) {
// returnValue[0] = environment
// returnValue[1] = notification_type
// returnValue[2] = original_transaction_id
// returnValue[3] = transaction_id
// returnValue[4] = latest_expired_receipt
// returnValue[5] = purchase_date_ms
// returnValue[6] = cancellation_date_ms
// returnValue[7] = expires_date
// returnValue[8] = grace_period_expires_date_ms
// returnValue[9] = product_id
// Carefully unwrap JSON
if(request) {
try {
// Declare local variables
const obj = JSON.parse(request.rawBody);
var original_transaction_id = 'Empty';
var transaction_id = 'Empty';
var purchase_date_ms = 'Empty';
var expires_date = 'Empty';
var grace_period_expires_date_ms = 'Empty';
var product_id = 'Empty';
// Unwrap variables in responseBody
var environment = (obj.environment) ? obj.environment : 'Empty';
var notification_type = (obj.notification_type) ? obj.notification_type : 'Empty';
var latest_expired_receipt = (obj.latest_expired_receipt) ? obj.latest_expired_receipt : 'Empty';
var cancellation_date_ms = (obj.cancellation_date_ms) ? obj.cancellation_date_ms : 'Empty';
// Make sure latest_receipt_info is there before unwrapping
if (obj.latest_receipt_info) {
original_transaction_id = (obj.latest_receipt_info.original_transaction_id) ? obj.latest_receipt_info.original_transaction_id : 'Empty';
transaction_id = (obj.latest_receipt_info.transaction_id) ? obj.latest_receipt_info.transaction_id : 'Empty';
purchase_date_ms = (obj.latest_receipt_info.purchase_date_ms) ? obj.latest_receipt_info.purchase_date_ms : 'Empty';
expires_date = (obj.latest_receipt_info.expires_date) ? obj.latest_receipt_info.expires_date : 'Empty';
product_id = (obj.latest_receipt_info.product_id) ? obj.latest_receipt_info.product_id : 'Empty';
}
// Make sure pending_renewal_info is there before unwrapping
if (obj.pending_renewal_info) {
grace = (obj.pending_renewal_info.grace_period_expires_date_ms) ? obj.pending_renewal_info.grace_period_expires_date_ms : 'Empty';
}
return returnValue = [environment, notification_type, original_transaction_id, transaction_id, latest_expired_receipt, purchase_date_ms, cancellation_date_ms, expires_date, grace_period_expires_date_ms, product_id];
} catch (error) {
throw (error);
}
}
return returnValue = ['Empty', 'Empty', 'Empty', 'Empty', 'Empty', 'Empty', 'Empty', 'Empty', 'Empty', 'Empty'];
}
// Function passes request to app
exports.iapStatusUpdate = functions.https.onRequest(async (request, response) => {
// Constants
const userID = [];
const currentDate = admin.firestore.FieldValue.serverTimestamp();
const collectionUsers = 'myData';
const collectionNotifications = 'myNotifications';
// Parse JSON and get relevant notification data.
try {
var parsedJSON = parseJSON(request);
} catch (error) {
// The server cannot or will not process the request due to an apparent client error.
response.status(400).send(error);
}
// Save relevant notification data.
try {
const writeResult = await admin.firestore().collection(collectionNotifications).add( {"environment": parsedJSON[0],
"notification_type": parsedJSON[1],
"original_transaction_id": parsedJSON[2],
"transaction_id": parsedJSON[3],
"latest_expired_receipt": parsedJSON[4],
"purchase_date_ms": parsedJSON[5],
"cancellation_date_ms": parsedJSON[6],
"expires_date": parsedJSON[7],
"grace_period_expires_date_ms": parsedJSON[8],
"product_id": parsedJSON[9],
"date": currentDate});
} catch (error) {
// A generic error message (Write failed)
response.status(500).send(error);
}
// Query document from database
try {
// In the case of an initial buy, pause execution for 3 seconds to allow Firebase to update record first
if (parsedJSON[1].includes("INITIAL_BUY")) {
wait(3000);
}
// Query document from database and write document id in userID array
const snapshot = await admin.firestore().collection(collectionUsers).where("original_transaction_id", '==', parsedJSON[2]).get();
snapshot.docs.map(doc => userID.push(doc.id));
} catch (error) {
// This failure does not necessarily mean that a real error occurred
// INITIAL_BUY: The App Store notification could have arrived before Firebase saved the original_transaction_id
// In all other cases: The original_transaction_id should be known and a user with this id should exist
if (parsedJSON[1].includes("INITIAL_BUY")) {
response.status(200).send('OK');
} else {
response.status(404).send(error);
}
}
// Update database
try {
// If the notification type is cancel, downgrade user from premium to free
if (parsedJSON[1].includes("CANCEL")) {
const writeEvent = await admin.firestore().collection(collectionUsers).doc(userID[0]).set( {"canceled": true,
"cancellation_date_ms": parsedJSON[6],
"premium": false}, { merge: true });
} else if (parsedJSON[1].includes("INITIAL_BUY")) {
const writeEvent = await admin.firestore().collection(collectionUsers).doc(userID[0]).set( {"confirmedPurchase": true,
"confirmedPurchaseOn": parsedJSON[5],
"purchase_date_ms": parsedJSON[5],
"expires_date": parsedJSON[7],
"premium": true}, { merge: true });
} else if (parsedJSON[1].includes("INTERACTIVE_RENEWAL") || parsedJSON[1].includes("DID_RECOVER") ) {
const writeEvent = await admin.firestore().collection(collectionUsers).doc(userID[0]).set( {"renewedPurchaseOn": parsedJSON[5],
"billingRetryPeriod": true,
"expires_date": parsedJSON[7],
"premium": true}, { merge: true });
} else if (parsedJSON[1].includes("DID_FAIL_TO_RENEW")) {
const writeEvent = await admin.firestore().collection(collectionUsers).doc(userID[0]).set( {"billingRetryPeriod": true,
"grace_period_expires_date_ms": returnValue[8],
"premium": true}, { merge: true });
}
} catch (error) {
// A generic error message, given when an unexpected condition was encountered and no more specific message is suitable.
response.status(500).send(error);
}
// Send final response
response.status(200).send('OK');
});
Improvements and suggestions are most welcome.

Related

Broadcasting to Members of a Database Using Twilio Messaging Services

fine people of Stack Overflow.
I'm trying to solve a problem I'm having involving twilio functions, messaging services, and databases.
What I'm attempting to do is send a message to all members of a database at once.
My code is a mess, as Javascript isn't my native language and I'm rather new to twilio.
The problem I believe I'm having is with the async/await feature of javascript.
Here is my code so far:
// Boiler Plate Deta Code
const { Deta } = require("deta");
// Function to access database and get object of conta
async function FetchDB(db) {
let res = await db.fetch();
allItems = res.items;
// continue fetching until last is not seen
while (res.last){
res = await db.fetch({}, {last: res.last});
allItems = allItems.concat(res.items);
}
}
// Function to get total number of contacts.
async function ReturnNumberOfContacts(allItems) {
number_of_contacts = allItems.length;
}
// Function to send message to contact in database.
async function SendMessages(allItems, message) {
allItems.forEach(contact => {
let users_name = contact.name
client.messages
.create({
body: `Hey ${users_name}! ${message}`,
messagingServiceSid: messaging_service,
to: contact.key
})
});
}
// Function to submit response to broadcaster.
async function SuccessResponse(user_name, number_of_contacts) {
responseObject = {
"actions": [
{
"say": `${user_name}, your broadcast has successfully sent to ${number_of_contacts} contacts.`
},
{
"listen": true
}
]
}
}
// Main Function
exports.handler = async function(context, event, callback) {
// Placeholder for number of contacts
let number_of_contacts;
// Place holder for object from database of all contacts
let allItems;
// Placeholder for users message
let message;
// Placeholder for response to user
let responseObject;
//Twilio and Deta, Etc Const
const client = require('twilio')(context.ACCOUNT_SID, context.AUTH_TOKEN);
const deta = Deta(context.DETA_PROJECT_KEY);
const db = deta.Base("users2");
const messaging_service = context.MESSAGING_SERVICE;
// From Phone Number
const from = event.UserIdentifier;
// Parse memory
const memory = JSON.parse(event.Memory);
// Fetch all items from database and return total number of contacts.
// Update relavent variables
await FetchDB(db, allItems).then(ReturnNumberOfContacts(allItems));
// Figure out if message came from short circuit broadcast or normal
if (memory.triggered) {
message = memory.message;
} else {
message = memory.twilio.collected_data.broadcast_message.answers.message_input.answer;
}
// Check if verified and set name.
const current_user = await db.get(from);
// Get the current users name or set a default value
let user_name = current_user.name || "friend";
// Determine if user is an authorized broadcaster
if (from === context.BROADCAST_NUMBER) {
// Decide if the sending of a message should be cancelled.
if (message.toLowerCase() === "c" || message.toLowerCase() === "cancel") {
responseObject = {
"actions": [
{
"say": `${user_name}, you have canceled your request and no messages have been sent.`
},
{
"listen": false
}
]
}
// Return Callback and end task
callback(null, responseObject);
}
// Move forward with sending a message.
else {
// Send message to users in database and send success message to broadcaster.
await SendMessages(message, client, messaging_service)
.then(SuccessResponse(user_name, number_of_contacts))
return callback(null, responseObject);
}
// The user is not authorized so return this.
}
return callback(null, {
"actions": [
{
"say": "You are not authorized to broadcast."
},
{
"listen": false
}
]
})
};
So when the Fetch() function is triggered, I want the database to load a list of everyone and have twilio send them the desired message saved in the message variable. I have the code working so that I can read from the database and get the proper values, and send a single text message with the desired message, but the problem I'm having now is integrating it all together.
Thanks if anyone can point me in the right direction here.
Again, I'm new to javascript and more specifically asynchronous programming.
Twilio developer evangelist here.
The issue from the error says that allItems is undefined when you call ReturnNumberOfContacts.
I think the issue comes from trying to use allItems as a sort of global variable, same for number_of_contacts. It would be better for FetchDB to resolve with the list of items and ReturnNumberOfContacts to resolve with the number of the items.
You also have some arguments missing when you call SendMessages in your function. I've updated it to the point that I think it will work:
// Boiler Plate Deta Code
const { Deta } = require("deta");
// Function to access database and get object of conta
async function FetchDB(db) {
let res = await db.fetch();
let allItems = res.items;
// continue fetching until last is not seen
while (res.last) {
res = await db.fetch({}, { last: res.last });
allItems = allItems.concat(res.items);
}
return allItems;
}
// Function to send message to contact in database.
async function SendMessages(allItems, message, client, messagingService) {
return Promise.all(
allItems.map((contact) => {
let usersName = contact.name;
return client.messages.create({
body: `Hey ${usersName}! ${message}`,
messagingServiceSid: messagingService,
to: contact.key,
});
})
);
}
// Main Function
exports.handler = async function (context, event, callback) {
// Placeholder for users message
let message;
//Twilio and Deta, Etc Const
const client = require("twilio")(context.ACCOUNT_SID, context.AUTH_TOKEN);
const deta = Deta(context.DETA_PROJECT_KEY);
const db = deta.Base("users2");
const messagingService = context.MESSAGING_SERVICE;
// From Phone Number
const from = event.UserIdentifier;
// Parse memory
const memory = JSON.parse(event.Memory);
// Fetch all items from database and return total number of contacts.
// Update relavent variables
const allItems = await FetchDB(db);
const numberOfContacts = allItems.length;
// Figure out if message came from short circuit broadcast or normal
if (memory.triggered) {
message = memory.message;
} else {
message =
memory.twilio.collected_data.broadcast_message.answers.message_input
.answer;
}
// Check if verified and set name.
const currentUser = await db.get(from);
// Get the current users name or set a default value
let userName = currentUser.name || "friend";
// Determine if user is an authorized broadcaster
if (from === context.BROADCAST_NUMBER) {
// Decide if the sending of a message should be cancelled.
if (message.toLowerCase() === "c" || message.toLowerCase() === "cancel") {
// Return Callback and end task
callback(null, {
actions: [
{
say: `${userName}, you have canceled your request and no messages have been sent.`,
},
{
listen: false,
},
],
});
}
// Move forward with sending a message.
else {
// Send message to users in database and send success message to broadcaster.
await SendMessages(allItems, message, client, messagingService);
return callback(null, {
actions: [
{
say: `${userName}, your broadcast has successfully sent to ${numberOfContacts} contacts.`,
},
{
listen: true,
},
],
});
}
// The user is not authorized so return this.
}
return callback(null, {
actions: [
{
say: "You are not authorized to broadcast.",
},
{
listen: false,
},
],
});
};
What I did here was change FetchDB to only take the db as an argument, then to create a local allItems variable that collects all the contacts and then returns them.
async function FetchDB(db) {
let res = await db.fetch();
let allItems = res.items;
// continue fetching until last is not seen
while (res.last) {
res = await db.fetch({}, { last: res.last });
allItems = allItems.concat(res.items);
}
return allItems;
}
This is then called in the main body of the function to assign a local variable. I also replaced the ReturnNumberOfContacts function with a simple assignment.
const allItems = await FetchDB(db);
const numberOfContacts = allItems.length;
One thing you may want to consider is how many contacts you are trying to send messages to during this function. There are a few limits you need to be aware of.
Firstly, Function execution time is limited to 10 seconds so you need to make sure you can load and send all your messages within that amount of time if you want to use a Twilio Function for this.
Also, there are limits for the number of concurrent connections you can make to the Twilio API. That limit used to be 100 connections per account, but it may vary these days. When sending asynchronous API requests as you do in JavaScript, the platform will attempt to create as many connections to the API that it can in order to trigger all the requests asynchronously. If you have more than 100 contacts you are trying to send messages to here, that will quickly exhaust your available concurrent connections and you will receive 429 errors. You may choose to use a queue, like p-queue, to ensure your concurrent connections never get too high. The issue in this case is that it will then take longer to process the queue which brings me back to the original limit of 10 seconds of function execution.
So, I think the above code may work in theory now, but using it in practice may have other issues that you will need to consider.

Why aren't my messages sending using Twilio Messaging Service and Twilio Functions? [duplicate]

fine people of Stack Overflow.
I'm trying to solve a problem I'm having involving twilio functions, messaging services, and databases.
What I'm attempting to do is send a message to all members of a database at once.
My code is a mess, as Javascript isn't my native language and I'm rather new to twilio.
The problem I believe I'm having is with the async/await feature of javascript.
Here is my code so far:
// Boiler Plate Deta Code
const { Deta } = require("deta");
// Function to access database and get object of conta
async function FetchDB(db) {
let res = await db.fetch();
allItems = res.items;
// continue fetching until last is not seen
while (res.last){
res = await db.fetch({}, {last: res.last});
allItems = allItems.concat(res.items);
}
}
// Function to get total number of contacts.
async function ReturnNumberOfContacts(allItems) {
number_of_contacts = allItems.length;
}
// Function to send message to contact in database.
async function SendMessages(allItems, message) {
allItems.forEach(contact => {
let users_name = contact.name
client.messages
.create({
body: `Hey ${users_name}! ${message}`,
messagingServiceSid: messaging_service,
to: contact.key
})
});
}
// Function to submit response to broadcaster.
async function SuccessResponse(user_name, number_of_contacts) {
responseObject = {
"actions": [
{
"say": `${user_name}, your broadcast has successfully sent to ${number_of_contacts} contacts.`
},
{
"listen": true
}
]
}
}
// Main Function
exports.handler = async function(context, event, callback) {
// Placeholder for number of contacts
let number_of_contacts;
// Place holder for object from database of all contacts
let allItems;
// Placeholder for users message
let message;
// Placeholder for response to user
let responseObject;
//Twilio and Deta, Etc Const
const client = require('twilio')(context.ACCOUNT_SID, context.AUTH_TOKEN);
const deta = Deta(context.DETA_PROJECT_KEY);
const db = deta.Base("users2");
const messaging_service = context.MESSAGING_SERVICE;
// From Phone Number
const from = event.UserIdentifier;
// Parse memory
const memory = JSON.parse(event.Memory);
// Fetch all items from database and return total number of contacts.
// Update relavent variables
await FetchDB(db, allItems).then(ReturnNumberOfContacts(allItems));
// Figure out if message came from short circuit broadcast or normal
if (memory.triggered) {
message = memory.message;
} else {
message = memory.twilio.collected_data.broadcast_message.answers.message_input.answer;
}
// Check if verified and set name.
const current_user = await db.get(from);
// Get the current users name or set a default value
let user_name = current_user.name || "friend";
// Determine if user is an authorized broadcaster
if (from === context.BROADCAST_NUMBER) {
// Decide if the sending of a message should be cancelled.
if (message.toLowerCase() === "c" || message.toLowerCase() === "cancel") {
responseObject = {
"actions": [
{
"say": `${user_name}, you have canceled your request and no messages have been sent.`
},
{
"listen": false
}
]
}
// Return Callback and end task
callback(null, responseObject);
}
// Move forward with sending a message.
else {
// Send message to users in database and send success message to broadcaster.
await SendMessages(message, client, messaging_service)
.then(SuccessResponse(user_name, number_of_contacts))
return callback(null, responseObject);
}
// The user is not authorized so return this.
}
return callback(null, {
"actions": [
{
"say": "You are not authorized to broadcast."
},
{
"listen": false
}
]
})
};
So when the Fetch() function is triggered, I want the database to load a list of everyone and have twilio send them the desired message saved in the message variable. I have the code working so that I can read from the database and get the proper values, and send a single text message with the desired message, but the problem I'm having now is integrating it all together.
Thanks if anyone can point me in the right direction here.
Again, I'm new to javascript and more specifically asynchronous programming.
Twilio developer evangelist here.
The issue from the error says that allItems is undefined when you call ReturnNumberOfContacts.
I think the issue comes from trying to use allItems as a sort of global variable, same for number_of_contacts. It would be better for FetchDB to resolve with the list of items and ReturnNumberOfContacts to resolve with the number of the items.
You also have some arguments missing when you call SendMessages in your function. I've updated it to the point that I think it will work:
// Boiler Plate Deta Code
const { Deta } = require("deta");
// Function to access database and get object of conta
async function FetchDB(db) {
let res = await db.fetch();
let allItems = res.items;
// continue fetching until last is not seen
while (res.last) {
res = await db.fetch({}, { last: res.last });
allItems = allItems.concat(res.items);
}
return allItems;
}
// Function to send message to contact in database.
async function SendMessages(allItems, message, client, messagingService) {
return Promise.all(
allItems.map((contact) => {
let usersName = contact.name;
return client.messages.create({
body: `Hey ${usersName}! ${message}`,
messagingServiceSid: messagingService,
to: contact.key,
});
})
);
}
// Main Function
exports.handler = async function (context, event, callback) {
// Placeholder for users message
let message;
//Twilio and Deta, Etc Const
const client = require("twilio")(context.ACCOUNT_SID, context.AUTH_TOKEN);
const deta = Deta(context.DETA_PROJECT_KEY);
const db = deta.Base("users2");
const messagingService = context.MESSAGING_SERVICE;
// From Phone Number
const from = event.UserIdentifier;
// Parse memory
const memory = JSON.parse(event.Memory);
// Fetch all items from database and return total number of contacts.
// Update relavent variables
const allItems = await FetchDB(db);
const numberOfContacts = allItems.length;
// Figure out if message came from short circuit broadcast or normal
if (memory.triggered) {
message = memory.message;
} else {
message =
memory.twilio.collected_data.broadcast_message.answers.message_input
.answer;
}
// Check if verified and set name.
const currentUser = await db.get(from);
// Get the current users name or set a default value
let userName = currentUser.name || "friend";
// Determine if user is an authorized broadcaster
if (from === context.BROADCAST_NUMBER) {
// Decide if the sending of a message should be cancelled.
if (message.toLowerCase() === "c" || message.toLowerCase() === "cancel") {
// Return Callback and end task
callback(null, {
actions: [
{
say: `${userName}, you have canceled your request and no messages have been sent.`,
},
{
listen: false,
},
],
});
}
// Move forward with sending a message.
else {
// Send message to users in database and send success message to broadcaster.
await SendMessages(allItems, message, client, messagingService);
return callback(null, {
actions: [
{
say: `${userName}, your broadcast has successfully sent to ${numberOfContacts} contacts.`,
},
{
listen: true,
},
],
});
}
// The user is not authorized so return this.
}
return callback(null, {
actions: [
{
say: "You are not authorized to broadcast.",
},
{
listen: false,
},
],
});
};
What I did here was change FetchDB to only take the db as an argument, then to create a local allItems variable that collects all the contacts and then returns them.
async function FetchDB(db) {
let res = await db.fetch();
let allItems = res.items;
// continue fetching until last is not seen
while (res.last) {
res = await db.fetch({}, { last: res.last });
allItems = allItems.concat(res.items);
}
return allItems;
}
This is then called in the main body of the function to assign a local variable. I also replaced the ReturnNumberOfContacts function with a simple assignment.
const allItems = await FetchDB(db);
const numberOfContacts = allItems.length;
One thing you may want to consider is how many contacts you are trying to send messages to during this function. There are a few limits you need to be aware of.
Firstly, Function execution time is limited to 10 seconds so you need to make sure you can load and send all your messages within that amount of time if you want to use a Twilio Function for this.
Also, there are limits for the number of concurrent connections you can make to the Twilio API. That limit used to be 100 connections per account, but it may vary these days. When sending asynchronous API requests as you do in JavaScript, the platform will attempt to create as many connections to the API that it can in order to trigger all the requests asynchronously. If you have more than 100 contacts you are trying to send messages to here, that will quickly exhaust your available concurrent connections and you will receive 429 errors. You may choose to use a queue, like p-queue, to ensure your concurrent connections never get too high. The issue in this case is that it will then take longer to process the queue which brings me back to the original limit of 10 seconds of function execution.
So, I think the above code may work in theory now, but using it in practice may have other issues that you will need to consider.

Push Notifications using Firebase/Google Cloud Functions

I am trying to send a notification when a number is written to _random. I am able to get the device token, and the cloud function works perfectly. However, I do not receive the notification on the simulator. I am using the notification token that is pushed to Firebase to test with. If anybody can help, it will be highly appreciated.
https://i.stack.imgur.com/OB94c.png
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
//Initial function call:
exports.makeRandomFigures = functions.https.onRequest((req, res) => {
    //create database ref
    var rootRef = admin.database().ref();
    var doc_count_temp = 0;
    var keys = [];
    var random_num = 0;
    //get document count
    rootRef.once('value', (snapshot) => {
        doc_count_temp = snapshot.numChildren();
        //real number of member. if delete _timeStamp then minus 2 not 3!
        var doc_count = doc_count_temp - 3;
        //get num array previous generated
        var xRef = rootRef.child("_usedFigures");
        xRef.once('value', function(snap) {
            snap.forEach(function(item) {
                var itemVal = item.val();
                keys.push(itemVal);
            });
            //get non-duplicated random number
            var is_equal = true;
            while (is_equal) {
                random_num = Math.floor((Math.random() * doc_count) + 1);
                is_equal = keys.includes(random_num);
            }
            //insert new random vaule to _usedFigures collection
            rootRef.child('_usedFigures').push(random_num);
            rootRef.child('_random').set(random_num);
        });
    });
    //send back response
    res.redirect(200);
});
exports.sendFigureNotification = functions.database.ref('_random').onWrite(event => {
const payload = {
notification: {
title: 'Title',
body: `Test`, //use _random to get figure at index key
badge: '1',
sound: 'default'
}
};
const options = {
priority: "high",
timeToLive: 60 * 60 * 24, //24 hours
content_available: true
};
const token = "cge0F9rUTLo:APA91bGNF3xXI-5uxrdj8BYqRPkxUPA5x9IQALtm3VEFJAdV2WQrQufNkzIclT5B671mBcvR6IDMbgSKyL7iG2jAuxRM3qR3MXhkNp1_utlXhCpE2VZqTw6Yw3d4iMMvHl1B-Cvik6NY";
console.log('Sending notifications');
return admin.messaging().sendToDevice(token, payload, options);
});
You can't get push notifications on the simulator.
To try some alternative ways check this link :
How can I test Apple Push Notification Service without an iPhone?

Run firebase cloud function when a key is a specific value

const functions = require('firebase-functions');
var IAPVerifier = require('iap_verifier');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.verifyReceipt = functions.database.ref('/Customers/{uid}/updateReceipt')
.onWrite(event => {
const uid = event.params.uid;
var receipt = event.data.val();
(strReceipt).toString('base64');
var client = new IAPVerifier('IAP_secretkey')
client.verifyAutoRenewReceipt(receipt, true,function(valid, msg, data){
console.log(' RECEIPT');
if(valid) {
console.log('VALID RECEIPT');
console.log('msg:' + msg);
var strData = JSON.stringify(data);
console.log('data"' + strData);
const newReceiptRef = admin.database().ref('/Customers/{uid}/');
newReceiptRef.update({'receiptData1': data});
const recVerRef = admin.database().ref('/Customers/{uid}/');
newReceiptRef.update({'updateReceipt': 0});
// update status of payment in your system
}else{
console.log('INVALID RECEIPT');
console.log('msg:' + msg);
var strData = JSON.stringify(data);
console.log('data"' + strData);
}
});
});
This is my node js cloud function. The possible values for 'updateReceipt' are 0 and 1. Is it possible to run the cloud function only when the value is 1?
Thanks.
There is no way to only trigger the function when a specific value is present.
I can think of two options:
Write the nodes to a different branch depending on the updateReceipt value.
Add an if to your code.
The second options is definitely the simplest:
exports.verifyReceipt =
functions.database.ref('/Customers/{uid}/updateReceipt')
.onWrite(event => {
const uid = event.params.uid;
var receipt = event.data.val();
if (receipt.updateReceipt === 0) {
var client = new IAPVerifier('IAP_secretkey')
...
Alternatively, you can keep the updated receipt in a separate branch from the new receipts. That way you can trigger a function separately for just the new receipts.

Incrementing Value of incoming value in Azure Table Insert

I am an iOS developer & currently working with Azure mobile services SDK in iOS.
In my case, I am providing each user as a uniqueid say 'myid' in Table say 'todomytable'. In todomytable.js file I am performing check for myid value. If coming value of 'myid' from device is less than stored value in table then I am increasing incoming value greater than earlier stored value in table by One. Below is my logic in todomytable.js
table.insert(function (context) {
context.item.userId = context.user.id;
//....
var intIncomingID = context.item.myid; //ID coming from mobile device
//Call back to get max value of 'myid' from Table
var myFunction = function( callback) {
var query = {
sql: 'select MAX(myid) from TodoItem'
};
context.data.execute(query).then(function (results) {
var objectTest = eval(JSON.stringify(results));
var tempObject = objectTest[0];
var previuosIntID = tempObject["MAX(myid)"];
callback(previuosIntID);
});
};
myFunction(function(returnValue) {
console.log("returnValue is : "+returnValue);
if (intIncomingID<=returnValue)
{
console.log('IF');
context.item.userId = context.user.id;
returnValue = returnValue+1;
context.item.myid = returnValue;
console.log("UPDATED value is : "+context.item.myid);
}
else{
context.item.myid = intIncomingID;
console.log("ITEM value is :"+intIncomingID);
}
return context.execute();
});
});
But issue is that table is not getting update & in mobile log I am getting network request time out error as below
{Error Domain=NSURLErrorDomain Code=-1001 "The request timed out."}
Can anyone suggest how to resolve this issue? I have to store 'myid' in incremented order in table. I am new to Node.js, so sorry If I am making any foolish mistake.
It is likely that there is an exception occurring at some point that causes a promise to reject. Given that there is no promise exception handler defined, the exception ends up being "swallowed". Try the following:
var table = module.exports = require('azure-mobile-apps').table()
table.insert(function (context) {
context.item.userId = context.user.id;
var intIncomingID = context.item.myid; //ID coming from mobile device
var query = { sql: 'select MAX(myid) from TodoItem' };
return context.data.execute(query)
.then(function (results) {
var tempObject = results[0];
var previousIntID = tempObject["MAX(myid)"];
console.log("previous ID is : " + previousIntID);
if (intIncomingID <= previousIntID) {
context.item.myid = previousIntID + 1;
console.log("UPDATED value is : " + context.item.myid);
} else {
context.item.myid = intIncomingID;
console.log("ITEM value is :" + intIncomingID);
}
return context.execute();
});
});
Some points to note:
context.data.execute returns a promise - we are returning this from the insert function so that the Mobile Apps runtime handles any rejected promises
context.execute also returns a promise; we are returning this so that the promises are "chained" properly
Hope this helps!

Resources