Platform exception while using google cloud function - javascript

I am making an app in flutter and using cloud function in firebase to update a number in Firestone,
index.js
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require('firebase-functions');
// The Firebase Admin SDK to access Cloud Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
exports.getRandomNumbers=functions.https.onCall((context)=>
{
const numbers=admin.firestore().collection('RandomNumbers').document('CurrentRandomNumber');
return numbers.add({
RandomNumber=5;
});
});
and this is the function
onPressed: () async{
final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(
functionName: 'getRandomNumbers',
);
await callable.call().catchError((error){
print('$error');
});
},
The error that is coming is
PlatformException(functionsError, Cloud function failed with exception., {code: NOT_FOUND, details: null, message: NOT_FOUND})
I also found these in my log which I may think is the reason of this
W/DynamiteModule(12544): Local module descriptor class for providerinstaller not found.
I/DynamiteModule(12544): Considering local module providerinstaller:0 and remote module providerinstaller:0
W/ProviderInstaller(12544): Failed to load providerinstaller module: No acceptable module found. Local version is 0 and remote version is 0.
NOTE
I have updated my Google Play services to the latest.
I have taken permission of the INTERNET in the manifest file.
redownloaded the .json file and checked

The code in your Callable Cloud Function is not correct.
By doing
const numbers = admin.firestore().collection('RandomNumbers').document('CurrentRandomNumber');
you are declaring a DocumentReference. A DocumentReference does not have an add() method.
If you want to get the value of a specific field of this document you should use the asynchronous get() method, as follows:
exports.getRandomNumbers = functions.https.onCall((data, context) => {
const numbers = admin.firestore().collection('RandomNumbers').document('CurrentRandomNumber');
return numbers.get().then(documentSnapshot => {
if (documentSnapshot.exists) {
console.log('Document retrieved successfully.');
const aNumber = documentSnapshot.data().aNumber;
// Do something
// ...
return null; // or return the promise returned by an asynchronous method call,
// see https://firebase.google.com/docs/functions/terminate-functions?authuser=1
} else {
console.log('Document does not exist.');
return null;
}
})
});
If you want to increment the value of a specific field of the Firestore doc, you should probably use a Transaction (it depends on your exact use case).

Related

How to delete a Firebase Storage folder from a Firebase Cloud Function?

I couldn't find the deleteFiles() method in the Firebase API reference. My IDE tells me this method takes an optional DeleteFilesOptions argument and I couldn't find any information on that type as well. If someone could point me to this documentation I would appreciate it.
That said, I've seen a number of posts that use this method, with this argument, to delete an entire Storage folder (and all of its files) through a Cloud Function. My question is, is this the correct way to do it (since the documentation here is missing)?
const functions = require("firebase-functions");
const admin = require("firebase-admin");
exports.deleteStorageFolder = functions.https.onCall(async (data, _context) => {
const uid = data.userId;
try {
const bucket = admin.storage().bucket(); // returns the default bucket, which is good
await bucket.deleteFiles({
prefix: `images/users/${uid}`, // the path of the folder
});
return Promise.resolve(true);
} catch (error) {
throw new functions.https.HttpsError("unknown", "Failed to delete storage folder.", error);
}
});
As #Doug already mentioned in the comment, "Firebase just provides wrappers around Cloud Storage. They are the same thing.". Also, according to this documentation, "Cloud Storage for Firebase stores your files in a Google Cloud Storage bucket, making them accessible through both Firebase and Google Cloud. This allows you the flexibility to upload and download files from mobile clients via the Firebase SDKs for Cloud Storage."
Having been said that, I've tried replicating the code snippet you've provided using deleteFiles(), and it worked fine on my end:
// // The Firebase Admin SDK to access Firestore.
const functions = require("firebase-functions");
const admin = require('firebase-admin');
const firebaseConfig = {
// Your Firebase configuration...
};
admin.initializeApp(firebaseConfig);
const bucket = admin.storage().bucket();
async function deleteFolder(){
await bucket.deleteFiles({
prefix: 'images/users/${uid}' // the path of the folder
});
}
deleteFolder();
One another option that you can do is to directly use Google Cloud Storage, and skip using the Firebase Storage:
const {Storage} = require('#google-cloud/storage');
const storage = new Storage();
const bucket = storage.bucket("your-bucket-name");
bucket.deleteFiles({
prefix: 'images/users/${uid}'
}, function(err) {
if (!err) {
console.log("All files in the `images` directory have been deleted.");
}
});
Just a note, following the suggestion of Doug, you can try and test it out first in your local or test environment. For further reference, you can refer to delete() and deleteFiles()

