return value of a firebase callable function - node.js

I have difficulties returning a value using a callable firebase function, while the same code works fine when it is a httprequest.
So what I am doing, is getting some user data, then getting some other data (a list of vessels) and then only return the vessels the user has edit rights for. I am very sure the accessibleVessels object holds some json data: I changed the function in a functions.https.onRequest firebase function and it went fine.
exports.getFormData = functions.https.onCall( (data, context) => {
const uid = context.auth?.uid.toString;
try {
const user = admin.firestore().
doc("users/"+uid)
.get().then((doc: any) => {
return doc.data();
});
const vessels = admin.firestore().
collection("vessels").get()
.then(mapSnapshot((doc: any) => doc.data()));
const accessibleVessels = vessels.filter((vessel: any) => {
return user.hasEditRights.some((right: any) => {
return vessel.name === right;
});
});
return accessibleVessels;
} catch (error) {
console.log(error);
return {"error": true};
}
});
When I run this I get:
Data cannot be encoded in JSON. [Function (anonymous)]
Looking in the documentation I understand that I do need to return json or a promise. I read other answers about this (returning a promise) but don't see how this would work in my example: in the examples I find not much is done with the data, it's just returned. I want to put the data in variables instead of chaining everything, so I can combine both. How should I do this?

The easiest way to fix this is using async/await:
exports.getFormData = functions.https.onCall(async(data, context) => { // 👈
const uid = context.auth?.uid.toString;
try {
const user = await admin.firestore() // 👈
.doc("users/"+uid)
.get().then((doc: any) => {
return doc.data();
});
const vessels = await admin.firestore() // 👈
.collection("vessels").get()
.then(mapSnapshot((doc: any) => doc.data()));
const accessibleVessels = vessels.filter((vessel: any) => {
return user.hasEditRights.some((right: any) => {
return vessel.name === right;
});
});
return accessibleVessels;
} catch (error) {
console.log(error);
return {"error": true};
}
});
I also recommend reading the MDN documentation on async/await, the Firebase documentation on sync, async, and promises and Doug's awesome series on JavaScript Promises in Cloud Functions.

Related

Why does Async firebase fetching is not working? (NODE JS)

Building a NodeJS REST API.
Trying to send load data from FireBase collection, then sending it to the user (as API response).
Looks like the problem is that it's not waits for the firebase fetch to resolve, but send back a response without the collection data. (tried to use ASYNC-AWAIT but its not working)
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {
snapshot.docs.forEach(msg => {
console.log(msg.data().messageContent)
return {
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}
})
})
}
try {
const chatData = await getChatData()
console.log(chatData)
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData
})
} catch (err) {
if (!err.statusCode) {
err.statusCode(500)
}
next(err)
}
}
As you can see, I've used 2 console.logs to realize what the problem, Terminal logs looks like:
[] (from console.logs(chatData))
All messages (from console.log(msg.data().messageContent))
Is there any way to block the code unti the firebase data realy fetched?
If I correctly understand, you want to send back an array of all the documents present in the messages subcollection. The following should do the trick.
exports.getChatMessages = async (req, res, next) => {
const chatId = req.params.chatId;
const collectionRef = db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc');
try {
const chatsQuerySnapshot = await collectionRef.get();
const chatData = [];
chatsQuerySnapshot.forEach((msg) => {
console.log(msg.data().messageContent);
chatData.push({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
});
});
console.log(chatData);
res.status(200).json({
message: 'Chat Has Found',
chatData: chatData,
});
} catch (err) {
if (!err.statusCode) {
err.statusCode(500);
}
next(err);
}
};
The asynchronous get() method returns a QuerySnapshot on which you can call forEach() for enumerating all of the documents in the QuerySnapshot.
You can only await a Promise. Currently, getChatData() does not return a Promise, so awaiting it is pointless. You are trying to await a fixed value, so it resolves immediately and jumps to the next line. console.log(chatData) happens. Then, later, your (snapshot) => callback happens, but too late.
const getChatData = () => new Promise(resolve => { // Return a Promise, so it can be awaited
db.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot(resolve) // Equivalent to .onSnapshot((snapshot) => resolve(snapshot))
})
const snapshot = await getChatData();
console.log(snapshot)
// Put your transform logic out of the function that calls the DB. A function should only do one thing if possible : call or transform, not both.
const chatData = snapshot.map(msg => ({
authorID: msg.data().authorID,
messageContent: msg.data().messageContent,
timeStamp: msg.data().timeStamp,
}));
res.status(200).json({
message: 'Chat Has Found',
chatData
})
Right now, getChatData is this (short version):
const getChatData = () => {
db
.collection('chats')
.doc(chatId)
.collection('messages')
.orderBy('timeStamp', 'asc')
.onSnapshot((snapshot) => {}) // some things inside
}
What that means is that the getChatData function calls some db query, and then returns void (nothing). I bet you'd want to return the db call (hopefully it's a Promise), so that your await does some work for you. Something along the lines of:
const getChatData = async () =>
db
.collection('chats')
// ...
Which is the same as const getChatData = async() => { return db... }
Update: Now that I've reviewed the docs once again, I see that you use onSnapshot, which is meant for updates and can fire multiple times. The first call actually makes a request, but then continues to listen on those updates. Since that seems like a regular request-response, and you want it to happen only once - use .get() docs instead of .onSnapshot(). Otherwise those listeners would stay there and cause troubles. .get() returns a Promise, so the sample fix that I've mentioned above would work perfectly and you don't need to change other pieces of the code.

