Unable to return an array of json from Cloud Firestore via Cloud Functions (onCall) to Swift - swift

I have a problem getting the result from a Cloud Function.
This is my Cloud Function:
exports.retrieveTrips = functions.https.onCall((data, context) => {
const uidNumber = context.auth.uid;
var arrayOfResults = new Array();
var idOfFoundDoc;
var query = admin.firestore().collection('Users').where('UID','==', uidNumber);
query.get().then(snapshot =>
{
snapshot.forEach(documentSnapshot =>
{
idOfFoundDoc = documentSnapshot.id;
});
var queryDoc = admin.firestore().collection('Users').doc(idOfFoundDoc).collection('Trips');
queryDoc.get().then(snapshot =>
{
snapshot.forEach(documentSnapshot =>
{
arrayOfResults.push(documentSnapshot.data());
});
console.log('ARRAY: ' , arrayOfResults);
return arrayOfResults;
})
.catch (err =>
{
console.log ('Error adding document: ', err);
});
})
.catch (err => {
//response.send('Error getting documents', err);
console.log ('Error getting documents', err);
});
And this is the code that I have in my application.
#IBAction func RetrieveTripsButton(_ sender: Any)
{
self.functions.httpsCallable("retrieveTrips").call() {(result, error) in
if let error = error as NSError? {
if error.domain == FunctionsErrorDomain
{
let message = error.localizedDescription
print ("Message: " + message)
}
return
}
print ("Result: -> \(type(of: result))")
print("Result.data type: \(type(of: result?.data))");
print ("Result.data -> \(result?.data)")
}
}
And this is the printed result.
Result: -> Optional<FIRHTTPSCallableResult>
Result.data type: Optional<Any>
Result.data -> Optional(<null>)
The console log is able to print arrayOfResults correctly. Furthermore, when I change this functions to onRequest and feed it the relevant information, the res.status(200).send(arrayOfResults) is able to display the array of JSON in the page.
If I placed the return arrayOfResults; outside of the .then function, I would get a result along with an empty array. My issue is similar to this problem here but I'm unable to receive even that when I return { text: "some_data" }; .
Any help would be great, thank you!

You have to chain the different promises and return the result of the promises chain, as follows.
Note that it is actually what the OP explains in his answer to the SO post you mention "The issue was that I forgot to return the actual promise from the cloud function".
exports.retrieveTrips = functions.https.onCall((data, context) => {
const uidNumber = context.auth.uid;
const arrayOfResults = new Array();
let idOfFoundDoc;
const query = admin.firestore().collection('Users').where('UID','==', uidNumber);
return query.get().then(snapshot => { // here add return
snapshot.forEach(documentSnapshot =>
{
idOfFoundDoc = documentSnapshot.id;
});
const queryDoc = admin.firestore().collection('Users').doc(idOfFoundDoc).collection('Trips');
return queryDoc.get(); // here add return and chain with then()
})
.then(snapshot => {
snapshot.forEach(documentSnapshot => {
arrayOfResults.push(documentSnapshot.data());
});
console.log('ARRAY: ' , arrayOfResults);
return { arrayOfResults : arrayOfResults }; //return an object
})
.catch (err => {
console.log ('Error getting documents', err);
//Here you may return an error as per the documentation https://firebase.google.com/docs/functions/callable#handle_errors, i.e. by throwing an instance of functions.https.HttpsError
});
});
I would also suggest that you look at these two videos from the Firebase team, about Cloud Functions and promises: https://www.youtube.com/watch?v=7IkUgCLr5oA and https://www.youtube.com/watch?v=652XeeKNHSk.

Related

how to get callback return value in nestjs

I am going to use vonage for text service.
However, only node.js syntax exists, and the corresponding API is being used.
There is a phenomenon that the callback is executed later when trying to receive the values ​​returned from the callback to check for an error.
How can I solve this part? The code is below.
await vonage.message.sendSms(from, to, text, async (err, responseData) => {
if (err) {
console.log('1');
result.message = err;
} else {
if (responseData.messages[0]['status'] === '0') {
console.log('2');
} else {
console.log('3');
result.error = `Message failed with error: ${responseData.messages[0]['error-text']}`;
}
}
});
console.log(result);
return result;
When an error occurs as a result of executing the above code,
result{error:undefined}
3
Outputs are in order.
From what I can understand the issue is that you are passing a async callback. you could simply just give vonage.message.sendSms() a synchronous callback like so.
const result = {};
vonage.message.sendSms(from, to, text, (err, responseData) => {
if (err) {
console.log('1');
result.message = err;
} else {
if (responseData.messages[0]['status'] === '0') {
console.log('2');
} else {
console.log('3');
result.error = `Message failed with error: ${responseData.messages[0]['error-text']}`;
}
}
});
if you want to use async or promises I would suggest something like this
const sendSMS = (from, to, text) => new Promise( (resolve, reject) => {
vonage.message.sendSms(from, to, text, (err, responseData) => {
if (err) {
reject(err);
} else {
resolve(responseData);
}
});
});
// elsewhere
sendSMS(from, to, text)
.then(...)
.catch(...);

"Cannot read property 'uid' of undefined" error - Plaid link token

I'm getting a "Cannot read property 'uid' of undefined" when trying to create a plaid token....I have spent like 3 days trying to make it work.
does anybody knows how to fix it?
Cloud function to get Plaid token
//PLAID - create link Token plaid Nat
const plaid = require("plaid");
exports.createPlaidLinkToken = functions.https.onCall(async (data, context) => {
const customerId = context.auth.uid;
const plaidClient = new plaid.Client({
clientID: functions.config().plaid.client_id,
secret: functions.config().plaid.secret,
env: plaid.environments.development,
options: {
version: "2019-05-29",
},
});
return plaidClient.createLinkToken({
user: {
client_user_id: customerId,
},
client_name: "Reny",
products: ["auth"],
country_codes: ["US"],
language: "en",
})
.then((apiResponse) => {
const linkToken = apiResponse.link_token;
return linkToken;
})
.catch((err) => {
console.log(err);
throw new functions.https.HttpsError(
"internal",
" Unable to create plaid link token: " + err
);
});
});
swift function
class func createLinkToken(completion: #escaping (String?) -> ()){
//this is the firebase function
Functions.functions().httpsCallable("createPlaidLinkToken").call { (result, error) in
if let error = error {
debugPrint(error.localizedDescription)
return completion(nil)
}
guard let linkToken = result?.data as? String else {
return completion(nil)
}
completion(linkToken)
}
}
The only .uid in your code is in this line:
const customerId = context.auth.uid;
So it seems like context.auth is undefined. In the Cloud Functions code you can handle this with:
exports.createPlaidLinkToken = functions.https.onCall(async (data, context) => {
// Checking that the user is authenticated.
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'while authenticated.');
}
const customerId = context.auth.uid;
...
The new code here comes from the Firebase documentation on handling errors in callable Cloud Functions.
You'll also want to check if the user is signed in in your Swift code.

Firebase Cloud Functions Array Parameter Undefined

When I try to put an array as a parameter for cloud functions. In my code, the array shows up correct, but in the logs for the cloud function it shows up as undefined.
Array Result:
["user1", "user2"]
Cloud Function:
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.chatNotifications = functions.https.onCall((data, context) => {
const title = data.title;
const payloadMsg = data.message;
const memberList = data.membersList;
functions.logger.log("Member List:", memberList);
for (let i = 0; i < memberList.count; i++) {
const memberName = memberList[i];
const message = {
notification: {
title: title,
body: payloadMsg,
},
topic: memberName,
apns: {
payload: {
aps: {
badge: "1",
},
},
},
};
admin.messaging().send(message)
.then((response) => {
// Response is a message ID string.
console.log("Successfully sent message:", response);
})
.catch((error) => {
console.log("Error sending message:", error);
});
}
});
Code:
functions.httpsCallable("chatNotifications").call(["title": chatName, "message": name + " has been added to the group", "memberList": memberList]) { (result, error) in
if let error = error as NSError? {
if error.domain == FunctionsErrorDomain {
let message = error.localizedDescription
print(message)
}
}
if let text = result?.data as? String {
print("Result: ", text)
}
}
I can see that in your code you are sending this:
"memberList": memberList
But in the cloud function you have this:
data.membersList
The name of the data is different, also use
memberList.length
instead of
memberList.count

Migrating callbacks to Async

I'm struggling with migrating a HAPI function that verifies a JWT token and then makes a database call using the decoded credentials.
The problem is that jwt.verify uses a callback, but Hapi and Hapi.MySQL2 have both been updated to use async functions
The main function is as follows
exports.LoadAuth = (req, h) => {
let token = req.headers.authorization.split(' ')[1]
VerifyToken(token, async function (err, decoded) {
if (!err) {
let sql = '#SELECT STATEMENT USING decoded.id'
const [data] = await mfjobs.query(sql, decoded.id)
let auids = []
data.forEach(function (ag) {
auids.push(ag.Name)
})
auids = base64(auids.toString())
return auids
} else {
return {message: 'Not Authorised'}
}
})
}
The VerifyToken function is as follows:
VerifyToken = (tok, done) => {
jwt.verify(tok, Buffer.from(secret, 'base64'), function (err, decTok) {
if (err) {
done(err)
} else {
done(null, decTok)
}
})
}
Debugging everything above works up to the point that the data should be returned to the front end. At which point I get an ERROR 500
I know that the issue is with the VerifyToken function as if I omit this and hard code the decoded.id into the query the correct data reaches the front end.
Any pointers?
You can convert your VerifyToken function to Promises.
let VerifyToken = (tok) => {
return new Promise((resolve, reject) => {
jwt.verify(tok, Buffer.from(secret, 'base64'), function (err, decTok) {
if (err) {
reject(err)
} else {
resolve(decTok)
}
})
});
}
Now you have a function that you can use with async await notation and internally checks jwt validation via callbacks.
Then we can slightly modify your controller as follows.
exports.LoadAuth = async (req, h) => {
let token = req.headers.authorization.split(' ')[1];
try {
let decoded = await VerifyToken(token);
let sql = '#SELECT STATEMENT USING decoded.id';
const [data] = await mfjobs.query(sql, decoded.id);
let auids = [];
data.forEach(function (ag) {
auids.push(ag.Name)
});
auids = base64(auids.toString());
return auids
} catch (e) {
return {message: 'Not Authorised'}
}
}
We just converted your handler function to async function, and we already have a VerifyToken function that returns a promise so, we can call it with the await operator.

Return data in json after subscribe

I am using Angular 5 and want to return data from function getDionaeaResults in json format after subscribing to service
getDionaeaResults(sql) : any {
this.dionaeaService.getDionaeaConnectionLogs(sql).subscribe(res => {
this.data = res;
}),
(error: any) => {
console.log(error);
});
return this.data;
}
After calling this function, this.totalAttacks prints undefined.
getTotalAttack() {
this.totalAttacks = this.getDionaeaResults("some query")
console.log(this.totalAttacks,'attacks')
}
Would suggest using the Obseravable .map() function.
getDionaeaResults(sql) : Observable<any> {
return this.dionaeaService
.getDionaeaConnectionLogs(sql)
.map(res => res);
}
getTotalAttack(sql){
this.getDionaeaResults("some query")
.subscribe(
res => { this.totalAttacks = res; },
err => { console.log(err); }
);
}
this.getDionaeaResults is returning undefined because the service you're calling is asynchronous you have to wait for the subscribe callback. as Observables are asynchronous calls
this.data=res
might execute after the return statement. You can perhaps call that dionaeaService directly inside getTotalAttack() function, like this:
getTotalAttack(sql){
this.dionaeaService.getDionaeaConnectionLogs(sql).subscribe(res => {
this.totalAttacks = res;
}),
(error: any) => {
console.log(error);
});
}