I'm having this problem that I've spent hours looking into but can't seem to figure out. I'm trying to use Firebase with Node.js, but am unable to access the different Firebase functions (e.g. firebase.auth() and firebase.firestore()). However, when I initialize via the admin SDK, those end up working. Below is my simple code as well as the error:
const functions = require('firebase-functions');
const firebase = require('firebase');
const app = express();
firebase.initializeApp(functions.config().firebase);
const auth = firebase.auth();
const db = firebase.firestore();
And the resulting error: TypeError: firebase.auth is not a function
Note I also had a few lines initializing express and handlebars but doubt those would have had any impact.
I've also tried including "require("firebase/auth")" but that hasn't worked. Below is also my package.json, specifically the modules I have installed:
"dependencies": {
"consolidate": "^0.15.1",
"express": "^4.16.3",
"firebase": "^5.0.4",
"firebase-admin": "~5.12.1",
"firebase-functions": "^1.0.4",
"firebase-tools": "^3.18.6",
"firebaseauth": "^1.0.0",
"handlebars": "^4.0.11"
},
Any help please? I'm a bit of a novice web developer so any advice would be much appreciated. Thank you!
Cloud Functions triggers have a format like this:
exports.myFunctionName = functions.firestore
.document('users/marie').onWrite((change, context) => {
// ... Your code here
});
You cannot access the client-side SDKs from inside a Cloud Function. Instead, if there are features you want to access inside a Cloud Function, you can use the Admin SDKs.
var admin = require('firebase-admin');
var app = admin.initializeApp();
Short answer: Use the admin SDK, that's what it's for - Cloud Functions to interact with the rest of your backend.
Longer answer:
To help you along, here is an example of a working index.js file for a Firebase Cloud Function that accesses both the database (not firestore) and the auth portion of Firebase... with some comments to provide context. It's an API endpoint that accepts a user's token, verifies that the user is an 'admin' in my system, and then creates a new user account before responding with the new UID.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.createUser = functions.https.onRequest( (req, res) => {
// Look at the accessToken provided in the request, and have Firebase verify whether it's valid or not
admin.auth().verifyIdToken(req.body.accessToken).then(decodedIdToken => {
// Token was valid, pull the UID out of the token for the user making this request
let requestorUid = decodedIdToken.uid;
// Make a query to the database to see what that user's role is
admin.database().ref(`userBasicInfo/${requestorUid}/role`).once('value').then( dataSnapshot => {
if (dataSnapshot.val() === 'admin') {
// If their role is "admin" then let's create a user!
let userEmail = `${req.body.newuserid}#company.org`;
admin.auth().createUser({
email: userEmail,
password: req.body.password
}).then(userRecord => {
// See the UserRecord reference doc for the contents of userRecord.
res.send({uid: userRecord.uid});
}).catch( error => {
console.log("Error creating user:");
console.log(error)
res.send(false);
});
} else {
console.log('Checked the database for the UID provided in the access token, role was not "admin".');
res.send(false);
}
}).catch( err => {
console.log('Error finding user in database:');
console.log(err);
res.send(false);
})
}).catch( error => {
console.error('Error while verifying Firebase ID token:');
console.log(error);
res.send(false);
});
});
And the package.json would be:
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"serve": "firebase serve --only functions",
"shell": "firebase experimental:functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"dependencies": {
"firebase-admin": "^5.11.0",
"firebase-functions": "^1.0.2"
},
"private": true
}
Related
I am trying to read data in Firebase - Cloud Firestore database in Dialogflow. But not able to connect to the database and getting the following error in log:
Warning, estimating Firebase Config based on GCLOUD_PROJECT.
Initializing firebase-admin may fail
Code is in welcome intent fulfillment - Inline Editor(Powered by Cloud Functions for Firebase)
Collection Name in Cloud Firestore - users
document - 10 and 11 (totally 2)
field - book_id and text
Can someone please help me resolve this one or face similar issue.?
I have tried different solutions provided for this error faced by others, but none of it has solved mine.
Tried changing node version to 8 in package.json
Tried Initializeapp as below:
const admin = require('firebase-admin');
const app = dialogflow({debug: true});
admin.initializeApp();
Its not going inside app.intent(welcome) itself
index.js
// See https://github.com/dialogflow/dialogflow-fulfillment-nodejs
// for Dialogflow fulfillment library docs, samples, and to report issues
'use strict';
const axios = require('axios');
const {dialogflow} = require('actions-on-google');
const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment');
const admin = require('firebase-admin');
const app = dialogflow({debug: true});
admin.initializeApp();
const db = admin.firestore();
db.settings({timestampsInSnapshots: true});
const collectionRef = db.collection('users');
process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements
exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
const agent = new WebhookClient({ request, response });
console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers));
console.log('Dialogflow Request body: ' + JSON.stringify(request.body));
/*function welcome(agent) {
agent.add(`Welcome to my agent!`);
}*/
function fallback(agent) {
agent.add(`I didn't understand`);
agent.add(`I'm sorry, can you try again?`);
}
app.intent('welcome', (conv) => {
console.log("welcome agent invoked");
const book = (agent.parameters.book).toString();
const termRef = collectionRef.doc('10');
return termRef.get()
.then((snapshot) => {
const {text, book_id} = snapshot.data();
conv.ask(`Here you go, ${text}, ${book_id}. ` +
`What else do you want to know?`);
console.log('text:', +text);
console.log('book_id:', +book_id);
}).catch((e) => {
console.log('error:', e);
conv.close('Sorry, try again');
});
});
});
package.json
{
"name": "dialogflowFirebaseFulfillment",
"description": "This is the default fulfillment for a Dialogflow agents using Cloud Functions for Firebase",
"version": "0.0.1",
"private": true,
"license": "Apache Version 2.0",
"author": "Google Inc.",
"engines": {
"node": "8"
},
"scripts": {
"start": "firebase serve --only functions:dialogflowFirebaseFulfillment",
"deploy": "firebase deploy --only functions:dialogflowFirebaseFulfillment"
},
"dependencies": {
"actions-on-google": "^2.2.0",
"firebase-admin": "^5.13.1",
"firebase-functions": "^2.0.2",
"dialogflow": "^0.6.0",
"dialogflow-fulfillment": "^0.5.0"
}
}
Trying to get book_id value and text value from collection users in cloud firestore
Once a new vendor is registered on my Firebase Realtime Database, I want to send the new vendor a welcome email via Sendgrid. I've constructed a Firebase function newVendorEmail() to do this in my app's functions/src/index.ts folder and configured everything there as per https://app.sendgrid.com/guide/integrate/langs/nodejs/verify. I'm also able to retrieve vendor details from Firebase via onCreate() in newVendorEmail() and pass them to the dynamic_template_data part of the msg object without any problem. But when the newVendorEmail() function was triggered in Firebase Functions the email was not sent and I got this response instead in my Firebase Functions Console: TypeError: Object.values is not a function at Mail.setDynamicTemplateData (/user_code/node_modules/#sendgrid/mail/node_modules/#sendgrid/helpers/classes/mail.js:342:12). Help, please?
I've tried upgrading to the latest #sendgrid/mail npm package v6.4.0, tried switching to a new Sendgrid API key, tried storing this new API key in process.env as per Sendgrid's github example https://github.com/sendgrid/sendgrid-nodejs/blob/master/use-cases/kitchen-sink.md instead of functions.config(), but to no avail.
in node/process.env:
{ SENDGRID_API_KEY:
'SG....E',
...
}
in functions/src/index.ts:
'use strict'
const functions = require('firebase-functions')
const admin = require('firebase-admin')
const sendgrid = require('#sendgrid/mail')
// init function
admin.initializeApp()
//init firebase ref const
const ref = admin.database().ref()
// set sendgrid api from process env
sendgrid.setApiKey(process.env.SENDGRID_API_KEY)
export const newVendorEmail = functions.database
.ref('users/{userId}/profile')
.onCreate((snapshot, context) => {
// call field data using snapshot.val()
let msg
const userData = snapshot.val()
if (userData.type === 'vendor') {
// set email data
msg = {
to: userData.email,
from: {
name: 'Blk. Party',
email: '...#blkparty.com'
},
// custom templates
templateId: '...',
dynamic_template_data: {
subject: 'Welcome to Blk. Party!',
name: userData.name,
regLink: userData.regLink
},
}
}
// send email via sendgrid
return sendgrid.send(msg)
})
in package.json:
...
"dependencies": {
"#sendgrid/mail": "^6.4.0",
"firebase-admin": "~6.0.0",
"firebase-functions": "^2.1.0"
},
"devDependencies": {
"#sendgrid/mail": "^6.4.0",
...
}
...
I expect emails to be sent without any error.
I had the same problem. In my case, the solution was to switch from node6 to node8 in firebase functions.
I was trying cloud functions today, followed the docs and got it to deploy successfully but the logs will always be empty.
I am using RN firebase client SDK from this website
https://rnfirebase.io/docs/v5.x.x/functions/reference/functions
"dependencies": {
"firebase-admin": "^7.0.0",
"firebase-functions": "^2.2.0",
}
This is my function code :
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) => {
console.log('hello world');
response.send("Hello from Firebase yo!");
});
this is my dashboard screen shot:
this is my logs screen shot:
Did i miss anything ?
To make sure this is as clean and isolated as possible, I created a new test project with nothing in it other than these steps.
I then enabled cloud firestore in the console. I created a new collection called "blarg" (great name, eh!). I added a document to it, and used the auto-id, then added a field named "humpty" with the string dumpty. It looks like this in the console:
I created a directory called signatures in the storage section of the firebase console. I want to write a file into this directory when my function (below) triggers.
I then added a cloud function with the following code. It shows up (I assume) correctly in the functions section, with the name testItOut and triggering on the event document.update for /blarg/{eventId}.:
const functions = require('firebase-functions');
const os = require('os');
const fs = require('fs');
const path = require('path');
require('firebase-admin').initializeApp();
exports.testItOut = functions.firestore
.document('blarg/{docId}')
.onUpdate((change, context) => {
console.log( "Inside #testItOut" );
const projectId = 'testing-60477';
const Storage = require('#google-cloud/storage');
const storage = new Storage({
projectId,
});
let bucketName = 'signatures';
let fileName = 'temp.txt';
const tempFilePath = path.join(os.tmpdir(), fileName);
console.log( `Writing out to ${tempFilePath}` );
fs.writeFileSync(tempFilePath, "something!" );
return storage
.bucket( bucketName )
.upload( tempFilePath )
.then( () => fs.unlinkSync(tempFilePath) )
.catch(err => console.error('ERROR inside upload: ', err) );
});
The package.json looks like this:
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"lint": "eslint .",
"serve": "firebase serve --only functions",
"shell": "firebase functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"dependencies": {
"#google-cloud/storage": "^1.7.0",
"firebase-admin": "~5.12.1",
"firebase-functions": "^1.0.3"
},
"devDependencies": {
"eslint": "^4.12.0",
"eslint-plugin-promise": "^3.6.0"
},
"private": true
}
If I change the value of the key "humpty" I see the function invocation. But, I get the error inside the logs.
ERROR inside upload: { ApiError: testing-60477#appspot.gserviceaccount.com does not have storage.objects.create access to signatures/temp.txt.
at Object.parseHttpRespBody (/user_code/node_modules/#google-cloud/storage/node_modules/#google-cloud/common/src/util.js:193:30)
at Object.handleResp (/user_code/node_modules/#google-cloud/storage/node_modules/#google-cloud/common/src/util.js:131:18)
...
This is as simple as it can get, I'd assume. What am I doing wrong? I thought calling initializeApp() gave my function rights to write to the storage bucket for the account automatically?
The only strange error I see is that billing is not setup. I thought this was necessary only if you use external APIs. Is Google Storage an "external API?" The log message does not indicate that's the issue.
The issue was that I mistakenly thought the call to bucket was to set the subdirectory inside the bucket. Instead of bucket('signatures') I should have made it an empty call like bucket() and then provided an options parameter (like { destination: '/signatures/temp.txt' }) for the upload call:
const functions = require('firebase-functions');
const os = require('os');
const fs = require('fs');
const path = require('path');
const admin = require('firebase-admin');
admin.initializeApp();
exports.testItOut = functions.firestore
.document('blarg/{docId}')
.onUpdate((change, context) => {
console.log( "Inside #testItOut" );
const storage = admin.storage()
let fileName = 'temp.txt';
let destination = '/signatures/temp.txt';
const tempFilePath = path.join(os.tmpdir(), fileName);
console.log( `Writing out to ${tempFilePath}` );
fs.writeFileSync(tempFilePath, "something!" );
return storage
.bucket()
.upload( tempFilePath, { destination } )
.then( () => fs.unlinkSync(tempFilePath) )
.catch(err => console.error('ERROR inside upload: ', err) );
});
I got confused because I saw lots of examples in firebase docs where there are calls to bucket("albums") (like this: https://cloud.google.com/nodejs/docs/reference/storage/1.6.x/Bucket#getFiles) and assumed this meant the bucket can be used to set a subdirectory for uploads. It's obvious to me now the difference, but I was thrown off because the calls to bucket did not look like bucketname-12345.appspot.com as I see in the firebase storage console.
Don't bother trying to use the Google Cloud Storage SDK like this:
const Storage = require('#google-cloud/storage');
const storage = new Storage({
projectId,
});
Instead, just use the Admin SDK after you initialize it. The Admin SDK wraps the Google Cloud Storage SDK. When you initialize admin, you are also implicitly initializing all of the components it wraps.
const admin = require('firebase-admin')
admin.initializeApp()
const storage = admin.storage()
// Now use storage just like you would use #google-cloud/storage
The default service account that's used when you init admin with no args like this should have write access to your project's default storage bucket.
I can successfully deploy the HelloWord application fine as per the video guide here.
But now I was following the instructions here in order to link Algolia and Firebase with Cloud Functions, I can run the code on my computer and it updates, but somehow it does not auto-update when I make changes on Firebase.
Secondly, when I deploy the application I get the "deploy complete" message but nothing appears in Firebase>>Functions and Algolia is not updated when I make changes to Firebase.
index.js code
const algoliasearch = require('algoliasearch');
const dotenv = require('dotenv');
const firebase = require('firebase');
// load values from the .env file in this directory into process.env
dotenv.load();
// configure firebase
firebase.initializeApp({
databaseURL: process.env.FIREBASE_DATABASE_URL,
});
const database = firebase.database();
// configure algolia
const algolia = algoliasearch(
process.env.ALGOLIA_APP_ID,
process.env.ALGOLIA_API_KEY
);
const index = algolia.initIndex(process.env.ALGOLIA_INDEX_NAME);
//synchronize firebase database with algolia index
const contactsRef = database.ref('/contactDetail/botswana');
contactsRef.on('child_added', addOrUpdateIndexRecord);
contactsRef.on('child_changed', addOrUpdateIndexRecord);
contactsRef.on('child_removed', deleteIndexRecord);
function addOrUpdateIndexRecord(contact) {
// Get Firebase object
const record = contact.val();
// Specify Algolia's objectID using the Firebase object key
record.objectID = contact.key;
// Add or update object
index
.saveObject(record)
.then(() => {
console.log('Firebase object indexed in Algolia', record.objectID);
})
.catch(error => {
console.error('Error when indexing contact into Algolia', error);
process.exit(1);
});
}
function deleteIndexRecord(contact) {
// Get Algolia's objectID from the Firebase object key
const objectID = contact.key;
// Remove the object from Algolia
index
.deleteObject(objectID)
.then(() => {
console.log('Firebase object deleted from Algolia', objectID);
})
.catch(error => {
console.error('Error when deleting contact from Algolia', error);
process.exit(1);
});
}
package.json code
{
"name": "functions",
"description": "Cloud Functions for Firebase",
"scripts": {
"serve": "firebase serve --only functions",
"shell": "firebase experimental:functions:shell",
"start": "npm run shell",
"deploy": "firebase deploy --only functions",
"logs": "firebase functions:log"
},
"dependencies": {
"algoliasearch": "^3.24.9",
"dotenv": "^4.0.0",
"firebase": "^4.8.1",
"firebase-admin": "~5.4.2",
"firebase-functions": "^0.7.1"
},
"private": true
}
Did you check the logs in the Functions section ? In order to see changes in Algolia, you should first see the function appearing in your firebase console.
I had to make few changes to the code before it runs
functions needed to be exported using "exports"
functions does not use .on() as listener instead use e.g. onCreate() with one parameter, more here. See below
exports.childAdded=contactsRef.onCreate(addOrUpdateIndexRecord);
Solution:
const algoliasearch = require('algoliasearch');
const dotenv = require('dotenv');
const functions = require('firebase-functions');
// load values from the .env file in this directory into process.env
dotenv.load();
// configure algolia
const algolia = algoliasearch(
process.env.ALGOLIA_APP_ID,
process.env.ALGOLIA_API_KEY
);
const index = algolia.initIndex(process.env.ALGOLIA_INDEX_NAME);
//synchronize firebase database with algolia index
const contactsRef = functions.database.ref('/contactDetail/locationA/{contactID}');
exports.childAdded=contactsRef.onCreate(addOrUpdateIndexRecord);
exports.childChanged=contactsRef.onUpdate(addOrUpdateIndexRecord);
exports.childRemoved=contactsRef.onDelete(deleteIndexRecord);
function addOrUpdateIndexRecord(contact) {
console.log("The function addOrUpdateIndexRecord is running!");
// Get Firebase object
const record = contact.data.val();
// Specify Algolia's objectID using the Firebase object key
record.objectID = contact.data.key;
// Add or update object
return index
.saveObject(record)
.then(() => {
console.log('Firebase object indexed in Algolia', record.objectID);
})
.catch(error => {
console.error('Error when indexing contact into Algolia', error);
process.exit(1);
});
}
function deleteIndexRecord(contact) {
// Get Algolia's objectID from the Firebase object key
const objectID = contact.data.key;
// Remove the object from Algolia
return index
.deleteObject(objectID)
.then(() => {
console.log('Firebase object deleted from Algolia', objectID);
})
.catch(error => {
console.error('Error when deleting contact from Algolia', error);
process.exit(1);
});
}