I have callable function in my Cloud Functions:
export const getUserByEmail = functions.https.onCall((data, context) => {
const email = data.email
return admin.auth().getUserByEmail(email)
})
And I call it in my Angular application:
public getUserByEmail(email: string) {
return this.cloudFunctions.httpsCallable('getUserByEmail')(email)
}
...
this.getUserByEmail(email)
How should I change my function to be able to use it like this:
this.getUserByEmail(email)
.then(data => console.log(data))
.catch(error => console.log(error))
And return 500 from cloud function on error?
As explained in the documentation, if you want your client (front-end) to receive errors sent from the Callable Cloud Function, you need to "return errors from a callable by throwing (or returning a Promise rejected with) an instance of functions.https.HttpsError".
So, in your Cloud Function you need to throw an HttpsError, as shown below. In this example we treat the case of a user-not-found error thrown by the Admin SDK and, in turn we throw an HttpsError with a not-found code.
See other possibilities for the HttpsError codes in the doc (table "Parameter"), and here for the Admin Authentication API errors codes.
export const getUserByEmail = functions.https.onCall((data, context) => {
const email = data.email
return admin.auth().getUserByEmail(email)
.catch(error => { //Catching the error thrown by the Admin SDK
if (error.code === 'auth/user-not-found') {
throw new functions.https.HttpsError( //throwing the HttpsError
'not-found',
'User with email (' + email + ') was not found!'
);
} else if (error.code === 'auth/...') {
//Possibly manage other Admin Authentication API errors codes
} else {
//...
}
})
})
And then, in your front-end, you do as follows (again, based on the doc, see the specific section):
this.getUserByEmail(email)
.then(data => console.log(data))
.catch(error => {
var code = error.code;
var message = error.message;
console.log(code + '/' + message);
})
If you have something else to do (that might cause error) before returning the user data in the https callable function, you can do like this:
export const getUserByEmail = functions.https.onCall((data, context) => {
const email = data.email
// Return your own promise.
return new Promise((resolve, reject) => {
try {
// Do other stuff there might cause error.
} catch (error) {
// Reject if error.
// But you might need to throw the HttpsError again if the client
// can only get INTERNAL as error message.
return reject(error);
};
return admin.auth().getUserByEmail(email)
.then((data) => {
// Remember to resolve the promise, or else you may get an unexpected
// error, such as timeout or even an irrelevant CORS error.
resolve(data);
}
.catch((error) => {
// Follow #Renaud's idea in his answer.
});
});
});
Related
I Have the following function and I want to do a conditional inside of the snapshot and then do some actions,
the current issue is I a can see the first console.log in the logs but the the function is not proceeding in to the snapshot for some reason what is the best way to get data inside a cloud-function ? in case you don't want to send them as response ?
export const deleteClient = functions.https.onCall((data, context) => {
console.log(context.auth?.uid, data.clientAccountId, ':::::::::::::ID1 + ID2:::::::::::::');
db.collection('ClientsData')
.doc(data.clientAccountId)
.get()
.then((snapshot) => {
console.log(context.auth?.uid, data.clientAccountId, ':::::::::::::snapshot.data()?.sharedId:::::::::::::');
if (context.auth?.uid! === snapshot.data()?.sharedId) {
admin
.auth()
.deleteUser(data.clientAccountId)
.then(() => {
console.log('Successfully deleted user');
})
.catch((error: any) => {
console.log('Error deleting user:', error);
});
db.collection('ClientsData').doc(data.clientAccountId).delete();
}
})
.catch((err) => {});
});
If you do not want to return anything from the function, you can simply return null. Also you should log any errors in the catch block so you'll know if something is wrong. Try refactoring the code using async-await syntax as shown below:
export const deleteClient = functions.https.onCall(async (data, context) => {
try {
console.log(context.auth?.uid, data.clientAccountId, ':::::::::::::ID1 + ID2:::::::::::::');
const snapshot = await db.collection('ClientsData').doc(data.clientAccountId).get()
console.log(context.auth?.uid, data.clientAccountId, ':::::::::::::snapshot.data()?.sharedId:::::::::::::');
if (context.auth?.uid! === snapshot.data()?.sharedId) {
await Promise.all([
admin.auth().deleteUser(data.clientAccountId),
db.collection('ClientsData').doc(data.clientAccountId).delete()
]);
}
} catch (e) {
console.log(e)
}
return null;
});
I'm trying to return a resolve or reject depending on if the promise was successful or not. I can't seem to figure out why this isn't returning the response. All I get from my promise is [object Object]. This is what I get in response.
Here's the code:
app.get('/', (req,res) => {
return new Promise((resolve,reject) => {
var sql = "INSERT INTO usersinfo (firstname,lastname,email,number,latitude,longitude) VALUES(?,?,?,?,?,?)";
conn.query(sql,[fname,lname,email,num,req.query.latitude,req.query.longitude], (err,result) => {
if (err) {
res.send('error')
console.log(err,'there has been an error')
reject('There was an error')
return
}else{
console.log('inserted')
resolve({ success:'true' })
}
res.end()
})
})
})
Where I'm fetching the url:
const res = await fetch(`http://MYIPADDRESS?latitude=${latitude}&longitude=${longitude}`)
console.log(res)
I don't seem to get what's wrong. And if I attach the then((response) => console.log(response)) method I get an error in the expo app saying There was an error sending log messages to your development environment PrettyFormatPluginError:value.hasOwnProperty is not a function. (In 'value.hasOwnProperty('tag')','value.hasOwnProperty is undefined)
You don't need to create a promise. The send method can be called asynchronously -- when the response is ready:
app.get('/', (req,res) => {
var sql = "INSERT INTO usersinfo (firstname,lastname,email,number,latitude,longitude) VALUES(?,?,?,?,?,?)";
conn.query(sql,[fname,lname,email,num,req.query.latitude,req.query.longitude], (err,result) => {
if (err) {
res.send('error');
console.log(err,'there has been an error');
} else {
res.send({ success:'true' }); // <---
console.log('inserted');
}
});
});
NB: also, there is no need to call res.end() as res.send() already implies that.
On the client side you'll have to await the JSON content to be generated:
const response = await fetch(`http://MYIPADDRESS?latitude=${latitude}&longitude=${longitude}`);
const result = await response.json(); // <---
console.log(result);
I am part of a project which uses nodeJS + ExpressJS for the backend application, and We have a middleware function to log accesses on routes in the database.
When an User tries to access the /user route with a post method, a middleware receives the Request, get information like the URL, ip address, origin, a description of the event and record it in the database.
Everything works just fine, but some of my teammates were discussing about how to log the erros also in the database.
I will put bellow a code example
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
return response.status.json({ error: 'E-mail already in use' });
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
response.status(500).json({ error });
}
};
When we were discussing about how to implement it, we realized we'd have to call a log error function in a lot of places since we have many flows which leads to an error response.
So the code would be just like:
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
function() // here we would log the error
return response.status.json({ error: 'E-mail already in use' });
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
function() // here we would log the error
response.status(500).json({ error });
}
};
is it a properly way of dealing with error logging or is there any better way of doing it? Thank you for reading!
You can use the built-in error handler provided by Express.JS for this kind of logic, of course it requires a bit of setup. Like most things in Express.JS, the error handler it's just a middleware function with four parameters err, req, res and next, which MUST be placed after all your other middlewares. It comes to play when, inside a router handle (for example), your call next(err) (where err it's an Error) or by simply throwing err. Check out the documentation for more.
app.use(...)
app.use(...)
app.use((req, res, next) => {
if (req.params.id === undefined) {
let error = new Error("ID required.")
error.statusCode = 400
error.statusMessage = "Request not valid, ID not found."
throw error;
} else {
// Do some stuff...
}
})
// NOTE: After ALL your other middlewares
app.use((err, req, res, next) => {
console.error(err)
res
.status(err.statusCode)
.json(err.statusMessage)
})
Ideally you should log the errors only inside the catch block. Whenever you encounter an error just throw a new error by calling throw new Error("Type your error message here"). Then your function inside catch block will log and handle the error appropriately.
I would change your code to this:
const create = (request, response) => {
try {
const user = request.body;
const userExists = await usersRepository.findOne({ where: { email } });
if(userExists) {
throw new Error("E-mail already in use")
}
const creadtedUser = await usersRepository.create(user);
return response.status(200).json({ user: creadtedUser });
} catch (error) {
function() // log your error
response.status(500).json({ error.message });
}
};
Read more about Errors here.
I have the following code:
"use strict";
const Raven = require("raven");
Raven.config(
"test"
).install();
module.exports = function(Reservation) {
function dateValidator(err) {
if (this.startDate >= this.endDate) {
err();
}
}
function sendEmail(campground) {
return new Promise((resolve, reject) => {
Reservation.app.models.Email.send(formEmailObject(campground),
function(
err,
mail
) {
if (err) {
console.log(err);
Raven.captureException(err);
reject(err);
} else {
console.log(mail);
console.log("email sent!");
resolve(mail);
}
});
});
}
function formEmailObject(campground) {
return {
to: "loopbackintern#yopmail.com",
from: "noreply#optis.be",
subject: "Thank you for your reservation at " + campground.name,
html:
"<p>We confirm your reservation for <strong>" +
campground.name +
"</strong></p>"
};
}
Reservation.validate("startDate", dateValidator, {
message: "endDate should be after startDate"
});
Reservation.observe("after save", async function(ctx, next) {
try {
const campground = await Reservation.app.models.Campground.findById(
ctx.instance.campgroundId
);
const mail = await sendEmail(campground);
next();
} catch (e) {
Raven.captureException(e);
next(e);
}
});
};
Sorry for the poor formatting. When the flow is done I get this error:
(node:3907) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Callback was already called.
I am calling the next() callback in two places, one in the try code and one in the catch code. I assume that when it all goes right, next callback is called only once, and the same when it goes wrong. But it seems that it is called twice and I don't know why.
I also tried to call next outside the try/catch code but it results in the same error. If I left only the next that is called inside the catch code it doesn't throw the error.
Any idea? Thanks!
if you are using async function you shouldn't explicitly call next, it gets automatically called.
check out this github issue for loopback async/await
so your hook can be like the following.
Reservation.observe("after save", async ctx => {
try {
const campground = await Reservation.app.models.Campground.findById(
ctx.instance.campgroundId
);
const mail = await sendEmail(campground);
} catch (e) {
Raven.captureException(e);
throw e;
}
});
NB: you don't need to wrap it in try catch unless you want to modify/work with the error.
You should declare your sendEmail method as async as it returns a promise.
async function sendEmail(campground) {
...
}
After reading this article, I created a await-handler.js file which include following code.
module.exports = (promise) =>
promise
.then(data => ({
ok: true,
data
}))
.catch(error =>
Promise.resolve({
ok: false,
error
})
);
Then in MyModel.js file, I created a async function to get a value from database as follow.
const awaitHandler = require("./../await-handler.js")
const getMaxNumber = async (MyModel) => {
let result = await awaitHandler(MyModel.find());
if (result.ok) {
if (result.data.length) {
return result.data.reduce((max, b) => Math.max(max, b.propertyName), result.data[0] && result.data[0].propertyName);
} else {
return 0;
}
} else {
return result.error;
}
}
As per #Mehari's answer, I've commented call to next() method as follow:-
module.exports = function(MyModel) {
MyModel.observe('before save', async(ctx, next) => {
const maxNumber = await getMaxNumber (MyModel);
if(ctx.instance) {
...
set the required property using ctx.instance.*
like createdAt, createdBy properties
...
// return next();
} else {
...
code for patch
...
// return next();
}
})
}
This solves the warning issue whenever saving endpoint is triggered.
But the warning issue still appear when I run the endpoint to load the resource.Like
http://localhost:3000/api/MyModel
Previously, the issue appear only when the before save operation hook gets triggered.
After encountering this issue, I checked adding access and loaded operation hooks and I found that the the warnings are issued after loaded operation hook.
MyModel.observe('access', (ctx, next) => {
return next();
})
MyModel.observe('loaded', (ctx, next) => {
return next();
})
What could have caused this issue and how can it gets resolved?
When a user logs in with incorrect email and password, the success block of my client-side promise still executes, even though the server returned a 400.
I'm using Redux with React so I'm calling an action creator which calls an HTTP request using axios. I need help understanding why I'm not handling errors correctly, because the rest of my app's auth functions like signup, logout, etc all behave the same way even though I'm returning 400 statuses from the server.
Here is where I call login from my component, the success block always executes:
handleFormSubmit({
email, password
}) {
this.props.loginUser({
email, password
}).then(() => {
toastr.success("You are logged in!");
}).catch(() => {
toastr.warning("Could not log in");
})
}
Here is the action creator "loginUser", the success block for this function does NOT run when I return a 400 from the server:
export function loginUser({ email, password }) {
return function(dispatch) {
return axios.post(`/users/login`, {
email, password
})
.then(response => {
dispatch({
type: AUTH_USER
});
localStorage.setItem('token', response.headers['x-auth']);
browserHistory.push('/feature');
})
.catch(() => {
dispatch(authError('Incorrect email or password'));
});
}
}
Here is the route '/users/login' Please note that the 400 status does in fact return:
app.post('/users/login', (req, res) => {
var body = _.pick(req.body, ['email', 'password']);
User.findByCredentials(body.email, body.password).then(user => {
return user.generateAuthToken().then(token => {
res.header('x-auth', token).send(user);
});
}).catch(e => {
res.status(400).send();
});
});
You issue is that you're misunderstanding what a catch clause is in promises.
The way you can think if it is just a then with a rejection handler:
.then(null, function(err) {
// handle the error
})
Meaning it only handles the last unhandled error from the promise chain, and you can continue chaining after it no matter what happened.
Example:
new Promise((resolve, reject) => {
setTimeout(() => reject(Error('After 1 sec')), 1000)
})
.catch((err) => {
console.log(`catch: ${err}`);
return 5;
})
.then((five) => {
// this chains because the error was handled before in the chain
console.log(`catch.then: ${five}`); // 5
})
.catch(() => {
console.log('No error happened between this error handler and previous so this is not logged');
});
To make an error propagate from the current catch to the next error handler, you can return a rejected Promise (or re-throw the error) to make the chain skip all the success handlers until the next fail (or catch) handler.
new Promise((resolve, reject) => {
setTimeout(() => reject(Error('After 1 sec')), 1000)
})
.catch((err) => {
// return a reject promise to propagate to the next error handler
return Promise.reject(err);
// can also `throw err;`
})
.then((nothing) => {
// this doesn't happen now
console.log(nothing);
})
.catch(console.error); // this logs the error
Side note: When you don't provide a rejection handler in a Promise chain (the second parameter in .then), the default rejection handler essentially behaves like:
function defaultRejectionHandler(err) {
throw err;
}
Which means it re-throws whatever error was passed into it so the error can propagate to the next error handler that you do specify.
The problem is in your catch handler of loginUser function.
If you want to catch the error farther down the promise chain, you'll need to throw the error in the catch block.
.catch(() => {
dispatch(authError('Incorrect email or password'));
throw Error('Incorrect email or password');
});