Why does Firebase Admin SDK have a starting app length of 1? - javascript

I have a NextJS app that uses getStaticProps to pre-fetch data from firebase storage and firestore. Since getStaticProps never runs on the client, I decided to use Firebase Admin SDK to fetch my data. I initialize the app by using a seperate js file that initializes the app if the length of the initialized apps is 0, like so:
import * as admin from "firebase-admin"
if (!admin.app.length) {
try {
admin.initializeApp({
credential: admin.credential.cert({
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY,
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
}),
storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
})
} catch (e) {
console.error("Unable to initialize app!");
console.log(e)
}
}
export default admin;
For some reason, when importing this module and using the admin variable, the app crashes, telling me that I haven't Initialized an app. Doing a console log before the if check shows the the length of admin.app is 1, even though it says that I haven't initialized it.
Why is this the case? I can fix the bug by just using admin.app.length <= 1 as the if check, but I am confused on why an app is initialized before I called initializeApp.

Related

Firebase Realtime Database - "Error: Client is offline". React/NextJS

Using React with NextJS the following error occurs occasionally when fetching data from a Firebase Realtime Database.
Unhandled Runtime Error
Error: Error: Client is offline.
I am using Firebase 9.0.1 for React.
Top Level code for intialisation and config
import { initializeApp } from "firebase/app";
import { getDatabase, ref, onValue, child, get } from "firebase/database";
import CONFIG from '../CONFIG.json'
const FIREBASE_CONFIG = {
apiKey: CONFIG['FIREBASE_API_KEY'],
authDomain: CONFIG['FIREBASE_AUTH_DOMAIN'],
databaseURL: CONFIG['FIREBASE_DATABASE_URL'],
storageBucket: CONFIG['FIREBASE_STORAGE_BUCKET']
}
const fbApp = initializeApp(FIREBASE_CONFIG)
And later fetching data
export default function Leads() {
...
useEffect(() => {
const database = getDatabase(fbApp)
const ads = ref(database, 'ad_results')
get(ads).then((snap) => {
const results = snap.val()
...
I have tried searching similar issues but to no avail, any help would be appreciated.
I had the same issue with my Cloud Functions, which was very confusing. After some hours of debugging, I found out that the .get() method of the Realtime Database was causing this problem. My current workaround is to use instead .once('value').
So I changed my code from:
await database.ref(`foo/bar`).get();
to
await database.ref(`foo/bar`).once('value');
I am using firebase nodejs SDK, also facing the same error: "Error: Error: Client is offline." occasionally.
I have tried the above solution posted by Nils Reichardt. It works for me.
Thanks Nils!

Vue + Firebase: Functions useEmulator() ignored

I found this line of code in the Firebase docs firebase.functions().useEmulator('localhost', 5001) that supposedly points your Vue app to the locally running emulator, but for some reason my project is ignoring said line of code and continues to call the remotely deployed function instead.
Here's what the relevant part of my #/plugins/firebase.js looks like:
import firebase from 'firebase/app';
import 'firebase/functions';
firebase.initializeApp({
apiKey: process.env.VUE_APP_FIREBASE_API_KEY,
authDomain: process.env.VUE_APP_FIREBASE_AUTH_DOMAIN,
databaseURL: process.env.VUE_APP_FIREBASE_DATABASE_URL,
projectId: process.env.VUE_APP_FIREBASE_PROJECT_ID,
storageBucket: process.env.VUE_APP_FIREBASE_STORAGE_BUCKET,
messagingSenderId: process.VUE_APP_FIREBASE_MESSAGE_SENDER_ID,
appId: process.env.VUE_APP_FIREBASE_APP_ID,
measurementId: process.env.VUE_APP_FIREBASE_MEASUREMENT_ID
});
firebase.functions().useEmulator('localhost', 5001);
const func = {
botcheck: firebase.app().functions('europe-west2').httpsCallable('validateRecaptcha'),
};
export { func };
And then to call the botcheck function, I'll run the following in a Vuex action:
const fb = require('#/plugins/firebase');
...
await fb.func.botcheck();
What am I doing wrong? How do I get it to correctly point to my locally running emulator?
Vue project versions:
vue: 2.6.11
firebase: 8.3.2
Functions project versions:
firebase-admin: 9.2.0
firebase-functions: 3.11.0
Let me know if I need to include additional information.
This line:
firebase.functions()
is functionally equivalent to:
firebase.app().functions('us-central1')
In your current code, you connect functions that don't specify a region to the emulator. Because you specify the region as europe-west2 when using it, you need to connect the europe-west2 functions to the emulator. You can do this by changing this line:
firebase.functions().useEmulator('localhost', 5001);
to use the correct region:
firebase.app().functions('europe-west2').useEmulator('localhost', 5001)
Additional Note: While firebase.functions() and firebase.app().functions() return the same instance of a Functions object (connected to the us-central1 region), firebase.app().functions('us-central1') (where you pass in the region) returns a different instance of Functions. You would need to connect each instance that you use to the emulator.
Here's your code as I make sure that useEmulator() is configured properly with Cloud Functions for Firebase Emulator. Feel free to try it:
import firebase from 'firebase/app';
import 'firebase/functions';
const firebaseConfig = {
// Your config
};
const app = firebase.initializeApp(firebaseConfig);
const functions = app.functions('europe-west2');
functions.useEmulator('localhost', 5001);
const func = {
botcheck: functions.httpsCallable('validateRecaptcha'),
};
export { func };

'No Firebase App '[DEFAULT]' has been created' even though initializeApp is called

I am trying to add Firebase (Firestore) to my Nuxt project, however I am recieving the following error when initialising a const from firebase.firestore() in my index.vue file:
Uncaught FirebaseError: Firebase: No Firebase App '[DEFAULT]' has been
created - call Firebase App.initializeApp() (app/no-app).
I have installed Firebase in my project and also the module (#nuxtjs/firebase).
My nuxt.config.js file looks like this:
export default {
...
plugins: ['~/plugins/firebase.js'],
components: true,
buildModules: [
'#nuxt/typescript-build',
'#nuxtjs/tailwindcss',
],
modules: [],
...
}
And my firebase.js file is within my plugins folder as follows:
import firebase from 'firebase/app'
const config = {
...
}
let app = null
if (!firebase.apps.length) {
app = firebase.initializeApp(config)
}
export default firebase
I've compared the above to other examples online and haven't spotted any issues. However I'm new to everything from Nuxt to Firebase, so I may be missing something obvious. Any suggestions appreciated.
This typically happens if you call initializeApp() more than once on a single firebase app. If you're working with a single firebase db, make sure to initialize it when your app starts.
According to this GitHub Discussion this snippet for firebase.js should work:
import fb from "firebase/app"
export const firebase = !fb.apps.length ? fb.initializeApp(firebaseConfig) : fb.app()
// somecomponent.js
import {firebase} from "../firebase.js"
// do your firebase stuff here
Credit to #Brunocrosier with his post; even though this snippet isn't case-specific, I decided to include it for the sake of completeness.
Besides this thread - generally speaking, Uncaught FirebaseError: Firebase: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp() (app/no-app). is often a result of of either calling firebase. before initializing via .initializeApp(); or by calling .initializeApp() multiple times (for example Next.js might try to initialize it on the back- as well as the frontend - which seems to be the case in your code) within your firebase app.
Hence as a solution I highly suggest to move your initialization to the firebase.js file in order to initialize it directly when your app starts.
For further reading purposes:
The nuxt/firebase documentation
Error: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp()
The previously mentioned GitHub Discussion
FireBase JavaScript documentation
This normally happens when you try to access firestore before initializing the firebase. So as default we can check if firebase is initialized or not by firebase.apps.length but it's not a good practice to initialize firebase each and every time.
so if you are only using firestore then in your plugin you can export firestore directly after initialization like following,
import firebase from 'firebase/app'
const config = {
...
}
let app = null
if (!firebase.apps.length) {
app = firebase.initializeApp(config)
}
export default firebase.firestore()
But since you are working with nuxt there is special nuxt package called firebase/nuxt
with that installed you can define your configuration in nuxt config inside the module section as bellow,
modules: [
[
'#nuxtjs/firebase',
{
config: {
apiKey: '<apiKey>',
authDomain: '<authDomain>',
databaseURL: '<databaseURL>',
projectId: '<projectId>',
storageBucket: '<storageBucket>',
messagingSenderId: '<messagingSenderId>',
appId: '<appId>',
measurementId: '<measurementId>'
},
services: {
auth: true // Just as example. Can be any other service.
}
}
]
],
I think it is a better way to use firebase inside the nuxt.js

firebase.storage() takes either no argument or a Firebase App instance

Actually, the title is more or less the whole explanation of the problem.
I am trying to use Firebase inside my React app, which also uses NextJS and the problem is that I cannot get storage to work.
import firebase from 'firebase'
import uuid from 'uuid/v4'
// Init
try {
firebase.initializeApp({
apiKey: 'apiKey',
authDomain: 'authDomain',
databaseURL: 'dbUrl',
projectId: 'projID',
storageBucket: 'storageBucket',
messagingSenderId: 'id'
})
} catch (err) {
if (!/already exists/.test(err.message)) {
console.error('Firebase initialization error', err.stack)
}
}
console.log(firebase.app().name) // <- name
// References
const database = firebase.database()
const storage = firebase.storage().ref() // <- the problem
const documentImageStorage = storage.child('images/')
const documentsRef = database.ref('/documents/')
const documentsRequestsRef = database.ref('/requests/')
So, as I run the code I can confirm that the app works as the database works properly and the name ([default]) is returned correctly, but the line in which the storage reference is defined returns an error:
Firebase: firebase.storage() takes either no argument or a Firebase App instance. (app/invalid-app-argument).
Any ideas why this might happen? How can I solve it?
(Firebase Storage JS dev)
I was able to reproduce your error in Next.js. I'm not super familiar with it, but I understand Next.js does React-y server-side rendering, so the code you write for your page will generally be executed in the node server.
Unfortunately, Storage isn't supported in node right now, which includes server-side rendering contexts (feel free to leave a comment in the Github issue about your use case).
It should work in normal React apps (i.e. client-side code) though.
EDIT: found a (barely) workaround
The code appears to not crash in Next.js if you add an import at the top of the file:
import firebase from 'firebase'
import _s from 'firebase/storage'
import uuid from 'uuid/v4'
...
Regardless, the Storage library still isn't supported in node. Most anything interesting (uploading objects, getting object metadata) won't work, so unless all you wanted to do was call storage.toString() somewhere this probably doesn't solve your problem.
Firebase docs states that you save storage service reference to variable, then save storage reference to different variable.
https://firebase.google.com/docs/storage/web/create-reference
So I would do this like so:
const storage = firebase.storage();
const storageRef = storage.ref();
You can also put path to your storage folder to get reference for it, like this:
const documentImageStorage = storage.ref('/images/');
Ok, so I figured out how to make it work!
export const storage = process.browser ? firebase.storage().ref() : undefined
This way, storage part, which is unavailable on the backend isn't loaded, but on the frontend it is and everything works perfectly!
Thanks for both answers!

Initialize Firebase references in two separate files in the new API

I have upgraded to the new API and don't know how to initialize Firebase references in two separate files:
/* CASE 1 */
// 1st file
var config = {/* ... */};
firebase.initializeApp(config);
var rootRef = firebase.database().ref();
// 2nd file - initialize again
var config = {/* ... */};
firebase.initializeApp(config);
var rootRef = firebase.database().ref();
RESULT: bundle.js:535 Uncaught Error: Firebase App named '[DEFAULT]' already exists.
/* CASE 2 */
// 1st file
var config = {/* ... */};
firebase.initializeApp(config);
var rootRef = firebase.database().ref();
// 2nd file - don't initialize
var rootRef = firebase.database().ref();
RESULT: bundle.js:529 Uncaught Error: No Firebase App '[DEFAULT]' has been created - call Firebase App.initializeApp().
Before the new API I just called
var myFirebaseRef = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com/");
in each file, and it worked okay.
This is an issue I ran into as well upgrading to the new version of Firebase. You might want two separate firebase apps initialized, like explained in other answers, but I just wanted to use the refs in two different locations in my app and I was getting the same error.
What you need to do for this situation is to create a firebase module for your app that only initializes firebase once, then you import or require it elsewhere in your app.
This is pretty simple, here is mine: modules/firebase.js
import firebase from 'firebase';
var firebaseConfig = {
apiKey: "some-api-key",
authDomain: "some-app.firebaseapp.com",
databaseURL: "https://some-app.firebaseio.com",
storageBucket: "some-app.appspot.com",
};
var FbApp = firebase.initializeApp(firebaseConfig);
module.exports.FBApp = FbApp.database(); //this doesnt have to be database only
And then elsewhere in your application you simply:
import FBApp from '/your/module/location'
var messagesRef = FBApp.ref("messages/");
You need to name your different instances (Apps as Firebase calls them); by default you're working with the [DEFAULT] App, because that's the most common use case, but when you need to work with multiple Apps then you have to add a name when initialising:
// Intialize the "[DEFAULT]" App
var mainApp = firebase.intializeApp({ ... });
// Intialize a "Secondary" App
var secondaryApp = firebase.initializeApp({ ... }, "Secondary");
...
mainApp.database().ref("path/to/data").set(value);
secondaryApp.database().ref("path/to/data").set(anotherValue);
You can find a more example scenarios in the updated Initialize multiple apps section of the Add Firebase to your JavaScript Project guide.
If you don't have the control over where Firebase will be instantiated, you can do something like this:
try {
let firApp = firebase.app(applicationName);
return firApp;
} catch (error) {
return firebase.initializeApp({
credential: firebase.credential.cert(firebaseCredentials),
databaseURL: firebaseUrl
}, applicationName);
}
Firebase will try to get the application, if it doesn't exist, then you can initialize it freely.
I made the mistake by importing like this.
import firebase from 'firebase'
const firebaseConfig = {
apiKey: 'key',
authDomain: 'domain',
databaseURL: 'url',
storageBucket: ''
};
firebase.initializeApp(firebaseConfig);
This worked fine for a few days but when I tried to sign in with custom tokens my auth object was not changed. I had to refresh the page for it to update so I could make certain calls to the database which were protected by my own auth credentials rules.
".read": "$uid === auth.uid || auth.isAdmin === true || auth.isTeacher === true",
When I changed my imports to this it worked again.
import firebase from 'firebase/app';
import 'firebase/auth';
import 'firebase/database';
const firebaseConfig = {
apiKey: 'key',
authDomain: 'domain',
databaseURL: 'url',
storageBucket: ''
};
firebase.initializeApp(firebaseConfig);
Then whenever I need to use Firebase in a certain module I import this(notice the import from firebase/app instead of firebase):
import firebase from 'firebase/app';
And talk to certain services like so:
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// Authenticated.
} else {
// Logged out.
}
});
firebase.database().ref('myref').once('value').then((snapshot) => {
// do stuff with the snapshot
});
To make multiple instances using new firebase.initializeApp(), you need a second parameter for the firebase constructor:
firebase.initializeApp( {}, "second parameter" );
Compare it to the old way to generate multiple instances where
new Firebase.Context()
is the second parameter:
new Firebase('', new Firebase.Context() );
For a small fraction of the people here, this issue might be cause by trying to initialize fb admin on the same script that you used to initialize fb on the front end. If anybody is initializing firebase twice on the same script (once for admin and once for frontend), then you need initialize firebase admin in a different script than in front end, and do not import anything that is exported on the backend script on the frontend (and vise versa).

Categories

Resources