How can I prevent "Bad request" when calling Firebase's `.onCall()` method? - firebase

I've just upgraded to using Firebase Cloud Functions v1.x. According to this answer
Callable functions are exactly the same as HTTP functions
With that in mind, I've tried to convert my pre-1.x mock-code:
export const myHttpAction = functions.https.onRequest((req, res) => {
try {
const result = await myHttpActionWorker(req.body);
return res.send({ status: 'OK' });
} catch (err) {
console.error(err);
return res.status(500).send({ status: 'Server error' });
}
});
to the following:
export const myHttpAction = functions.https.onCall(async (data, context) => {
console.log(context.auth);
try {
const result = await myHttpActionWorker(data);
return { status: 'OK' };
} catch (err) {
console.error(err);
return { status: 'Server error' };
}
});
But upon submission to my endpoint, /myHttpAction, with the same data that I used in pre-1.x, I get the following back:
{
"error": {
"status": "INVALID_ARGUMENT",
"message": "Bad Request"
}
}
I'm not sure why the request is "bad" since it's exactly the same and Callable functions are "exactly the same". Any idea what gives?
My package.json specifies "firebase-functions": "^1.0.1".

You're misunderstanding what was meant by "exactly the same" (and omitting the entire remainder of the answer!). They're the same in terms of security (as the original question was asking), because a callable function is an HTTP function, with extra stuff going on behind the scenes that managed by the callable client SDK. The answer lists out those differences. Those differences don't have any effect on security. But you can't simply swap in a callable for an HTTP function and expect everything to be the same for existing callers.
If you want to invoke a callable function without using the client SDK, you'll have to follow its protocol specification. The documentation on that is forthcoming, but you can get the basics here:
How to call Firebase Callable Functions with HTTP?

Related

Firebase cloud function error: Maximum call size stack size exceeded

I've made firebase cloud function which adds the claim to a user that he or she has paid (set paid to true for user):
const admin = require("firebase-admin");
exports.addPaidClaim = functions.https.onCall(async (data, context) => {
// add custom claim (paid)
return admin.auth().setCustomUserClaims(data.uid, {
paid: true,
}).then(() => {
return {
message: `Succes! ${data.email} has paid for the course`,
};
}).catch((err) => {
return err;
});
});
However, when I'm running this function: I'm receiving the following error: "Unhandled Rejection (RangeError): Maximum call stack size exceeded". I really don't understand why this is happening. Does somebody see what could cause what's getting recalled which in turn causes the function to never end?
Asynchronous operations need to return a promise as stated in the documentation. Therefore, Cloud Functions is trying to serialize the data contained by promise returned by transaction, then send it in JSON format to the client. I believe your setCustomClaims does not send any object to consider it as an answer to the promise to finish the process so it keeps in a waiting loop that throws the Range Error.
To avoid this error I can think of two different options:
Add a paid parameter to be able to send a JSON response (and remove the setCustomUserClaim if it there isn’t any need to change the user access control because they are not designed to store additional data) .
Insert a promise that resolves and sends any needed information to the client. Something like:
return new Promise(function(resolve, reject) {
request({
url: URL,
method: "POST",
json: true,
body: queryJSON //A json variable I've built previously
}, function (error, response, body) {
if (error) {
reject(error);
}
else {
resolve(body)
}
});
});

Next.js API / API resolved without sending a response for /api/employees, this may result in stalled requests

I looked over the previous postings but they did not seem to match my issue. I am only using the next.js package and the integrated api pages. Mongoose is what I am using to make my schemas. I keep getting the above error only for my post calls. Any ideas what is going wrong here?
import dbConnect from "../../../utils/dbConnect";
import Employee from "../../../models/Employee";
dbConnect();
export default async (req, res) => {
const { method } = req;
switch (method) {
case "POST":
await Employee.create(req.body, function (err, data) {
if (err) {
res.status(500).json({
success: false,
data: err,
});
} else {
res.status(201).json({
success: true,
data: data,
});
}
});
break;
default:
res.status(405).json({
success: false,
});
}
};
This is a false warning because in the provided code you always return a response. It's just Next.js doesn't know it.
If you are sure that you return a response in every single case, you can disable warnings for unresolved requests.
/pages/api/your_endpoint.js
export const config = {
api: {
externalResolver: true,
},
}
Custom Config For API Routes

Where is the best place to modify backend response with redux-saga?

I have a function that prepares the errors from backend to be easy for displaying in the components - it's named prepareErrorMessages. It accepts the response from the backend and some default error message.
So - in the saga I have this:
function* updateSomethingFlow(action) {
try {
const response = yield call(updateSomething, action.payload);
if (response) {
yield put({
type: UPDATE_SUCCESS
});
}
} catch (err) {
yield put({
type: UPDATE_FAILURE,
payload: prepareErrorMessages(err, 'Failed to update. Please, try again.')
});
}
}
So - am I wrong to modify the errors from the backend here?
Or is it better to do this in the reducer?
case UPDATE_FAILURE:
nextState = {
...state,
loading: false,
errors: prepareErrorMessages(payload, 'Failed to update. Please, try again.'),
};
break;
And also - why is it better to update there?
Personally, I think its right to do it in the reducer.
That is where you handle the responses. Action creators should only set the payload which could be some static data or a promise.
Dont see why you cannot transform/modify the received data there.
Personally, I would prefer to have it in the saga because I think it is the right place of handling this kind of logic.
I prefer my reducers to only be responsible for changing state, not for data transformation.
But it is my personal opinion.
We are using a Transformer for transforming the response getting from the Api. Transformer is the function which takes the input and provide the desired output. Designing the transformer makes the code clean and easy to test.
For example :-
function* updateSomethingFlow(action) {
try {
const response = yield call(updateSomething, action.payload);
// after getting the response from the api pass through the transformer.
const transformedResponse =apiTransformer(action.payload);
if (response) {
yield put({
type: UPDATE_SUCCESS,
data: trasnformedResponse
});
}
} catch (error) {
yield put({
type: UPDATE_FAILURE,
error: error)
});
}
}
const apiTransformer = function(apiResponse) {
// implement the logic. This function returns the transformed Response
}
Using this you can move reducer free from the error. Makes the code testable and making mocks easy.
For global backend Errors make a global errorHandler using Redux Middleware, like this
const errorTracking = store => next => action => {
if (/_FAILURE$/.test(action.type)) {
const errorCode = parseInt(
_.get(action, ['error', 'response', 'status'])
)
// this was for my use case
if (errorCode === 403) {
// launch an Global error handler action
return next(ErrorHandlerAction())
} else return next(action)
}
return next(action)
}
While for not genric error You can implement HOC wrap it around the component for visualisation. Thus you can have global implementation for the errors.

