Google Cloud Function/google-auth-library: Cannot read property 'user' of undefined - javascript

Following this: https://medium.com/#nedavniat/how-to-perform-and-schedule-firestore-backups-with-google-cloud-platform-and-nodejs-be44bbcd64ae
Code is:
const functions = require('firebase-functions'); // is installed automatically when you init the project
const { auth } = require('google-auth-library'); // is used to authenticate your request
async function exportDB () {
const admin = await auth.getClient({
scopes: [ // scopes required to make a request
'https://www.googleapis.com/auth/datastore',
'https://www.googleapis.com/auth/cloud-platform'
]
});
const projectId = await auth.getProjectId();
const url = `https://firestore.googleapis.com/v1beta1/projects/${projectId}/databases/(default):exportDocuments`;
return admin.request({
url,
method: 'post',
data: {
outputUriPrefix: 'gs://name-of-the-bucket-you-created-for-backups'
}
});
}
const backup = functions.pubsub.topic('YOUR_TOPIC_NAME_HERE').onPublish(exportDB);
module.exports = { backup };
When I go to deploy via:
gcloud functions deploy backup --runtime nodejs8 --trigger-topic YOUR_TOPIC_NAME_HERE
I get error:
ERROR: (gcloud.functions.deploy) OperationError: code=3,
message=Function failed on loading user code. Error message: Code in
file index.js can't be loaded. Is there a syntax error in your code?
Detailed stack trace: TypeError: Cannot read property 'user' of
undefined
Is this something with google-auth-library?

I assume that you are trying to deploy GCF function triggered by HTTP request, I suggest you to check this link[1] seems is the same use case and can help you to use Google Cloud Datastore with node.js on GCF
[1] How to return an entire Datastore table by name using Node.js on a Google Cloud Function

Related

Send a POST request by axios with firebase functions

I am trying to send a POST request via axios andfirebase cloud function while the body of the request in axios contains data from changes occured in realtime db using the functions but everytime I deploy my code I get the error:
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:
https://cloud.google.com/functions/docs/monitoring/logging#viewing_logs. Additional
troubleshooting documentation can be found at
https://cloud.google.com/functions/docs/troubleshooting#logging. Please visit
https://cloud.google.com/functions/docs/troubleshooting for in-depth troubleshooting
documentation.
I am quite new to firebase functions so I really don't know what I am doing here even after having some research.
Code:
const functions = require("firebase-functions");
const axios = require("axios");
exports.newNodeDetected = functions.database
.ref("Orders/{userId}/{customerId}/{ordernum}/customername")
.onCreate((snapchot, context) => {
const order = snapchot.val();
const userId = context.params.userId;
console.log(userId + "AND CUS NAME IS" + order);
axios.post("http://something.com/data.php", {
username: userId,
title: "ttl",
message: "msg",
})
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.log(error);
});
});
Just in case anyone had the same issue. I realized I was running my npm commands outside the functions folder, therefore the package.json wasn't getting edited and my libraries were not really getting downloaded.
Always be careful to use npm inside the functions folder!!

Firebase messaging warning messages on service worker install process

I'm using firebase for push web notifications, my file firebase-messaging-ws.js is something like this:
importScripts('https://www.gstatic.com/firebasejs/7.19.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/7.19.0/firebase-messaging.js');
fetch('./firebase-data.json')
.then(r => r.json())
.then(fbData => {
let app = firebase.initializeApp(fbData);
firebase.messaging(app);
console.log('Firebase Service Worker loaded for projectId:', fbData.projectId);
}).catch(err => {
console.error('Error configuring firebase messaging: ', err)
});
I'm using a json file, firebase-data.json to load the firebase configuration data and It works, I can receive push notifications, however several log warnings appear in the console when I call to firebase.messaging(), If I use a local object (without fetch() command) then all works OK and there is no warning messages.
The log messages are like Event handler of 'XXX' event must be added on the initial evaluation of worker script.:
Can I avoid the warning messages when I use an external file to load the firebase configuration data ?
Basically a service worker script requires synchronous execution, meaning you can't initialize Firebase Messaging within a promise.
Spitballing here, this is untested, but try this:
importScripts('https://www.gstatic.com/firebasejs/7.19.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/7.19.0/firebase-messaging.js');
const response = await fetch('./firebase-data.json');
const fbData = await response.json();
let app = firebase.initializeApp(fbData);
firebase.messaging(app);
console.log('Firebase Service Worker loaded for projectId:', fbData.projectId);
Alternatively, if it doesn't like await outside of an async function, use JavaScript to export your config data and use importScripts to expose it to your service worker:
firebase-data.js
const fbData = {...};
firebase-messaging-ws.js
importScripts('https://www.gstatic.com/firebasejs/7.19.0/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/7.19.0/firebase-messaging.js');
importScripts('./firebase-data.js');
let app = firebase.initializeApp(fbData);
firebase.messaging(app);
console.log('Firebase Service Worker loaded for projectId:', fbData.projectId);
As the other answer explains, moving the fbData to a js file allow the variable to be used inside the Service Worker script.
Just for the record, I did all of this to share the Firebase data between the Service Worker scope and the Angular application scope, The firebase-data.js is something like this:
const FIREBASE_DATA = {
"apiKey": "***********",
"authDomain": "***********.firebaseapp.com",
"databaseURL": "https://***********.firebaseio.com",
"projectId": "***********",
"storageBucket": "***********.appspot.com",
"messagingSenderId": "***********",
"appId": "1:***************"
}
if (typeof module !== 'undefined') {
module.exports = { ...FIREBASE_DATA }
}
With this implementation I can use self.FIREBASE_DATA inside the service worker file and I can also import the module in my angualr app, por instance mi environment.ts is something like:
import FIREBASE_DATA from "../firebase-data";
export const environment = {
production: true,
url_base: '/api',
firebase: FIREBASE_DATA
};