NodeJS Async Await Undefined Response

I am trying to use async/await to wait for a request to complete and return some information before I proceed with the rest of my code. The function logs the correct response, but the await line says it received an undefined value. This is the function I am calling, which logs the correct response here console.log(loginResponse.idToken);
However, this line let newtoken = await AuthHelper.returnValidToken(token) logs an undefined instead of the response. What mistake am I making here?
returnValidToken: async (token) => {
await AuthHelper.msal
.acquireTokenSilent(loginRequest)
.then((loginResponse) => {
AuthHelper.decodedValidToken(
loginResponse.idToken.rawIdToken,
key,
(jsonToken) => {
if (jsonToken.result === "success") {
// debugger;
console.log(loginResponse.idToken);
return (loginResponse.idToken);
}
}
);
})
.catch((err) => {
console.log(err);
});
},
You should convert the code to use async/await completely
returnValidToken: async (token) => {
try{
const loginResponse = await AuthHelper.msal.acquireTokenSilent(loginRequest);
const jsonToken = await AuthHelper.decodedValidToken(loginResponse.idToken.rawIdToken,key)
if (jsonToken.result === "success") {
console.log(loginResponse.idToken);
return (loginResponse.idToken);
}
}
catch(e){
console.log(e)
return null;
}
},
This is assuming that the AuthHelper.decodedValidToken is also async
As far as I understand this, you should be using either async/await - or Promise.then().
But not both.
It's not that changing the style from a mix of async/await with then didn't work.
The problem is that you're missing a return before AuthHelper.decodedValidToken.
That said, I totally agree that you should choose one style (preferably async/await) and stick to it.

Express returns empty array

I currently have the following code
router.get('/uri', (request,response) => {
let final = [];
TP.find({userID: request.userID})
.then(tests =>{
tests.forEach(test => {
A.findById(test.assignmentID)
.then(assignment => {
final.push({
testID: test._id,
name: assignment.title,
successRate: `${test.passedTests}/${test.totalTests}`
})
})
.catch(error => {
console.log(error)
})
})
return response.send(final);
})
.catch(err => {
console.log(err);
return response.sendStatus(500);
})
})
The code is supposed to query 2 MongoDB databases and construct an array of objects with specific information which will be sent to the client.
However, I always get an empty array when I call that endpoint.
I have tried making the functions async and make them wait for results of the nested functions but without success - still an empty array.
Any suggestions are appreciated!
forEach doesn't care about promises inside it. Either use for..of loop or change it to promise.all. The above code can be simplified as
router.get('/uri', async (request,response) => {
const tests = await TP.find({userID: request.userID});
const final = await Promise.all(tests.map(async test => {
const assignment = await A.findById(test.assignmentID);
return {
testID: test._id,
name: assignment.title,
successRate: `${test.passedTests}/${test.totalTests}`
};
}));
return response.send(final);
});
Hope this helps.

