Push Notifications using Firebase/Google Cloud Functions - ios

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?

Related

App Store Server Notifications and Firebase Cloud Functions

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.

How to send voip push notification from node js? I can send voip push from curl but not node

I am making ios app using voip push notification.
I want to send voip push notification from node js, but not well.
I read this tutorial CallKit iOS Swift Tutorial for VoIP Apps (Super Easy) and I made ios app using voip push.
I can send voip push from curl command.
curl -v -d '{"aps":{"alert":"hello"}}' --http2 --cert chara.pem:passphase https://api.push.apple.com/3/device/ede0d5e78f771d5916345aa48bd098e86aeab40b5e7d985fb9c74586d1a5a681
node index.js
const http2 = require('http2');
const fs = require('fs');
exports.handler = async (event) => {
const bundleID = 'com.swiswiswift.CharacterAlarm'
const deviceToken = 'ede0d5e78f771d5916345aa48bd098e86aeab40b5e7d985fb9c74586d1a5a681'
const headers = {
'apns-topic': bundleID
};
const options = {
protocol: 'https:',
method: 'POST',
hostname: 'api.push.apple.com',
path: '/3/device/' + deviceToken,
headers: headers,
cert: fs.readFileSync('chara.pem'),
passphrase: "passphrase"
};
const client = http2.connect('api.push.apple.com')
const req = client.request(options)
req.setEncoding('utf8')
req.on('response', (headers, flags) => {
console.log(headers)
});
let data = ''
req.on('data', (d) => data += d)
req.on('end', () => client.destroy())
req.end()
}
exports.handler('local-test')
[Object: null prototype] {
':status': 405,
'apns-id': 'xxxxxxx-xxxx-xxxx-xxx-xxxxxxxx' }
var apn = require("apn");
var deviceToken = "device token";
var service = new apn.Provider({
cert: '.path to /cert.pem', key:'pat to ./key.pem'
});
var note = new apn.Notification();
note.expiry = Math.floor(Date.now() / 1000) + 60; // Expires 1 minute from now.
note.badge = 3;
note.sound = "ping.aiff";
note.alert = " You have a new message";
note.payload = {'messageFrom': 'Rahul test apn'};
note.topic = "(Bundle_id).voip";
note.priority = 10;
note.pushType = "alert";
service.send(note, deviceToken).then( (err,result) => {
if(err) return console.log(JSON.stringify(err));
return console.log(JSON.stringify(result))
});
This is a working code for apn voip push
also refer this
https://alexanderpaterson.com/posts/send-ios-push-notifications-with-a-node-backend
I'm using a slightly different approach:
If you don't know where to find or how to create the key, keyId and teamID, have a look at this article on Medium. He explains all of this in detail.
var apn = require('apn');
const { v4: uuidv4 } = require('uuid');
const options = {
token: {
key: 'APNKey.p8',
keyId: 'S83SFJIE38',
teamId: 'JF982KSD6f'
},
production: false
};
var apnProvider = new apn.Provider(options);
// Sending the voip notification
let notification = new apn.Notification();
notification.body = "Hello there!";
notification.topic = "my.bundleid.voip"; // Make sure to append .voip here!
notification.payload = {
"aps": { "content-available": 1 },
"handle": "1111111",
"callerName": "Richard Feynman",
"uuid": uuidv4()
};
apnProvider.send(notification, deviceToken).then((response) => {
console.log(response);
});

IOS push notifications not being grouped - apn

I am using apn push notificaitons from node server.
I want to group same nature of notifications together.
For that I am using threadId parameter but its still not grouping the notifications.
Here is my server side code:
var apnOptions = {
token: {
key: "certificates/F5TRRQ26V.p8",
keyId: "F5TRRQ26V",
teamId: "4ZGSRKDXH"
},
production: false
};
var apnProvider = new apn.Provider(apnOptions);
var note = new apn.Notification();
note.expiry = Math.floor(Date.now() / 1000) + 3600; // Expires 1 hour from now.
note.badge = 3;
note.sound = "ping.aiff";
note.alert = "\uD83D\uDCE7 \u2709 You have a new message";
note.payload = { 'messageFrom': 'John Appleseed' };
note.topic = "com.example.myapp";
note.threadId = "870";
apnProvider.send(note, '5DA3C319EB50GDDGGAAA333DC9FE6DB240C34C66CBF1E547CD8307DCA15').then( (result) => {
console.log(result);
});
On IOS app side I am receiving this:
User Info = [AnyHashable("thread-id"): 870, AnyHashable("threadId"): 870, AnyHashable("aps"): {
alert = "\Ud83d\Udce7 \U2709 You have a new message";
badge = 3;
sound = "ping.aiff";
"thread-id" = 870;
}, AnyHashable("messageFrom"): John Appleseed]
But still the notifications are not being grouped.

