How to check if Firebase Function has already been initialized - firebase

Just in case its important, this is how I am importing the functions stuff
import * as functions from "firebase-functions";
import admin = require("firebase-admin");
I am currently using the below code to check if the firebase function has already been initialized:
if (admin.apps.length === 0) {
admin.initializeApp();
} else {
functions.logger.log("App already initialized");
}
I got this code from: How to check if a Firebase App is already initialized on Android
My problem is that this doesn't seem to be checking if the app has already been initialized because I see the log from the else statement but immediately after I see the error log that the default Firebase app does not exist.
If I call admin.initializeApp(); it works, but only sometimes, other times it would tell me that
"Unhandled error FirebaseAppError: The default Firebase app already exists. This means you called initializeApp() more than once without providing an app name as the second argument. In most cases you only need to call initializeApp() once"
What is the correct way to see if the app has already been initialized?
I am not sure if the below is important but here goes:
I have my functions in separate files, to import the files into the index.ts file I:
Import the function:
import checkPayment = require("./checkPayment");
I'm not sure how to define what this is:
exports.checkPayment = checkPayment;
Perhaps I am importing the other functions incorrectly.
Thank you for your time.

Related

Firestore has already been started and cannot be changed - Nuxt 2, Firebase Emulator

EDIT (ANSWER): There were two issues with my code. One was that I was passing "http://localhost" instead of just "localhost" to the connectFirestoreEmulator line. The second was that I needed to check db._settingsFrozen before running the emulator line in order to prevent it from being double-initialized. I believe this has something to do with how Nuxt runs its code on the server side vs client side: I think Nuxt shares context/state across sessions on the server side, leading to the emulator line getting run more than once.
I have a Nuxt 2 app that I've connected to Firebase production successfully for a while now. I'm trying to set it up now to use the emulator instead of production when in local environment (indicated by an env variable), but I'm getting an error with the Firebase module specifically. There doesn't seem to be any issues using the auth emulator (which I've also set up). Code (plugins/firebase.js):
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
export const services = { auth, db };
export default (context) => {
if (context.env.appEnv === 'dev') {
console.log('dev', parseInt(context.env.firestorePort))
connectFirestoreEmulator(db, 'http://localhost', parseInt(context.env.firestorePort));
connectAuthEmulator(auth, `http://localhost:${context.env.authPort}`);
}
}
I have narrowed it down to the "connectFirestoreEmulator" line which when commented out, there are no errors. When it's included, this is the error I get:
Firestore has already been started and its settings can no longer be changed. You can only modify settings before calling any other methods on a Firestore object.
Finally, when I set the plugin to run on client-side only, it no longer errors out which is odd. I'll probably just keep working with this client-only for now, but I would like to be able to run the firebase stuff on the server too ideally.
Any ideas/guidance is appreciated. Thanks in advance!
I've Googled the relevant terms and found one other question that was similar to my question ("Firebase Error : Firestore has already been started and its settings can no longer be changed." connecting Firebase v9 with Firestore Emulator). However, because I don't have enough reputation on SO, I can't comment to ask the OP if he ever found out what was happening (which I would normally do before asking my own question, but I'm not being given a choice here).
I also even looked at the nuxt-firebase npm package's source code (https://github.com/nuxt-community/firebase-module) to see how they may have set up the emulator, but their plugin/code is written so differently from mine that it was unhelpful.

Can cold start happen multiple times per invocation in Firebase functions?

I am trying to organize my functions with some business logic and some helper functions that are running only from within my code (not exposed endpoints).
As I am trying to organize it, some helper functions would depend on admin or other imports, so in both business.js and common.js I would have
const admin = require("firebase-admin");
admin.initializeApp();
Lets say business.js function calls some function from common.js, and they both use admin.firestore() - how does cold start work in this case? Would both of these functions pay for the cold start time - would common.childCalledFunction pay for cold start even if admin is initialized for called function business.parentCalledFunction? Is cold start once per invocation or per each function? If per function, what would be preferred solution to common functions like fetch and validate from Firestore?
I have found many articles online about cold start but never actually answer to this particular case, or maybe I just misread it.
Also, my knowledge of javascript is limited.
See note at end.
When using require(), you pay the cold start cost of loading the code whenever require() is first used for a particular library which involves fetching the code (and its dependencies) from the disk and evaluating it. Further to this, there is another cold start when you call the initialization method of these libraries.
The Firebase SDKs use the named singleton pattern. When you first invoke
initializeApp(name) (where name defaults to "[DEFAULT]" when omitted) you initialize an instance of the SDK's core functionality for that name. Care must be taken around initializeApp(name) as you can only initialize each named app once without it throwing a "App named X already exists" error. Additionally each component of the Firebase SDK gets initialized the first time you call it for a given app using getFirestore(app) or firestore(app) (where app defaults to the default app when omitted) in the case of Firestore. Unlike initializeApp() these "get component" initializers can be called as many times as you want.
So in your case, calling initializeApp() at the top of both common.js and business.js would actually crash your application. Instead, you should move the initializeApp() into it's own firebase.js file so it is only evaluated once, but can be used in multiple places.
This would mean swapping out these lines:
// business.js / common.js
const admin = require("firebase-admin");
admin.initializeApp();
to:
// business.js / common.js
const admin = require('./firebase.js')
// firebase.js
const admin = require("firebase-admin");
admin.initializeApp();
module.exports = admin;
project/
functions/
lib/
business.js
common.js
firebase.js
functions/
feat1.js
feat2.js
index.js
...
...
As you are new to JavaScript, take the time to learn the new versions of the JavaScript SDKs, known as the Modular SDKs, as these are what will be using going forward in new versions of Firebase. This will require using a compiler step on your code before production, but it does offer the ability to trim off the fat components of the SDK you aren't actually using. It also involves learning the new names for functions such as getFirestore() instead of admin.firestore() and using doc(firestore, path) instead of firestore.doc(path). You should also use ES6 module imports instead of require().