How am I suppose to stub a function which is dependent on result of previous function?

I have recently started writing tests and I don't have much experience.If any of the community member could point me in the right direction I would be really thankful. My scenario is simple I am half way through it but unable to solve my exact problem. Below is my code..
return generateServiceToken(req.body.appId, req.body.token, req.auth.userId)
.then(result => {
someService
.createCredentialsForUser(
req.auth.userId,
result.user.uid,
result.user.token
)
.then(result => {
return res.status(201).send(result);
});
})
.catch(error => {
return res.status(500).send({ error: `Credentials not valid - ${error}` });
});
The generateToken function is responsible to call a third party api to generate some credentials for their platform and return us the create credentials.
function generateServiceToken(appId: String, token: String, userId: String) {
return new Promise ((resolve, reject)=>{
const apiURL = `https://someapi.com/api/api.php?op=useradd&token=${token}&addr=${userId}&appid=${appId}`;
request.post(apiURL, (error, response, body) => {
const resp = JSON.parse(body);
if (resp.error) return reject(resp.error);
return resolve(resp);
});
});
}
Whereas, the someService.createCredentialsForUser function is responsible to save those credentials in database and return back the result in simple json format.
I am just stuck in stubbing someService.createCredentialsForUser function while writing the test case for happy-path
My test case is below..
describe.only("controllers/v3/some/", () => {
const c = {};
before(() => {
c.sandbox = sinon.createSandbox();
c.someServiceStub = c.sandbox
.stub(someService, "createCredentialsForUser")
.resolves(VALID_OUTPUT);
});
describe("when the request is valid", () => {
before(() => {
c.agent = setupApp(authenticationMiddleware(USER_ID));
return test(c, VALID_REQUEST_BODY);
});
it("should return 201", () => {
expect(c.response.statusCode).to.equal(201);
});
it("should call createCredentialsForUser", () => {
expect(c.stubs.createCredentialsForUser.called).to.equal(true);
});
});
});
The TestCase function is as follows..
function testCase(context, body = VALID_REQUEST_BODY) {
context.sandbox.resetHistory();
console.log(body.length);
const c = context;
return context.agent
.put(`/v3/some/`)
.send(body)
.then(r => {
c.response = r;
});
//.catch(err=>{c.response=err});
}
My someService.createCredentialsForUser function is responsible to save data into database I want to stub that that in a way that I could expect response return from generateServiceToken
I tried couples of ways which are as follows ..
First, I tried to stub that function in before() but no luck it fails with
error : IllegalArgumentError: init() must be called prior to use.
Second, I tried
c.response = c.sandbox.stub(someService, 'createCredentialsForUser').returns(Promise.resolve(r));
in my test function to stub with the value of resolved promise but no luck in this case it fails with the same error as mentioned above.

NodeJs - Async/Await inside async/await

I have the following code. I expect the output: START,Middle,Middle,END
but instead I get this START,Middle,END,Middle
(FYI prices array has 2 values in my example)
console.log("START");
await Promise.all(prices.map(async(price) => {
let obj: any = {};
obj.normal = price.normal;
await new Transport(obj).save(async (err: any, doc: any) => {
console.log("Middle");
price.transport_id = doc._id;
});
}));
console.log("END");
console.log(prices);
Change the inner await to a return statement, otherwise prices.map() is generating an array of entries that are undefined instead of promises.
Since Transport#save() does not return a promise, you'll need to wrap it with a Promise constructor since it is a callback-style API, or refer to the documentation which may perhaps explain how to instead return a promise.
To wrap it, you could do something like this:
// ...
obj.normal = price.normal;
return new Promise((resolve, reject) => {
new Transport(obj).save((err: any, doc: any) => {
console.log('Middle');
if (err) return reject(err);
price.transport_id = doc._id;
resolve(price);
});
});

Resources