How do I receive the context parameter in the a cloud function onCall?

I have a cloud function that is called by my front-side app in React Native. Here is my cloud function:
exports.listProducts = functions.https.onCall((temp, context) => {
console.log("fired");
return context;
});
And here is how I call the function:
let temp = await functions().httpsCallable("listProducts")(
null,
firebase.auth().currentUser
);
However, I can't acccess the context variable in my cloud function which according to docs should be the Firebase auth. And the console.log in that function does not give me a log.
Any parameters you pass to the callable function are available in the first parameter, which you call temp, but that the Firebase documentation refers to as data.
You don't need to pass the current user to the callable function yourself, as the Firebase SDK already handles that for you, and the server already decoded the current user into context.auth.
For more on both parameters and the auth context, have a look at the Firebase documentation on writing and deploying callable functions, which contains:
Use functions.https.onCall to create an HTTPS callable function. This method takes two parameters: data, and optional context:
// Saves a message to the Firebase Realtime Database but sanitizes the text by removing swearwords.
exports.addMessage = functions.https.onCall((data, context) => {
// ...
});
For a callable function that saves a text message to the Realtime Database, for example, data could contain the message text, while context parameters represent user auth information:
// Message text passed from the client.
const text = data.text;
// Authentication / user information is automatically added to the request.
const uid = context.auth.uid;
const name = context.auth.token.name || null;
const picture = context.auth.token.picture || null;
const email = context.auth.token.email || null;

FirebaseError: [code=invalid-argument]: Function setDoc() called with invalid data

I am using Firebase SDK's within Node-red (as specified in NPM docs they can be used for IoT devices with NODE.js).
I can use all of the CRUD methods with Firebase RealtimeDatabase.
With Firebase Firestore I can only use READ and DELETE functionality.
SET and UPDATE results in weird errors that I couldn't find answers
anywhere on the internet.
I am importing Firebase SDK's through require() inside settiings.js and functionGlobalContext so I can access them in Node-red functions:
functionGlobalContext: {
firebase: require('firebase/app'),
firebaseDatabase: require('firebase/database'),
firebaseFirestore: require('firebase/firestore'),
// os:require('os'),
// jfive:require("johnny-five"),
// j5board:require("johnny-five").Board({repl:false})
},
First I initialize my whole Firebase project with this code (and everything initializes fine without errors):
//Load data from Global contexta
const firebase = global.get('firebase');
const firebaseDatabase = global.get('firebaseDatabase');
const firebaseFirestore = global.get('firebaseFirestore');
const firebaseConfig = {
//my Firebase credentials
};
//Set up Firebase
const app = firebase.initializeApp(firebaseConfig);
const database = firebaseDatabase.getDatabase();
const firestore = firebaseFirestore.getFirestore();
//Save the database reference to Global context
global.set('app', app);
global.set('database', database);
global.set('firestore', firestore);
And here I am trying basic SET operation with Firestore:
const ft = global.get('firebaseFirestore');
const firestore = global.get('firestore');
const frankDocRef = ft.doc(firestore, "users", "frank");
await ft.setDoc(frankDocRef, {
name: "Frank",
age: 12
});
Unfortunately even though this code is ctrl+c ctrl+v from Firestore docs I get this error:
"FirebaseError: [code=invalid-argument]: Function setDoc() called with invalid data. Data must be an object, but it was: a custom Object object (found in document users/frank)"
When I use the same code inside a web app everything works fine.
There has to be something going on under the hood with Node-red
I tried creating the object using various methods and all of them resulted in the same error.
Does anybody have any idea what could be going wrong here?

Firebase emulator wont run