How to set authDomain when using init.js

I am using the auto SDK URLs to initialize Firebase:
<script src="/__/firebase/6.1.1/firebase-app.js"></script>
<script src="/__/firebase/6.1.1/firebase-auth.js"></script>
<script src="/__/firebase/6.1.1/firebase-storage.js"></script>
<script src="/__/firebase/6.1.1/firebase-messaging.js"></script>
<script src="/__/firebase/6.1.1/firebase-firestore.js"></script>
<script src="/__/firebase/init.js"></script>
I need to set the authDomain to my business domain which is set up on Firebase so that the Google gmail authentication shows my domain name and not my firebase project name.
Do I have to stop using these URLs and instead construct a config and initialize Firebase manually? Or is there a way to update authDomain separately?
I tried the following to see if I could get the config settings and then I planned to modify the authDomain. I received an error instead.
<script>
fetch('/__/firebase/init.json').then(async response => {
firebase.initializeApp(await response.json());
});
</script>
Here is the error from the developer log:
(index):195 Uncaught (in promise) TypeError: Failed to execute 'json' on 'Response': body stream is locked
at (index):195
(anonymous) # (index):195
errors.ts:137 Overwriting FirebaseError base field "name" can cause unexpected behavior.
[UPDATE}
Seems that fetch is asynchronous and that all the scripts load asynchronously as well. Bottom line its darn near impossible to get the config information programmatically, change authDomain and then call initializeApp before loading my framework scripts and initializing my main app which relies on firebase already being there.
Here is the async version i tried:
await fetch('/__/firebase/init.json').then(async (response)=> {
let config = await response.json();
config[authDomain] = 'mydomain.com';
await firebase.initializeApp(config);
});
You should be able to use this, you just need to wait for the fetch() promise to resolve before attempting to do anything with Firebase.
For example
const firebaseReady = fetch('/__/firebase/init.json')
.then(res => res.ok ? res.json() : Promise.reject(res))
.then(config => {
// do whatever you need to do with config
return firebase.initializeApp(config)
})
Now you just need to chain on to the firebaseReady promise. For example
firebaseReady.then(app => {
const db = app.firestore()
const auth = app.auth()
// do other things here
})

Firebase callable functions response not working

I have a simple firebase functions script setup (running firebase-admin version 8.0 and firebase-functions version 2.3.1):
const functions = require('firebase-functions');
const cors = require('cors')({
origin: true,
});
//Gets and returns a user's ip address
exports.getIPAddress = functions.https.onRequest((req, res) => {
let ipAddress = req.headers['fastly-client-ip'] || req.connection.remoteAddress;
ipAddress = ipAddress.toString();
console.log('Fetched IP Address: ' + ipAddress);
return cors(req, res, () => {
res.status(200).send(ipAddress);
});
});
The function's goal is simply to return to user's IP address. It logs fine in the functions console, no errors.
Here is the client code:
var getIPAddress = mainFirebase.functions().httpsCallable('getIPAddress');
function testIP() {
getIPAddress().then(function(result) {
console.log(result.data.text)
});
}
However, the console says that 'result' is not a valid JSON object.
I've tried using https.onCall which somebody else on the internet recommended, however, the console says that function doesn't exist.
Any help getting the response to work properly would be greatly appreciated!
Your function is a regular HTTP type function. However, your client code is attempting to call it as if it were a callable type function. That's not going to work. If you want to invoke a callable type function, you'll have to implement the function according to the documentation.
If you need to keep the function as an HTTP type function, you can't use the Firebase client SDK to invoke it. Just invoke it as if it were any other type of HTTP endpoint.
For Callable functions. You need to create a function like:
exports.addMessage = functions.https.onCall(
async (data, context) => {
// context contains the user info.
}
);
And on your front-end you can call them like:
firebase.functions().httpsCallable('addMessage');
addMessage({text: messageText}).then(function(result) {
// Read result of the Cloud Function.
var sanitizedMessage = result.data.text;
}).catch(function(error) {
// Getting the Error details.
var code = error.code;
var message = error.message;
var details = error.details;
// ...
});
As you are calling an https message. You can also use the SDK to call https methods. But make sure you are handling CORS on your server.
In your client. Just use the http client.
this.http.post method with the function url.