I'm getting the errorNum :8 while sending the push notifications to ios devices

var apn = require('apn');
var gcm = require('android-gcm');
export default function notification( devicetype, devicetoken, alert, userid, action, profilepic, image, youtubeimage, id ) {
if(devicetoken != "(null)") {
var androidApiKey = '', cert = '', key = '', passphrase = '';
if(process.env.NODE_ENV.toLowerCase() == "production") {
cert = '/../config/ios_support/apns-cert.pem';
key = '/../config/ios_support/apns-key.pem';
passphrase = '*****';
androidApiKey = "*******";
}
else {
cert = '/../config/ios_support/apns-dev-cert.pem';
key = '/../config/ios_support/apns-dev-key.pem';
passphrase = '*******';
androidApiKey = "********";
}
if(devicetype == "ios"){
var myDevice = new apn.Device(devicetoken);
var note = new apn.Notification();
note.badge = 1;
note.sound = "notification-beep.wav";
note.alert = alert;
note.category = "respond"
note.device = myDevice;
note.payload = { 'action': action, 'userid': userid, 'profilepic': profilepic, 'id':id};
console.log("note.payload: "+ JSON.stringify(note.payload));
//, 'WatchKit Simulator Actions': [{"title": "Show", "identifier": "showButtonAction"}]
var callback = function (errorNum, notification) {
console.log('Error is:.....', errorNum);
}
var options = {
gateway: 'gateway.push.apple.com',
//'gateway.sandbox.push.apple.com',
// this URL is different for Apple's Production Servers and changes when you go to production
errorCallback: callback,
cert: __dirname.split('src/')[0] + cert,
key: __dirname.split('src/')[0] + key,
passphrase: passphrase,
port: ****,
cacheLength: 100
}
var apnsConnection = new apn.Connection(options);
apnsConnection.sendNotification(note);
}
else if(devicetype == "android"){
var gcmObject = new gcm.AndroidGcm(androidApiKey);
var message = new gcm.Message({
registration_ids: [devicetoken],
data: {
body: alert,
action: action,
userid: userid,
profilepic: profilepic,
id: id
}
});
gcmObject.send(message, function(err, response) {
if(err) console.error("error: "+err);
// else console.log("response: "+response);
});
}
}
}
Here is my code. In console I'm getting all the stuff and device token is also fine. Android mobiles are getting notifications. But notifications are not sending to ios devices. I'm getting this error in console : Error is:...... 8.
One more thing is, for the same device I'm able to send the notification for other functionality with other code.
Really I'm pulling my hair out for this issue. And can't understand what's wrong with my code. Anyone please give solution for this.
You are using an old version. Apple has changed some thing in the push api last year in march i guess.
Also you forgot to set your topic which is mandetory for apn push Notifications
Try something like this for you if (devicetype == "ios") block
if(devicetype == "ios") {
var myDevice = new apn.Device(devicetoken);
var note = new apn.Notification();
note.badge = 1;
note.sound = "notification-beep.wav";
note.alert = alert;
note.category = "respond"
note.payload = {'action': action, 'userid': userid, 'profilepic': profilepic, 'id': id};
//you missed this one i guess
note.topic = "<your-app-bundle-id>";
console.log("note.payload: " + JSON.stringify(note.payload));
//, 'WatchKit Simulator Actions': [{"title": "Show", "identifier": "showButtonAction"}]
var callback = function(errorNum, notification) {
console.log('Error is:.....', errorNum);
}
var options = {
token: {
key: __dirname.split('src/')[0] + cert,
keyId: __dirname.split('src/')[0] + key,
teamId: "developer-team-id"
},
production: false // for development
};
var apnProvider = new apn.Provider(options);
apnProvider.send(note, myDevice).then( (result) => {
// see documentation for an explanation of result
console.log(result);
});
}
You can find the documentation here apn

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.

Resources