I'm using the Firebase emulator in order to test my Cloud Function. The code I'm trying to execute is:
import * as functions from "firebase-functions";
const fetch = require('node-fetch');
export const newZipCode = functions.database.ref('/zipCodes/{zipCode}').onCreate(async (snapshot, context) => {
const api = await fetch(`https://api.papapi.se/lite/?query=${context.params.zipCode}&format=json&apikey=xxx`);
const json = await api.json();
console.log(context.params.zipCode);
if (api.ok) {
snapshot.ref.update({
latitude: json.results[0].latitude,
longitude: json.results[0].longitude
});
} else {
console.log(${api.status}));
}
});
However, when I try to run the Firestore- and Functions emulators, I receive this error message:
"Missing expected firebase config value databaseURL, config is actually{"storageBucket":"xxxx-yyyy.appspot.com","projectId":"xxxx-yyyy"}"
Shouldn't Cloud Functions and Firestore emulators be able to communicate right of the box? If not, what needs to be done in order to get the emulators running? FYI, I'm running Node.js 14 and Firebase version 9.6.1.
The problem is that you are starting the Firestore emulator when you should start the Database emulator instead, as you are using the Realtime Database onCreate() event handler.
Since you are starting the Firestore emulator, you probably don't have a Realtime Database created in the Firebase console so you get the "Missing expected firebase config value databaseURL..." error. You can create one following the steps of the public documentation.
Or if you want to use Cloud Firestore you need to modify your code to use the Firestore onCreate() event handler instead.
import * as functions from "firebase-functions";
const fetch = require('node-fetch');
export const newZipCode = functions.firestore.document('/zipCodes/{zipCode}').onCreate(async (snapshot, context) => {
const api = await fetch(`https://api.papapi.se/lite/?query=${context.params.zipCode}&format=json&apikey=xxx`);
const json = await api.json();
console.log(context.params.zipCode);
if (api.ok) {
snapshot.ref.update({
latitude: json.results[0].latitude,
longitude: json.results[0].longitude
});
} else {
console.log(${api.status}));
}
});

Firebase HTTP Cloud Functions - Read database once

I have a Firebase HTTPs function. The function needs to read a value from a Firebase database based on the query parameter, and return a result based on this data.
The Firebase JS SDK says to do this using:
return firebase.database().ref('/users/' + userId).once('value').then(function(snapshot) {
var username = snapshot.val().username;
// ...
});
However, the Cloud functions examples have:
var functions = require('firebase-functions');
functions.database.ref('/');
But the DB reference doesn't have the method once, only onWrite (https://firebase.google.com/docs/reference/functions/functions.database.RefBuilder). This is obviously for DB write functions, rather than HTTP functions.
Is there a correct way to read from the database once in a HTTP function? Can I use the normal Firebase SDK, or is there a better way?
Thanks.
I found the solution in combining the answer here on how to get the parameter and an answer from Michael Blight to
How to run query from inside of Cloud function?
The answer there also shows what is required to use firebase-admin.
The following works for me when calling my-project.firebaseapp.com/event/123/.
var functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.showEvent = functions.https.onRequest((req, res) => {
const params = req.url.split("/");
const eventId = params[2];
return admin.database().ref('events/' + eventId).once('value', (snapshot) => {
var event = snapshot.val();
res.send(`
<!doctype html>
<html>
<head>
<title>${event.name}</title>
</head>
<body>
<h1>Title ${event. name} in ${event.city}</h1>
</body>
</html>`
);
});
});
You're confusing two parts:
the firebase-functions module, which contains the logic to trigger based on database calls with functions.database.ref('/path').onWrite().
the firebase-admin module, which allows your function to call into the database.
Since you have a HTTP function, you should trigger as the documentation for HTTP functions shows:
exports.data = functions.https.onRequest((req, res) => {
// ...
});
Then in your function, you access the database as the documentation for the Admin SDK shows:
return admin.database().ref('/users/' + userId).once('value').then(function(snapshot) {
var username = snapshot.val().username;
// ...
});
So in total:
exports.date = functions.https.onRequest((req, res) => {
admin.database().ref('/users/' + userId).once('value').then(function(snapshot) {
var username = snapshot.val().username;
res.status(200).send(username);
});
});
Note that this is a tricky pattern. The call to the database happens asynchronously and may take some time to complete. While waiting for that, the HTTP function may time out and be terminated by the Google Cloud Functions system. See this section of the documentation.
As a general rule I'd recommend using a Firebase Database SDK or its REST API to access the database and not rely on a HTTP function as middleware.

Categories