How to access multiple Realtime Database instances in Cloud Functions for Firebase

I'm using multiple databases in a Firebase project. Cloud functions for the main (default) database work great, however, I cannot make them work for a secondary database. For example I want to make a read request on a node with admin privileges:
//this works
admin.database().ref(nodePath).once('value')...
This works in the main database, however, if I want to execute the command on another database, it doesn't work:
//this doesn't work
admin.database(secondaryDatabaseUrl).ref(nodePath).once('value')...
Although the functions are deployed, I get an error on the console when trying to execute the cloud function.
Here's the code for the cloud function with an https trigger:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const secureCompare = require('secure-compare');
exports.testFunction= functions.https.onRequest((req, res) => {
const key = req.query.key;
// Exit if the keys don't match
if (!secureCompare(key, functions.config().cron.key)) {
console.error('keys do not match');
res.status(403).send('error1');
return;
}
//test read request
//the line below crashes the function
return admin.database('https://secondary_db_url.firebaseio.com').ref(`/testNode`).once('value').then(dataSnapshot=> {
console.log('value', dataSnapshot.val());
return;
}).catch(er => {
console.error('error', er);
res.status(403).send('error2');
});
});
Below is the error log in the Firebase console:
TypeError: ns.ensureApp(...).database is not a function
at FirebaseNamespace.fn (/user_code/node_modules/firebase-admin/lib/firebase-namespace.js:251:42)
at exports.testFunction.functions.https.onRequest (/user_code/index.js:16:16)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:26:41)
at /var/tmp/worker/worker.js:671:7
at /var/tmp/worker/worker.js:655:9
at _combinedTickCallback (internal/process/next_tick.js:73:7)
at process._tickDomainCallback (internal/process/next_tick.js:128:9)
If I don't specify the secondary database URL, the function will make the read request on my main database which works great:
//this works
return admin.database().ref(`/testNode`).once('value').then(dataSnapshot=> {
...
I'm using the latest SDK versions: "firebase-admin": "^5.5.1" and "firebase-functions": "^0.7.3"
So, how do I get an instance of a secondary database in cloud functions using admin privileges?
Here's how to access database by URL using Admin SDK:
let app = admin.app();
let ref = app.database('https://secondary_db_url.firebaseio.com').ref();
Here's an example from Admin SDK integration tests: https://github.com/firebase/firebase-admin-node/blob/master/test/integration/database.js#L52
With cloud functions > 1.1 now, here is the documentation link that saved my life on this issue.
https://firebase.google.com/docs/database/usage/sharding#connect_your_app_to_multiple_database_instances
So, it looks like this at the top of my my cloud function index.js :
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
const dev = admin.initializeApp({
databaseURL: "https://appdev.firebaseio.com"
}, 'dev');
const v2 = admin.initializeApp({
databaseURL: "https://appv2.firebaseio.com"
}, 'v2');
and then, in my clond functions functions code I can do :
//will change stuff on default database
admin.database().ref().child(`stuff/${stuffId}`).set(myStuff)
//will change stuff on my dev database
admin.database(dev).ref().child(`stuff/${stuffId}`).set(myStuff)
//will change stuff on my v2 database
admin.database(v2).ref().child(`stuff/${stuffId}`).set(myStuff)
So it looks like you are trying to access multiple databases using the javascript web client API. Passing the URL of the database to the API like this doesn't work with the Admin SDK:
admin.database('https://secondary_db_url.firebaseio.com').ref(`/testNode`)
Instead, you have to initialize a second app, give it a name, and pass that app around to the Admin SDK APIs. Here's a complete sample that writes the same data to two different database instances in the same project:
const functions = require('firebase-functions')
const admin = require('firebase-admin')
admin.initializeApp(functions.config().firebase)
const otherConfig = Object.assign({}, functions.config().firebase)
otherConfig.databaseURL = 'https://your-other-db.firebaseio.com/'
const otherApp = admin.initializeApp(otherConfig, 'otherAppName')
exports.foo = functions.https.onRequest((req, res) => {
const data = { foo: 'bar' }
const p1 = admin.database().ref('data').set(data)
const p2 = admin.database(otherApp).ref('data').set(data)
Promise.all([p1, p2]).then(() => {
res.send("OK")
})
.catch(error => {
res.status(500).send(error)
})
})
Updating this while on Firebase Functions v3.14.0. None of this answers worked for me so I implemented this solution
instance Registers a function that triggers on events from a specific Firebase Realtime Database instance
functions.database.instance('my-app-db-2').ref('/foo/bar')
Use the name of the database instance and it works, no need for the url. functions.database.ref used without instance watches the default instance for events.
So if both the answers doesn't work.
What happened with me is both the method worked without any error but second instance of database was not getting updated.
I updated npm and firebase CLI it worked.
Also #Dough Stevenson you Passing the URL of the database to the API like this **does** work with the Admin SDK
And this is a good blog from Firebase about the same
Firebase Blog : Easier scaling with multi-database support!

Categories

Resources