Firebase cloud function call client side script - javascript

I have a script in Reactjs that get data (numbers) from api and addup this numbers with numbers from Firebase collection when user opens the page and the user can see this numbers.
There are going to be many users in the app and every user is going to have diffrent numbers from the same script
I was wondering if its possible with Firebase Cloud Functions to run this Client side script on the server and do the callculations of this numbers on the server and store this numbers in a Firestore collection.
im a begginer in nodejs and cloud functions i dont know if this is possible to do
get the numbers from Api
getLatestNum = (sym) => {
return API.getMarketBatch(sym).then((data) => {
return data;
});
};
Cloud function i was trying
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const db = admin.firestore();
exports.resetAppointmentTimes = functions.pubsub
.schedule('30 20 * * *')
.onRun((context) => {
const appointmentTimesCollectionRef = db.collection('data');
return appointmentTimesCollectionRef
.get()
.then((querySnapshot) => {
if (querySnapshot.empty) {
return null;
} else {
let batch = db.batch();
querySnapshot.forEach((doc) => {
console.log(doc);
});
return batch.commit();
}
})
.catch((error) => {
console.log(error);
return null;
});
});

It is indeed possible to call a REST API from a Cloud Function. You need to use a Node.js library which returns Promises, like axios.
It's not 100% clear, in your question, to which specific Firestore doc(s) you want to write, but I make the asumption it will be done within the batched write.
So, something along the following lines should do the trick:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const axios = require('axios');
admin.initializeApp();
const db = admin.firestore();
exports.resetAppointmentTimes = functions.pubsub
.schedule('30 20 * * *')
.onRun((context) => {
let apiData;
return axios.get('https://yourapiuri...')
.then(response => {
apiData = response.data; //For example, it depends on what the API returns
const appointmentTimesCollectionRef = db.collection('data');
return appointmentTimesCollectionRef.get();
})
.then((querySnapshot) => {
if (querySnapshot.empty) {
return null;
} else {
let batch = db.batch();
querySnapshot.forEach((doc) => {
batch.update(doc.ref, { fieldApiData: apiData});
});
return batch.commit();
}
})
.catch((error) => {
console.log(error);
return null;
});
});
Two things to note:
If you want to add the API result to some fields value, you need to give more details on your exact need
Important: You need to be on the "Blaze" pricing plan. As a matter of fact, the free "Spark" plan "allows outbound network requests only to Google-owned services". See https://firebase.google.com/pricing/ (hover your mouse on the question mark situated after the "Cloud Functions" title)

Related

How to list all subcollections of a Firestore document?

Hi,
I have a problem with downloading all collections from the document. I would like after finding the id (userUid) document to be able to download all its collections, I need the id of each of these collection
export const getAllMessagesByUserId = async (userUid) => {
const result = await firebase
.firestore()
.collection('messages')
.doc(userUid)
.onSnapshot((snapshot) => {
console.log(snapshot);
});
};
I wrote an article which proposes solutions to this problem: How to list all subcollections of a Cloud Firestore document? As a matter of fact, "retrieving a list of collections is not possible with the mobile/web client libraries" as explained in the Firestore documentation.
I would suggest you use the second method proposed in the article, using a Cloud Function.
Here is the code copied from the article.
Cloud Function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.getSubCollections = functions.https.onCall(async (data, context) => {
const docPath = data.docPath;
const collections = await admin.firestore().doc(docPath).listCollections();
const collectionIds = collections.map(col => col.id);
return { collections: collectionIds };
});
Example of calling the Cloud Function from a web app:
const getSubCollections = firebase
.functions()
.httpsCallable('getSubCollections');
getSubCollections({ docPath: 'collectionId/documentId' })
.then(function(result) {
var collections = result.data.collections;
console.log(collections);
})
.catch(function(error) {
// Getting the Error details.
var code = error.code;
var message = error.message;
var details = error.details;
// ...
});

Firebase cloud functions onCreate painfully slow to update database

