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.
Airwatch documentation is gawdawful. So bear with me.
From Airwatch SDK, how to I get the currently logged in username/user certificate?
It's in the API
//setup authorization profile
var client = new RestClient("https://enterprise.telstra.com/api/mdm");
client.Authenticator=new HttpBasicAuthenticator("#####", "#######");
var request = new RestRequest("devices/search", DataFormat.Json);
request.AddHeader("aw-tenant-code", "##########");
//get the data as json
var response = client.Get(request);
if (response.IsSuccessful)
{
//serialize the data into the Devices[] array RootObject
RootObject ro = new RootObject();
ro = JsonConvert.DeserializeObject<RootObject>(response.Content);
//create a new DataTable with columns specifically required
DataTable dt = new DataTable("tblDevices");
dt.Columns.Add("AssetNumber");
dt.Columns.Add("DeviceFriendlyName");
dt.Columns.Add("IMEI");
dt.Columns.Add("MacAddress");
dt.Columns.Add("Model");
dt.Columns.Add("DeviceType");
dt.Columns.Add("OEMinfo");
dt.Columns.Add("Platform");
dt.Columns.Add("SerialNumber");
dt.Columns.Add("UserEmailAddress");
dt.Columns.Add("UserName");
//iterate through each device data and add it into a DataRow
foreach(Device d in ro.Devices)
{
DataRow dr = dt.NewRow();
dr["UserName"] = d.UserName.ToUpper(); //uppercase the userid
//add the row to the table
dt.Rows.Add(dr);
}
With godly help from the client
AWController.clientInstance().retrieveStoredPublicCertificates { payload, error in
if let _ = error {
return
}
guard let payload = payload as? [String : [PKCS12Certificate]] else {
return
}
processCertificates(payload)
NotificationCenter.default.post(name: .awSDKCeritificatesReceived,
object: payload)
}
I cannot get a new record entry into my firestore document db to generate an alert to users.
IOS app fetches and updates firestore data with no issues
If I manually send a message from firebase my app gets the message no issues
I can deploy my cloud function to firebase with no errors
What am I doing wrong? Thanks for any help.
let functions = require('firebase-functions')
let admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
let db = admin.firestore()
exports.announceAlert = functions.database
.ref('/alerts/{documentId}')
.onCreate((snapshot, context) => {
let alert = snapshot.val()
sendNotification(alert)
})
function sendNotification(alert) {
let title = alert.Incident_Number
let summary = alert.Flash_Summary
let status = alert.Incident_Status
let payload = {
notification: {
title: 'Critical Incident: ' + title,
body: 'Summary: ' + summary,
sound: 'default'
}
}
console.log(payload)
let topic = "Alerts"
admin.messaging().sendToTopic(topic, payload)
}
This is what I did and it worked. Please keep in mind that the user of your iOS app has to subscribe to the topic and you do that through the app. The code below is just a function telling firebase to send a notification to subscribed users when a new document is created in a certain repository.
let functions = require('firebase-functions')
let admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
let db = admin.firestore()
exports.announceMessage = functions.firestore
.document('/myData/{documentId}')
.onCreate((snapshot, context) => {
let message = snapshot.data()
sendNotification(message)
})
function sendNotification(message) {
let title = message.column1
let notification = message.column2
let payload = {
notification: {
title: 'Some title: ' + title,
body: 'Some header: ' + notification
},
}
console.log(payload)
let topic = "yourTopic"
return admin.messaging().sendToTopic(topic, payload)
}
For example, I have a car1 that was first owner by the manufacturer and then it was transferred over to the retailer and then to the user
In the fabcar example, I can know who is its current owner by i don't know who is the previous owner.
Is there a way to do it?
Here is the http://hyperledger-fabric.readthedocs.io/en/latest/write_first_app.html example I was following
Is not implemented in the chaincode. To do this you could implement a new method which returns the history of the asset.
Here the link to the official documentation for nodeJS (you can find it also for GoLang):
https://fabric-shim.github.io/ChaincodeStub.html#getHistoryForKey__anchor
Here an example:
async queryValueHistory(stub,args){
if (args.length != 1) {
throw new Error('Incorrect number of arguments. Expecting identifier ex: CAR01');
}
let carId = args[0];
let iterator = await stub.getHistoryForKey(carId);
let allResults = [];
while (true) {
let res = await iterator.next();
if (res.value && res.value.value.toString()) {
let jsonRes = {};
console.log(res.value.value.toString('utf8'));
jsonRes.TxId = res.value.tx_id;
jsonRes.Timestamp = res.value.timestamp;
jsonRes.IsDelete = res.value.is_delete.toString();
try {
jsonRes.Value = JSON.parse(res.value.value.toString('utf8'));
} catch (err) {
console.log(err);
jsonRes.Value = res.value.value.toString('utf8');
}
console.info(jsonRes);
allResults.push(jsonRes);
}
if (res.done) {
console.log('end of data');
try{
await iterator.close();
}catch(err){
console.log(err);
}
console.info(allResults);
return Buffer.from(JSON.stringify(allResults));
}
}
}
I am trying to get data from firebase database using firebase query
self.getReferencePath().child(User.followingReferencePath).queryOrderedByKey().queryLimited(toLast: 5)
When I pass this query to firebase and print the snapshot.value, I get the data in the order present in firebase as following
snapshot.value {
"-L2_hNtqB5UCBzgMFX0m" = {
"debate_id" = "-L2_hNtqB5UCBzgMFX0m";
};
"-L2_hSQSl7T5cDCfDoiV" = {
"debate_id" = "-L2_hSQSl7T5cDCfDoiV";
};
"-L2_h_vrwqID5Esl6gOk" = {
"debate_id" = "-L2_h_vrwqID5Esl6gOk";
};
"-L2_hfAruruMjzrLEZtI" = {
"debate_id" = "-L2_hfAruruMjzrLEZtI";
};
"-L2_hk2Uq7IBcflkO5YQ" = {
"debate_id" = "-L2_hk2Uq7IBcflkO5YQ";
};
}
But after converting the snapshot.all values as NSArray, the order changes as follow
let followingDbate = followingDebatesSnapshot.allValues as NSArray
print("followingDbate",followingDbate)
followingDbate (
{
"debate_id" = "-L2_hk2Uq7IBcflkO5YQ";
},
{
"debate_id" = "-L2_h_vrwqID5Esl6gOk";
},
{
"debate_id" = "-L2_hfAruruMjzrLEZtI";
},
{
"debate_id" = "-L2_hNtqB5UCBzgMFX0m";
},
{
"debate_id" = "-L2_hSQSl7T5cDCfDoiV";
}
)
I want to get the last Id as=nd pass it through another query. As the order changes I am not able to get the last id correctly. Please advice how to get the data without changing the order and also ow to get the last id from the snapshot I get. Thanks in advance