HTTP request to a google service returns Error: read ECONNRESET firebase cloud functions

I am trying to translate the name of a user from english to an indian language using google translate api and storing the data back in realtime database with a cloud function.
This function is invoked by a write to the database, and I am using a HTTP POST request to send a request to the cloud translate api and the response is stored back to the database. My code for the translate request is this.
var translate_options = { method: 'POST',
url: 'https://translation.googleapis.com/language/translate/v2',
qs:
{ key: 'key goes here',
},
form: {
q: fullData.name,
target: "te"
},
};
request(translate_options, function (error, translate_response, translate_body) {
if (error){
console.log("In translating, got an error");
console.log(error);
}
// Query to the database goes here.
});
This code, if tried in my laptop, gives me the correct translation, but if I deploy it as a cloud function, it gives me an error. Very specifically
{ Error: read ECONNRESET
at exports._errnoException (util.js:1020:11)
at TLSWrap.onread (net.js:568:26) code: 'ECONNRESET', errno: 'ECONNRESET', syscall: 'read' }
I am on firebase blaze plan, and I am able to sent POST request to my other services, but not a google service.
Can anybody help me with this issue. Thanks in advance.
Edit :
The full code is
var functions = require('firebase-functions');
var admin = require('firebase-admin');
var request = require("request");
admin.initializeApp(functions.config().firebase);
exports.whenUserIsAdded = functions.database.ref('users/{companyId}/{uid}').onCreate(event => {
var fullData = event.data.val();
var lang_code = {
"bengali": "bn",
"telugu": "te",
"english": "en"
}
var lang_var = lang_code[fullData['edition']];
var translate_options = { method: 'POST',
url: 'https://translation.googleapis.com/language/translate/v2',
qs:
{ key: 'Key goes here',
},
form: {
q: fullData.name,
target: lang_var
},
};
request(translate_options, function (error, translate_response, translate_body) {
var farmer_name = "";
if(error){
console.log("There is an error in translation");
console.log(error);
}
translate_body = JSON.parse(translate_body);
if(translate_body.data.translations){
farmer_name = translate_body.data.translations[0].translatedText;
console.log("The farmer name is " + fullData.name +" : " + farmer_name);
// Code to write to the database;
} else{
console.log("The translation failed");
farmer_name = fullData.name;
console.log("The famrer name is " + farmer_name);
}
})
});
You're not returning a promise that's resolved when all the work of your function is complete. If the work was completing in the past, that possibly just means you were lucky. Without returning a promise, Cloud Functions may terminate and clean up any work that wasn't complete when the function returns. Properly returning a promise will prevent Cloud Functions from cleaning up before the work is done.
Please consider reading my blog post about this. There is a section special just for ECONNRESET.

Error: Meteor code must always run within a Fiber

I am using stripe for payments in my app, I want to create a receipt document in my own database after a succesful transaction
My code:
Meteor.methods({
makePurchase: function(tabId, token) {
check(tabId, String);
tab = Tabs.findOne(tabId);
Stripe.charges.create({
amount: tab.price,
currency: "USD",
card: token.id
}, function (error, result) {
console.log(result);
if (error) {
console.log('makePurchaseError: ' + error);
return error;
}
Purchases.insert({
sellerId: tab.userId,
tabId: tab._id,
price: tab.price
}, function(error, result) {
if (error) {
console.log('InsertionError: ' + error);
return error;
}
});
});
}
});
However this code returns an error:
Error: Meteor code must always run within a Fiber. Try wrapping callbacks that you pass to non-Meteor libraries with Meteor.bindEnvironment.
I am not familiar with Fibers, any idea as to why this is?
The problem here is that the callback function which you pass to Stripe.charges.create is called asynchronously (of course), so it's happening outside the current Meteor's Fiber.
One way to fix that is to create your own Fiber, but the easiest thing you can do is to wrap the callback with Meteor.bindEnvironment, so basically
Stripe.charges.create({
// ...
}, Meteor.bindEnvironment(function (error, result) {
// ...
}));
Edit
As suggested in the other answer, another and probably better pattern to follow here is using Meteor.wrapAsync helper method (see docs), which basically allows you to turn any asynchronous method into a function that is fiber aware and can be used synchronously.
In your specific case an equivalent solution would be to write:
let result;
try {
result = Meteor.wrapAsync(Stripe.charges.create, Stripe.charges)({ /* ... */ });
} catch(error) {
// ...
}
Please note the second argument passed to Meteor.wrapAsync. It is there to make sure that the original Stripe.charges.create will receive the proper this context, just in case it's needed.
You might want to take a look at the docs for http://docs.meteor.com/#/full/meteor_wrapasync.

Resources