I am having a slightly odd issue, and due to the lack of errors, I am not exactly sure what I am doing wrong. What I am trying to do is on an onCreate event, make an API call, and then update a field on the database if the field is not set to null. Based on my console logs for cloud functions, I can see the API call getting a ok, and everything is working properly, but after about 2-5 minutes, it will update. A few times, it didnt update after 15 mins. What is causing such a slow update?
I have eliminated the gaxios call as the bottleneck simply from the functions logs, and local testing.
Some context: I am on the firebase blaze plan to allow for egress and my dataset isnt really big. I am using gaxios because it is already part of firebase-funcstions npm install.
The code is:
const functions = require('firebase-functions');
const { request } = require('gaxios');
const { parse } = require('url');
exports.getGithubReadme = functions.firestore.document('readmes/{name}').onCreate((snapshot, context) => {
const toolName = context.params.name;
console.log(toolName);
const { name, description, site } = snapshot.data();
console.log(name, description, site);
const parsedUrl = parse(site);
console.log(parsedUrl);
if (description) return;
if (parsedUrl.hostname === 'github.com') {
let githubUrl = `https://api.github.com/repos${parsedUrl.path}/readme`;
request({
method : 'GET',
url : githubUrl
})
.then((res) => {
let { content } = res.data;
return snapshot.ref.update({ description: content });
})
.catch((error) => {
console.log(error);
return null;
});
}
return null;
});
When you execute an asynchronous operation (i.e. request() in your case) in a background triggered Cloud Function, you must return a promise, in such a way the Cloud Function waits that this promise resolves in order to terminate.
This is very well explained in the official Firebase video series here (Learning Cloud Functions for Firebase (video series)). In particular watch the three videos titled "Learn JavaScript Promises" (Parts 2 & 3 especially focus on background triggered Cloud Functions, but it really worth watching Part 1 before).
So you should adapt your code as follows, returning the promise returned by request():
const functions = require('firebase-functions');
const { request } = require('gaxios');
const { parse } = require('url');
exports.getGithubReadme = functions.firestore.document('readmes/{name}').onCreate((snapshot, context) => {
const toolName = context.params.name;
console.log(toolName);
const { name, description, site } = snapshot.data();
console.log(name, description, site);
const parsedUrl = parse(site);
console.log(parsedUrl);
if (description) return null;
if (parsedUrl.hostname === 'github.com') {
let githubUrl = `https://api.github.com/repos${parsedUrl.path}/readme`;
return request({
method: 'GET',
url: githubUrl
})
.then((res) => {
let { content } = res.data;
return snapshot.ref.update({ description: content });
})
.catch((error) => {
console.log(error);
return null;
});
} else {
return null;
}
});

Payment not being charged, because of some error in the firebase cloud function

I'm making a react-redux app with firetore as database.
Now, I wanted to use firebase cloud functions for handling stripe payments.
Here is the setup:
Below is the action method for checkout, after receiving token and amount from react side.
export const checkoutFunc = (token, amount) => {
return (dispatch, getState, { getFirebase, getFirestore }) => {
const uid = getState().firebase.auth.uid;
const ref = database.ref();
ref.child(`payments/${uid}`).push({
token: token,
amount: amount
});
};
};
This function creates a payment and saves token and amount.
Now, here is the cloud function which should "charge" the payment, after the above payment is created.
exports.stripeCharge = functions.database
.ref("/payments/{userId}/{paymentId}")
.onWrite((change, context) => {
const payment = change.after.val();
const userId = context.params.userId;
const paymentId = context.params.paymentId;
if (!payment || payment.charge) return;
return admin
.database()
.ref(`/teachers/${userId}`)
.once("value")
.then(snap => {
return snap.val();
})
.then(customer => {
const amount = payment.amount;
const idempotency_key = paymentId;
const source = payment.token.id;
const currency = "usd";
const charge = { amount, currency, source };
return stripe.charges.create(charge, { idempotency_key });
})
.then(charge => {
admin
.database()
.ref(`/payments/${userId}/${paymentId}/charge`)
.set(charge)
.then(charge => {
return true;
});
});
});
The creation of payment works and the token and amount is saved in payments table. But, the cloud function is not doing its job of charging the token.
Expected Result:
https://i.ibb.co/Fq9Zfhq/image.png
Actual result:
https://i.ibb.co/Krk7cGL/image.png
Though the answer provided by #Doug Stevenson is helpful, it was not the main problem. So, I am writing the solution here for other people struggling with it.
I was using the wrong public key and secret key pair in my app, that when I used correctly, it worked.
You're not returning the promise returned from set() using the Admin SDK. The function is terminating and cleaning up before that async work is complete.
.then(charge => {
return admin // add a return here
.database()
.ref(`/payments/${userId}/${paymentId}/charge`)
.set(charge)
.then(charge => {
return true;
});
});
FYI these promise chains are easier to visualize if you use async/await syntax instead of then/catch.

How to program Firebase Cloud Functions to iterate through array of references?

