Firebase Functions: when to call initializeApp/deleteApp - firebase

I have a variety of functions for Cloud Functions that use things derived from firebase-admin/app's initializeApp(). Since multiple functions use this, I had the idea of calling initializeApp() close to the beginning of the Typescript/Javascript. Like this:
import { initializeApp } from 'firebase-admin/app';
...
const app = initializeApp();
...
export const activeUser = https.onCall(
async (email: string, ctx: CallableContext) => {
...
const auth = getAuth(app);
const firestore = getFirestore(app);
try {
const user = await auth.getUserByEmail(email);
const snpsht = await firestore.collection('users').doc(user.uid).get();
...
export const userExists = https.onCall(
async (email: string, ctx: CallableContext) => {
if (!emulating() && forbiddenOrigin(ctx)) {
return `${ERRORFLAG}: forbidden`;
}
const auth = getAuth(app);
...
A testing headache showed me that I forgot about calling deleteApp(app).
Is deleteApp() necessary, or will the app be deleted as a side effect of Cloud tearing down the function?
Should initializeApp()/deleteApp() be called within each function where an app is needed, ie within activeUser and userExists in my above example?
Is there any way to tell Cloud Functions to run setup/teardown code before running a given function?

A call to deleteApp is not necessary. When a Cloud Functions instance terminates, all memory gets shut down with it (which is what deleteApp would do). The entire server instance is completely gone.
You only need to call deleteApp if you are controlling a process that needs to continue after the call to deleteApp with the Firebase resources freed from memory. Cloud Functions does not meet this criteria since you don't control the process startup or shutdown.

Related

Firebase Storage with Google Actions

I am having some issues connecting my firebase storage with my google action. I need to be able to "download" the json files inside in order to be able to read and pick out what a user may need given data that they provide when they call the action.
Below is the code that I currently have, complied from the different APIs and other stackoverflow questions I have found.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const Firestore = require('#google-cloud/firestore');
const firestore = new Firestore();
var storage = require('#google-cloud/storage');
const gcs = storage({projectId: 'aur-healthcare-group'});
const bucket = gcs.bucket('gs://aur-healthcare-group');
admin.storage().bucket().file('aur-healthcare-group/aur_members.json').download(function(errr, contents){
if(!err){
var jsObjext = JSON.parse(contents.toString('utf8'));
}
});
The current error I am receiving is "code":3,"message":"Function failed on loading user code. This is likely due to a bug in the user code. Error message: Error: please examine your function logs to see the error cause. When I check the logs I only get the above mentioned message again.
I believe that I am not accessing my firebase storage correctly and have trouble finding a good resource on how to access this correctly. Would somebody be able to give me an example of how to access the storage correctly so I will be able to apply it to my project?
Since you're running in Firebase Functions, you shouldn't need to require the #google-cloud/storage dependency directly. Rather, you can get the correctly authenticated storage component via admin.storage()
Following that, you shouldn't download the file to your function, as you would be better off reading directly into memory via a readStream.
With regards to your existing code error, it may be because you're checking if (!err) when the callback variable is errr.
I've done this in the past and here's a code snippet of how I achieved it. It's written in Typescript specifically, but I think you should be able to port it to JS if you're using that directly.
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin'
import { Bucket } from '#google-cloud/storage';
admin.initializeApp()
const db = admin.firestore()
const bucket = admin.storage().bucket('project-id.appspot.com') // Use your project-id here.
const readFile = async (bucket: Bucket, fileName: string) => {
const stream = bucket.file(fileName).createReadStream();
return new Promise((resolve, reject) => {
let buffer = '';
stream.on('data', function(d: string) {
buffer += d;
}).on('end', function() {
resolve(buffer)
});
})
}
app.handle('my-intent-handler', async (conv) => {
const contents = await readArticle(bucket, 'filename.txt')
conv.add(`Your content is ${contents}`)
})
exports.fulfillment = functions.https.onRequest(app)

Firebase Cloud Function does not terminate in functions:shell

I'm trying to import a JSON into a collection. For testing purpose, I'm using emulators and document creation trigger.
The workflow is :
start emulators with firebase emulators:start
start functions shell in another terminal with firebase functions:shell (tells me that no emulator is running btw)
call my function with tempoCF()
it runs and add documents to collection but it seems that the function does not terminate. I cannot call another function and need to press CTRL+C to be able to write again in the shell.
Here is the function I use :
const functions = require("firebase-functions");
const admin = require('firebase-admin');
const fetch = require('node-fetch');
admin.initializeApp();
const db = admin.firestore();
exports.tempoCF = functions
.firestore.document('/tempo/{docId}')
.onCreate(async (snap, context) => {
console.log("onCreate");
let settings = { method: "Get" };
let url = "https://opendata.paris.fr/api/records/1.0/search/?dataset=sanisettesparis&q=&rows=-1";
try {
let response = await fetch(url, settings);
let json = await response.json();
// TODO for each json object, add new document
return Promise.all(json["records"].map(toiletJsonObject => {
console.log(toiletJsonObject);
return db.collection('toilets').doc(toiletJsonObject["recordid"]).set({});
}));
}
catch(error) {
console.log(error);
return null;
}
}
);
I know I can use the emulator UI to create a new document that trigger the tempoCF function and it works as well but I fear that my function isn't correct and could generate bugs in production.
Here is the screenshot of the terminal. It prints logs and at the end, there is no way to write anything on the last empty line in the screenshot. I run it in Android Studio but I don't think that it matters.
I'm not on Windows but on a Mac, and I can reproduce your problem in the Terminal by calling the function with tempoCF(). Somehow, by doing that, you are simulating the creation a Firestore document without data.
But if I pass some data when calling the Cloud Function, e.g. tempoCF({foo: "bar"}) (i.e. providing new test data for the onCreate operation) I'm able to write to the Terminal after the CF has completed. See the doc for more details.

What happens to async calls in completed firebase functions?

If I choose not to wait for an async call to complete inside a firebase function, what happens to the async call when the function completes? On what thread does it continue, does it still run within the function's runtimeOpts and how does this affect usage?
import * as firebase from 'firebase';
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
exports.action = functions.https.onRequest((req, res) => {
admin.database().ref('action').add({ 1: 2 });
res.end(); // or just `return;` for other functions
});
Or maybe even
exports.action2 = functions.https.onRequest((req, res) => {
new Promise(resolve => setTimeout(() => admin.database().ref('action').add({ 1: 2 })}, 10_000));
res.end();
});
Have a look at this section of the documentation on Cloud Functions, which explains why it is "important to manage the lifecycle of a function to ensure that it resolves properly".
If you want to be sure that "the Cloud Functions instance running your Cloud Function (CF) does not shut down before your CF successfully reaches its terminating condition or state" (in your case, when the asynchronous work is done), you need to "resolve your CF by returning a JavaScript promise".
So, if you don't do that, there is the risk that the Cloud Functions instance running your CF shuts down before your calls to one or more async functions complete.
However, it does NOT mean that this will always happen. It may be very well possible that the Cloud Functions instance running your CF continues to run after you sent back the response to the client (with res.end();) resulting in the async work being completed.
The problem is that this is totally out of your control (it depends on how the Cloud Functions platform manages you Cloud Function instances): sometimes it may continue running (completing the async work), sometimes it may not.
You will find a lot of questions on Cloud Functions on Stack Overflow which explain that their problem is that the Cloud Function sometimes executes the async work correctly and sometimes not: the reason is simply because they don't return the Promises returned by the async calls.
Does this affect usage?
Yes, as clearly said in the doc: "By terminating functions correctly, you can avoid excessive charges from functions that run for too long or loop infinitely."
So, concretely, in your case you should do something like:
exports.action = functions.https.onRequest(async (req, res) => { // <-- Note the async keyword here
await admin.database().ref('action').add({ 1: 2 });
res.end(); // or just `return;` for other functions
});
Note that if your Cloud Function is synchronous (it does not call any asynchronous method) you have to terminate it with a simple return; statement (or by calling send(), redirect(), or end() for an HTTPS Cloud Function). For example:
exports.action = functions.https.onRequest((req, res) => {
const result = 1 + 3;
res.status(200).send(result);
});
or
exports.unusefulCloudFunction = functions.firestore
.document('users/{userId}').onWrite((change, context) => {
const result = 1 + 3;
console.log(result);
return;
});

admin.firestore(...).document is not a function at exports

I am running a cloud function that is triggered by a firebase realtime database change and updates FireStore. However, although the function triggers, the function cannot access the Firestore.
exports.reveal = functions.database.ref('/reveals/{postIDthatWasRevealed}/revealed').onUpdate((change, context) => {
const revealedValue = change.after.val()
if (revealedValue === true) {
var updates = {}
const postID = context.params.postIDthatWasRevealed
return admin.firestore().document('/posters/' + postID).get().then(querySnapshot => {
At this point, the console log states TypeError:admin.firestore(...).document is not a function at.. I have already tried this answer :Tried Answer But the problem continues. Does this have to do with the fact that I am accessing firestore inside of firebase cloud function? Are my cloud functions not properly updated?
Edit (Includes Initialization code)
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
In addition, I have tried to debug the function by changing it to :
if (revealedValue === true) {
var updates = {}
const postID = context.params.postIDthatWasRevealed
return admin.firestore()
Now, when I trigger the function, I get Function execution took 811 ms, finished with status: 'ok' Thus, it seems as if the firestore function is valid, just that my syntax may be off or I may be forgetting something
use doc() function instead of document()
document() is a property of functions.firestore
The equivalent for admin.firestore() is collection().doc()
See Firestore: Get Data for more info.
admin.firestore().collection('posters').doc(postID) ...

Firebase: How to run 'HTTPS callable functions' locally using Cloud Functions shell?

I couldn't find a solution for this use case in Firebase official guides.
They are HTTPS callable functions
Want to run Functions locally using Cloud Functions shell to test
Functions save received data to Firestore
The 'auth' context information is also needed
My code as below. Thanks in advance.
Function :
exports.myFunction = functions.https.onCall((data, context) => {
const id = context.auth.uid;
const message = data.message;
admin.firestore()...
// Do something with Firestore //
});
Client call :
const message = { message: 'Hello.' };
firebase.functions().httpsCallable('myFunction')(message)
.then(result => {
// Do something //
})
.catch(error => {
// Error handler //
});
There is an api exactly for this use case, see here.
I used it in javascript(Client side) as follows -
button.addEventListener('click',()=>{
//use locally deployed function
firebase.functions().useFunctionsEmulator('http://localhost:5001');
//get function reference
const sayHello = firebase.functions().httpsCallable('sayHello');
sayHello().then(result=>{
console.log(result.data);
})
})
where sayHello() is the callable firebase function.
When the client is an android emulator/device. Use 10.0.2.2 in place of localhost.
Also the code for flutter would be like so -
CloudFunctions.instance.useFunctionsEmulator(origin: 'http://10.0.2.2:5000')
.getHttpsCallable(functionName: 'sayHello')
Cloud functions have emulators for that. Check this link it can suite your case. Its not functions shell, but for testing purposes i think it can still works for you
In newer versions of firebase, this is the way:
import firebaseApp from './firebaseConfig';
import { getFunctions, httpsCallable, connectFunctionsEmulator } from 'firebase/functions';
const functions = getFunctions(firebaseApp);
export async function post(funcName, params) {
connectFunctionsEmulator(functions, 'localhost', '5001'); // DEBUG
let func = httpsCallable(functions, funcName);
let result = await func(params);
return result.data;
}

Resources