Is it possible to assert the number of reads/writes in a Firebase Cloud Function test?

I have a test suite that runs integration tests for my Firebase Cloud Functions against a locally running Firebase emulator, is it possible to assert the number of reads/writes made to the emulated Firestore instance?
E.g. for the following function I would want to assert that 1 write occurs & 0 reads
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.addMessage = functions.https.onCall(async (data, context) => {
const original = data.query.text;
const snapshot = await admin.firestore().collection("/messages").add({ original: original });
return { code: 200, message: "Success" };
});
Currently, Its not possible to assert the number of reads and writes in a Firebase Cloud Function test.
I would suggest that the best way would be is to write the test data to the Firebase Emulator, then read it back and also count the number of collections/documents as well using Client SDK.
If you want to assert data coming from the Firebase Emulator, then you can use Requests Monitor. We used Client SDK for this purpose as Admin SDK requests and access calls will not be listed because it bypass Security Rules. It's a current limitation according to this Blog.
The Firebase emulator has a log that shows requests as shown in example below.
Note: I don't think that you should have a dependency on it, since Firebase Emulator Request Monitor is a new feature that its implementation may change and Admin SDK can be included over time.

Unsupported Firebase Functions services?

I'm attempting to deploy a minimal Firebase Function based on an authentication trigger. I've worked successfully before with https and database triggers but I'm getting an error while deploying an authentication trigger (docs, reference).
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp();
exports.onCreateUser = functions
.region('europe-west1')
.auth.user()
.onCreate((user: admin.auth.UserRecord, context: functions.EventContext) => {
console.log(`Triggered On Create User: ${user.email}, ${user.uid}.`);
});
I receive the following error:
Ignoring trigger "onCreateUser" because the service "firebaseauth.googleapis.com" is not yet supported.
I've tried changing my engine node version from 10 to 8 or changing the region to europe-west2 or us-central1 but any variations on my configuration would throw this error.
The documentation I've referenced does not mention a limited support for these triggers. Is there any page with an overview of unsupported services and their limitations?
I seem to have mixed up my intent to run serve and deploy here. As I have been told by a very responsive support team the serve script spins up the emulator and is currently limited to the following scopes:
Functions
Firestore
Realtime Database
Hosting
For the development phase, however, you can make use of the interactive shell:
$ firebase functions:shell
✔  functions: Emulator started at http://localhost:5001
i  functions: Loaded functions: onCreateUser
firebase > onCreateUser({"uid":"99999","email":"test#testing.com"})
'Successfully invoked function.'
>  Triggered On Create User: test#testing.com, 99999.
>  Function returned undefined, expected Promise or value

react-native-firebase: TypeError: undefined is not an object

Almost looks like uploading to storage should work but I end up with this error:
TypeError: undefined is not an object (evaluating '_reactNativeFirebase.RNFirebase.storage'
I'm not sure what exactly this means—does it think that the native module is undefined or something?
I've tried two ways to get the storage instance:
import firebase from "react-native-firebase";
// attempt 1
const storage = firebase.initializeApp(config.firebase[Platform.OS], "myApp").storage();
const storage = firebase.storage();
And I put the file into firebase like so:
// `blob` is retrieved by calling `fetch(videoUri)`
storage.put(videoUri, {contentType: blob.data.type});
After that put call occurs, the UploadTask is created but rather than uploading that error gets thrown.
The library already has react-native link called on it, and other RNFirebase features like authentication are working already.
Any sugggestions? Thanks in advance!

Resources