admin.ref.on() is not working while once() works perfectly - firebase

I found this strange problem where documented feature seems not to be working.
I have this working code:
exports.getEvents = functions.https.onRequest((req, res) => {
cors(req, res, () => {
admin.database().ref('events').orderByValue().once('value', function(snapshot) {
res.status(200).send(snapshot.val());
}).catch(error => {
console.error('Error while reading data', error);
res.status(403).send('Error: ' + error);
});
When I change from once() to on() I get errors.
What I want to achieve is to have server send new JSON payload when there are changes to eventssince I have app that reads events.json directly and I can use only link to provide data (so all SDK functions are out). Am I doing something wrong?
Error log:
TypeError: admin.database(...).ref(...).orderByValue(...).on(...).catch is not a function
at cors (/user_code/index.js:24:11)
at cors (/user_code/node_modules/cors/lib/index.js:188:7)
at /user_code/node_modules/cors/lib/index.js:224:17
at originCallback (/user_code/node_modules/cors/lib/index.js:214:15)
at /user_code/node_modules/cors/lib/index.js:219:13
at optionsCallback (/user_code/node_modules/cors/lib/index.js:199:9)
at corsMiddleware (/user_code/node_modules/cors/lib/index.js:204:7)
at exports.getEvents.functions.https.onRequest (/user_code/index.js:19:2)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:26:47)
at /var/tmp/worker/worker.js:635:7

You've tried to add a .catch to the end of your statement. .on doesn't support this function.
See some sample code below which should fix your issue.
admin.database().ref('/somePath')
.orderByValue()
.on('child_added', (snapshot, prevChildKey) => {
console.log(snapshot.val()); // JSON
}, err => {
// Error is thrown here - Not in a .catch
});

Related

Calling an API using Axios and Firebase Cloud Functions

I want to make a Google Cloud Function calling an external API for me. After some research on Google I found the way using Axios. The call is actually working, when I'm using it on my own nodejs but when I want to deploy the function to Google Cloud functions I'm always getting an error (Function cannot be initialized. Error: function terminated.)
I'm on the Blaze plan.
const functions = require("firebase-functions");
const axios = require("axios");
exports.getData = functions.https.onRequest((req, res) => {
return axios.get("http://api.marketstack.com/v1/eod?access_key='myAccessKey'&symbols=AAPL")
.then((response) => {
const apiResponse = response.data;
if (Array.isArray(apiResponse["data"])) {
apiResponse["data"].forEach((stockData) => {
console.log(stockData["symbol"]);
});
}
}).catch((error) => {
console.log(error);
});
});
Could someone please help me?
EDIT: I finally fixed it: the mistake was, that I ended up with two package.json files (one in the directory where it should be and one which I actually didn't need). When I was installing the dependencies with npm install, axios was added into the wrong package.json file. Unfortunately the other package.json file made it up to the server and I ended up with a package.json file without the necessary dependencies on the server and thus this made the error occur.
I didn’t test your code but you should return "something" (a value, null, a Promise, etc.) in the then() block to indicate to the Cloud Function platform that the asynchronous work is complete. See here in the doc for more details.
exports.getData = functions.https.onRequest((req, res) => {
return axios.get("http://api.marketstack.com/v1/eod?access_key='myAccessKey'&symbols=AAPL")
.then((response) => {
const apiResponse = response.data;
if (Array.isArray(apiResponse["data"])) {
apiResponse["data"].forEach((stockData) => {
console.log(stockData["symbol"]);
});
}
return null;
}).catch((error) => {
console.log(error);
});
});
You probably want do more than just logging values in the then() e.g. call an asynchronous Firebase method to write to a database (Firestore or the RTDB): in this case take care to return the Promise returned by this method.

Access all children on node and update them for Firebase Function

I detected some recursion on one of the nodes of my realtime database and I want to delete (or set tu null) that specific node. This is my firebase function so far:
exports.cleanForms = functions.https.onRequest((req, res) => {
const parentRef = admin.database().ref("forms");
return parentRef.once('value').then(snapshot => {
snapshot.forEach(function(child) {
admin.database().ref('forms/'+child.key+'/user/forms').set(null);
});
});
});
Basically it should iterate all the records inside the forms node and delete its user/forms property.
But calling that function by going to this url: https://.cloudfunctions.net/cleanForms gives me this error:
Error: could not handle the request
And this is what I see on the logs:
10:47:57.818 PM cleanForms Function execution took 13602 ms, finished
with status: 'connection error'
The forms node has less than 3,000 records but as I mentioned before, it has some recursion on it. I don't know if it is failing due to its size or something related to that.
You are using an HTTPs Cloud Function: therefore you must "send a response to the client at the end" (Watch this official video by Doug Stevenson for more detail: https://youtu.be/7IkUgCLr5oA).
In your case, the "end" of the function will be when ALL of your set() asynchronous operations will be "done". Since the set() method returns a Promise, you have to use Promise.all() (again, watch this official video: https://youtu.be/d9GrysWH1Lc ).
So the following should work (not tested however):
exports.cleanForms = functions.https.onRequest((req, res) => {
const parentRef = admin.database().ref("forms");
parentRef.once('value')
.then(snapshot => {
const promises = [];
snapshot.forEach(child => {
promises.push(admin.database().ref('forms/'+child.key+'/user/forms').set(null));
});
return Promise.all(promises)
.then(results => {
response.send({result: results.length + ' node(s) deleted'});
})
.catch(error => {
response.status(500).send(error);
});
});

How to do Axios request from Firebase Cloud Function

I've tried the following in Firebase Cloud Function to do an Axios request but it didn't work.
const functions = require('firebase-functions');
const axios = require('axios');
const cors = require('cors')({ origin: true });
exports.checkIP = functions.https.onRequest((req, res) => {
cors(req, res, () => {
if( req.method !== "GET" ) {
return res.status(401).json({
message: "Not allowed"
});
}
return axios.get('https://api.ipify.org?format=json')
.then(data => {
console.log(data)
res.status(200).json({
message: data.ip
})
})
.catch(err => {
res.status(500).json({
error: err
})
})
})
})
I've also googled a lot for seeing some example of how to use Axios with Cloud Functions but found none. The above code is not returning anything.
Can anyone help?
P.S.: I've already added billing details in my Firebase account and not using the free Spark plan, rather using Blaze plan.
Edit:
I've finally able to do this using the request-promise node package but still no idea about how to do it with axios. As no matter what I try, axios doesn't work in Firebase cloud functions. This is what I did:
npm i --save cors request request-promise
Then this is the code I run: https://gist.github.com/isaumya/0081a9318e4f7723e0123f4def744a0e
Maybe it will help someone. If anyone knows how to do it with Axios please answer below.
I changed data.ip to response.data.ip and added return before the two res.status(... lines and the deployed cloud function works for me using Axios when I try it.
The code I have is
const functions = require('firebase-functions');
const axios = require('axios');
const cors = require('cors')({ origin: true });
exports.checkIP = functions.https.onRequest((req, res) => {
cors(req, res, () => {
if (req.method !== "GET") {
return res.status(401).json({
message: "Not allowed"
});
}
return axios.get('https://api.ipify.org?format=json')
.then(response => {
console.log(response.data);
return res.status(200).json({
message: response.data.ip
})
})
.catch(err => {
return res.status(500).json({
error: err
})
})
})
});
When I invoke the function I get back a reply like
{
"message": "127.168.121.130"
}
I experienced the same issue. An HTTP request with axios returns the following error message :
TypeError: Converting circular structure to JSON
Here is an explanation of what is going on and how to get around this
You can use the following package :
https://github.com/moll/json-stringify-safe
I'm not sure about the consistency of this approach and personally went for request-promise, which is heavier than axios but allows straightforward HTTP requests.

google places api returns a string, how do I parse to JSON object?

In a small webshop that I am trying to setup, I need to update the opening hours in the background with firebase functions and google place details when a user creates a shoppingcart.
I can succesfully sent a GET request with POSTMAN to retrieve the opening hours of a shop using the following instructions:
https://developers.google.com/places/web-service/details
But I cannot access the response from the GET request as I usually do with JSON responses.
I tried also:response.result.opening_hours.json()
Can someone tell me what I am doing wrong?
export const mapGooglePlaces = functions.database
.ref('/shopping-carts/{shoppingCartId}/shippingManner')
.onWrite(event => {
const shippingManner = event.data.val();
const optionsAPI = {
method: 'GET',
uri: 'https://maps.googleapis.com/maps/api/place/details/json?placeid=ChIJN1t_tDeuEmsRUsoyG83frY4&key=YOUR_API_KEY',
};
return request(optionsAPI)
.then(response => {
const openingHours = response.result.opening_hours;
console.log(openingHours);
return;
})
.catch(function (err) {
console.log(err);
});
});
The response is not a JSON object. It is JSON formatted text and must be parsed to create an object. Modify the code as follows:
return request(optionsAPI)
.then(response => {
const responseObject = JSON.parse(response);
const openingHours = responseObject.result.opening_hours;
console.log(openingHours);
return;
})
.catch(function (err) {
console.log(err);
});
Also, before using the opening_hours or any other property of result, you should test responseObject.status === 'OK' to confirm that a place was found and at least one result was returned.

Trying to run authy-client with Firebase Cloud Functions

I've been trying to get authy-client to run with Firebase Cloud Functions but I keep running into a ValidationFailedError. I've been testing the examples the author supplied at https://www.npmjs.com/package/authy-client with no luck.
For my Firebase function, I've been trying this:
const Client = require('authy-client').Client;
const client = new Client({ key: 'my API key here' });
exports.sendVerificationCode = functions.database.ref('users/{userId}/verify/status')
.onCreate(event => {
const sender = client.registerUser({
countryCode: 'US',
email: 'test#tester.com',
phone: '4035555555'
}).then( response => {
return response.user.id;
}).then( authyId => {
return client.requestSms({ authyId: authyId });
}).then( response => {
console.log(`SMS requested to ${response.cellphone}`);
throw Promise;
});
return Promise.all([sender]);
});
But I get this error:
ValidationFailedError: Validation Failed
at validate (/user_code/node_modules/authy-client/dist/src/validator.js:74:11)
at _bluebird2.default.try (/user_code/node_modules/authy-client/dist/src/client.js:632:31)
at tryCatcher (/user_code/node_modules/authy-client/node_modules/bluebird/js/release/util.js:16:23)
at Function.Promise.attempt.Promise.try (/user_code/node_modules/authy-client/node_modules/bluebird/js/release/method.js:39:29)
at Client.registerUser (/user_code/node_modules/authy-client/dist/src/client.js:617:34)
at exports.sendVerificationCode.functions.database.ref.onCreate.event (/user_code/index.js:24:25)
at Object.<anonymous> (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:59:27)
at next (native)
at /user_code/node_modules/firebase-functions/lib/cloud-functions.js:28:71
at __awaiter (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:24:12)
at cloudFunction (/user_code/node_modules/firebase-functions/lib/cloud-functions.js:53:36)
at /var/tmp/worker/worker.js:695:26
at process._tickDomainCallback (internal/process/next_tick.js:135:7)
I am new to Firebase' cloud functions so I may be overlooking something obvious but from what I've read, then() statements and the function itself needs to return/throw a Promise so I hacked that together.
I've also made sure that authy-client is included in the dependencies in the package.json file.
I originally signed up for the Twilio trial which gave me an option to create an application with Authy. Needing to be sure, I also signed in to Authy to check if the API key is the same, and they are. So I don't think the validation error is due to the API key.
Any help would be appreciated. Thank you.
Thank you all for your answers. I was finally able to figure out a solution. It had nothing to do with the code, well throw Promise was a mistake but it was apparently a problem with DNS resolutions that was resolved when I set up billing on Firebase.
As for my final code, as I originally wanted to use authy-client for phone verification, it looks something like this:
exports.sendVerificationCode = functions.database.ref('users/{userId}/verify')
.onCreate(event => {
const status = event.data.child('status').val();
const phoneNum = event.data.child('phone').val();
const countryCode = event.data.child('countryCode').val();
var method;
if(status === 'pendingSMS')
method = 'sms';
else
method = 'call';
// send code to phone
const sender = authy.startPhoneVerification({ countryCode: countryCode, phone: phoneNum, via: method })
.then( response => {
return response;
}).catch( error => {
throw error;
});
return Promise.all([sender]);
});
throw Promise doesn't really mean anything. If you want to capture problems that can occur anywhere in your sequence of promises, you should have a catch section. The general form is this:
return someAsyncFunction()
.then(...)
.then(...)
.catch(error => { console.error(error) })
That doesn't necessarily fix your error, though. That could be coming from the API you called.
Twilio developer evangelist here.
Doug is right about throw Promise, that will definitely need to be changed.
However, the error seems to be coming before that and from the API. Specifically, the stack trace tells us:
at Client.registerUser (/user_code/node_modules/authy-client/dist/src/client.js:617:34)
So the issue is within the registerUser function. The best thing to do is try to expose more of the error that is being generated from the API. That should give you the information you need.
Something like this should help:
const Client = require('authy-client').Client;
const client = new Client({ key: 'my API key here' });
exports.sendVerificationCode = functions.database.ref('users/{userId}/verify/status')
.onCreate(event => {
const sender = client.registerUser({
countryCode: 'US',
email: 'test#tester.com',
phone: '4035555555'
}).then( response => {
return response.user.id;
}).then( authyId => {
return client.requestSms({ authyId: authyId });
}).then( response => {
console.log(`SMS requested to ${response.cellphone}`);
}).catch( error => {
console.error(error.code);
console.error(error.message);
throw error;
});
});
Let me know if that helps at all.

Resources