I'm pretty new to Cloud Functions on Firebase and I'm struggling to program some code to iterate through an array of document references that have been downloaded from the Firestore.
The array is stored in my Firestore and contains references to each admin user in my users collection. Each of these users has a field in their document with their messaging token, which I need to send the message. I've manage to get the code to send a notification to a token that I define as a constant in the code however haven't had any luck sending to the tokens stored in the database.
Here is my code so far;
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
// exports.helloWorld = functions.https.onRequest((request, response) => {
// response.send("Hello from Firebase!");
// });
exports.notifyNewReport = functions.firestore
.document('admin/reportsToReview')
.onUpdate((change, context) => {
console.log('Change to doc function registered');
// Get an object representing the document
const newValueReports = change.after.data().reports;
// ...or the previous value before this update
const previousValueReports = change.before.data().reports;
if (newValueReports.length > previousValueReports.length) {
console.log('Report added to review list');
var adminsArray = ""
admin.firestore()
.collection('admin')
.doc('admins')
.get()
.then(doc => {
adminsArray = doc.data().admins
return console.log('Found admin UID: ' + adminsArray);
})
.catch(error => {
console.error(error);
res.error(500);
});
//Code to get send notification to each device
console.log("Construct the notification message.");
var message = {
notification: {
body: 'There are new reports to review!',
},
token: token
};
admin.messaging().send(message)
}
});
If anyone can point me in the right direction that would be greatly appreciated! :)

ECONRESET socket hungup

I have a function that triggers on firebase database onWrite. The function body use two google cloud apis (DNS and Storage).
While the function is running and working as expected (mostly), the issue is that the Socket hang up more often than I'd like. (50%~ of times)
My questions are:
Is it similar to what the rest of the testers have experienced? Is it a well known issue that is outstanding or expected behavior?
the example code is as follows:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const {credentials} = functions.config().auth;
credentials.private_key = credentials.private_key.replace(/\\n/g, '\n');
const config = Object.assign({}, functions.config().firebase, {credentials});
admin.initializeApp(config);
const gcs = require('#google-cloud/storage')({credentials});
const dns = require('#google-cloud/dns')({credentials});
const zoneName = 'applambda';
const zone = dns.zone(zoneName);
exports.createDeleteDNSAndStorage = functions.database.ref('/apps/{uid}/{appid}/name')
.onWrite(event => {
// Only edit data when it is first created.
const {uid, appid} = event.params;
const name = event.data.val();
const dbRef = admin.database().ref(`/apps/${uid}/${appid}`);
if (event.data.previous.exists()) {
console.log(`already exists ${uid}/${appid}`);
return;
}
// Exit when the data is deleted.
if (!event.data.exists()) {
console.log(`data is being deleted ${uid}/${appid}`);
return;
}
const url = `${name}.${zoneName}.com`;
console.log(`data: ${uid}/${appid}/${name}\nsetting up: ${url}`);
setupDNS({url, dbRef});
setupStorage({url, dbRef});
return;
});
function setupDNS({url, dbRef}) {
// Create an NS record.
let cnameRecord = zone.record('cname', {
name: `${url}.`,
data: 'c.storage.googleapis.com.',
ttl: 3000
});
zone.addRecords(cnameRecord).then(function() {
console.log(`done setting up zonerecord for ${url}`);
dbRef.update({dns: url}).then(res => console.log(res)).catch(err => console.log(err));
}).catch(function(err) {
console.error(`error setting up zonerecord for ${url}`);
console.error(err);
});
}
function setupStorage({url, dbRef}) {
console.log(`setting up storage bucket for ${url}`);
gcs.createBucket(url, {
website: {
mainPageSuffix: `https://${url}`,
notFoundPage: `https://${url}/404.html`
}
}).then(function(res) {
let bucket = res[0];
console.log(`created bucket ${url}, setting it as public`);
dbRef.update({storage: url}).then(function() {
console.log(`done setting up bucket for ${url}`);
}).catch(function(err) {
console.error(`db update for storage failed ${url}`);
console.error(err);
});
bucket.makePublic().then(function() {
console.log(`bucket set as public for ${url}`);
}).catch(function(err) {
console.error(`setting public for storage failed ${url}`);
console.error(err);
});
}).catch(function(err) {
console.error(`creating bucket failed ${url}`);
console.error(err);
});
}
I'm thinking your function needs to return a promise so that all the other async work has time to complete before the function shuts down. As it's shown now, your functions simply returns immediately without waiting for the work to complete.
I don't know the cloud APIs you're using very well, but I'd guess that you should make your setupDns() and setupStorage() return the promises from the async work that they're doing, then return Promise.all() passing those two promises to let Cloud Functions know it should wait until all that work is complete before cleaning up the container that's running the function.

Categories