Https callable cloud function not returning value - javascript

I have a Flutter app and I'm trying to get a client nonce from braintree. Per the braintree documentation, I have this in my cloud function:
exports.getClientNonce = functions.https.onCall(async (data, context) => {
gateway.clientToken.generate({}, function (err, response) {
if (err) {
throw new functions.https.HttpsError('unknown', 'Error getting client nonce');
} else {
console.log(`token: ${response.clientToken}`);
return response.clientToken;
}
});
});
Then, in my Flutter app I call the function (again, I'm following what the plugin says):
try {
HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(
functionName: 'getClientNonce',
);
dynamic result = await callable.call({});
final value = result.data;
debugPrint('token: $value');
var data = await BraintreePayment().showDropIn(
nonce: value,
amount: '2.0',
enableGooglePay: false,
inSandbox: true);
print("Response of the payment $data");
} on CloudFunctionsException catch (e) {
debugPrint('An error occurred');
} catch (e) {
debugPrint('An error occurred');
}
}
I tried changing the cloud function so that it only returns a random number (as soon as the function is executed), and my Flutter app is correctly receiving the value (so the cloud function is communicating fine). And in my Firebase console, I am able to view the client nonce specified by console.log. But the function is for whatever reason unable to return the actual client nonce. (It should be should be some string hash that is >2000 characters long)

The callable function needs to return a promise from the top-level of the function callback that resolves with the value to return. Right now, you're returning nothing from the top-level. The return you have now is just returning a value from the inner callback function that you pass to braintree API. This isn't going to propagate to the top level.
What you need to do is either use a version of braintree API that returns an API (if one exists), or promisify the existing call that uses a callback.
See also "3. Node style callback" here: How do I convert an existing callback API to promises?
I have not tested this, but the general format if you apply that pattern will look more like this:
exports.getClientNonce = functions.https.onCall(async (data, context) => {
return new Promise((resolve, reject) => {
gateway.clientToken.generate({}, function (err, response) {
if (err) {
reject(new functions.https.HttpsError('unknown', 'Error getting client nonce'));
} else {
console.log(`token: ${response.clientToken}`);
resolve(response.clientToken);
}
});
});
});

Related

Firebase Cloud function is unable to write a document after using paypal sdk

I am having problems with this function after I call a get order paypal function. It successfully gets the order details and write it to the console. However after that, it is supposed to write to a Firestore document but it doesnt do that. There is no error on the functions log only that it started and executed the functions. If I remove the paypal function it then can write to documents so I know that code works. I just dont know what I am messing up. Any help would be appreciated thank you.
const functions = require('firebase-functions');
// Import and initialize the Firebase Admin SDK.
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
const paypal = require('paypal-rest-sdk');
paypal => configured here
exports.createRecord = functions.firestore.document('messages/{messageId}').onCreate(
async (snapshot) => {
var orderId = "3L62701538009611M";
paypal.order.get(orderId, function (error, order) {
if (error) {
console.log(error);
return false;
} else {
console.log("Get Order Response",JSON.stringify(order));
var userObject = {
displayName : 'BarryAllen',
email : 'FMA#Speedster.com',
};
return admin.firestore().doc('paid/docnametest').set(userObject)
}
});
});
function logs
5:25:50.643 PM
checkWork
Function execution took 3559 ms, finished with status: 'ok'
5:25:47.516 PM
createRecord
Function execution took 236 ms, finished with status: 'ok'
5:25:47.281 PM
createRecord
Function execution started
5:25:47.085 PM
checkWork
Function execution started
You need to return a promise that resolves when all the asynchronous work is complete. Right now, the function is returning nothing, which means it will terminate immediately and the call to paypal will but shut down. The return statement inside the callback function isn't returning a promise to the top-level function.
What you will need to do is "promisify" the call to paypal, as they currently don't support promises (unless you want to use their 2.0 beta).
If you promisify, it will look something like this, which you will probably want to change based on what you really want to do with the response from paypal:
const p = new Promise((resolve, reject)) => {
paypal.order.get(orderId, function (error, order) {
if (error) {
console.log(error);
reject(error);
} else {
console.log("Get Order Response",JSON.stringify(order));
resolve(order);
}
}
})
return p.then(order => {
var userObject = {
displayName : 'BarryAllen',
email : 'FMA#Speedster.com',
};
return admin.firestore().doc('paid/docnametest').set(userObject).then
})

Is it possible to access the argument of an arrow function from another nested arrow function?

I'm trying to build a backend webhook for the Google Assistant that reads records from DynamoDB.
This is my code:
// Handle the Dialogflow intent named 'trip name'.
// The intent collects a parameter named 'tripName'.
app.intent('trip name', (conv, {tripName}) => {
const dynamoDb = IS_OFFLINE === true ?
new AWS.DynamoDB.DocumentClient({
region: 'ap-south-1',
// endpoint: 'http://127.0.0.1:8080',
}) :
new AWS.DynamoDB.DocumentClient({
region: 'ap-south-1',
// endpoint: 'http://127.0.0.1:8080',
});
const params = {
TableName: ACTIVITIES_TABLE
Key: {
'name':tripName
}
};
// conv.close('error retrieving!'); THIS WORKS
dynamoDb.get(params, (error, result) => {
// conv.close('error retrieving!'); THIS DOES NOT
if (error) {
conv.close('error retrieving!');
}
else {
conv.close(JSON.stringify(result, null, 2));
}
});
});
If I were to use conv from outside the DynamoDB function it works, but from inside it does not and return this error:
2019-08-03T03:56:22.521Z ** ERROR Error: No response has been set. Is this being used in an async call that was
not returned as a promise to the intent handler?
It made me conclude that maybe I'm not allowed to access an arrow function argument from another nested arrow function?
I'm using Actions on Google Client Library.
The problem has nothing to do with accessing parameters from one arrow function in another - that is perfectly allowed.
The issue is, as the error message suggests, that you are using an asynchronous function (a function that requires a callback0, but not returning a Promise object. The actions-on-google library requires you to return a Promise from your Intent Handler if you're doing any asynchronous operations so it knows to wait for those operations to complete.
You'll need to switch from using a callback in your call to dynamoDb.get() to using a Promise instead. To do this, you need to not include the callback function, so get() returns an AWS.Request object. This object has a promise() method, so you would use this to return the results from the Promise and then() chain. (And you must return this Promise.)
In your case, it might look something like this
return dynamoDb.get(params).promise()
.then( result => {
conv.close(JSON.stringify(result, null, 2));
})
.catch( error => {
conv.close('error retrieving!');
});

How to get promise.response to return an API’s response?

TLDR: my promise.response needed to be called within both the API call and the promise.
I am attempting to get a return value from an API call via a Promise for a simple Express.js server.
This seems to be a topic of a lot of questions, but I have yet to successfully adapt an implementation to this case. I've also tried:
placing the API call within resolve()
async/wait implementations (willing to revisit)
Here's the basic structure of the code in question. There's a comment above the section where the trouble probably is.
Promise
const externalModule = require('<route to module>');
let promise = new Promise(function(resolve,reject) {
// This is probably where the problem is
let returnValue = externalModule.apiCall(parameters);
resolve(returnValue);
});
promise.then(function(returnValue) {
console.log(returnValue);
});
External Module
module.exports = {
apiCall: function(parameters) {
apiCall(
parameters,
function(err, response) {
if (err) {
console.error(err);
return;
} else {
console.log("success");
return response
}
}
)
}
};
If the code were working properly, we'd see two strings. One from inside the API call ("success") and another from it's return value. Because undefined is appearing before "success," we know that the resolve function has fired before the function above it has returned.
Logs from the Shell
> undefined
> "success"
You aren't providing a way to use the response from the api call. Convert that toa promise and then use it.
module.exports = {
apiCall: function(parameters) {
return new Promise((res, rej) => {
apiCall(
parameters,
function(err, response) {
if (err) {
rej(err);
} else {
res(response);
}
}
)
});
}
};
Then use it like so
let promise = externalModule.apiCall(parameters);

why does this mysql error causes nodejs to crash instead of going to the catch function?

I have a mysql statement that creates an entry, it has a .then function and a .catch function, but when the following error occurs:
TypeError('Bind parameters must not contain undefined. To pass SQL NULL specify JS null');
the server crashes instead of answering a 500 like defined in the .catch function
Note: I'm using the mysql2 library from npm with promises (require('mysql2/promise');)
Here's the code that calls it (req.params.account_name is undefined):
const CREATE_ACCOUNT_STATEMENT =
'INSERT INTO `Accounts` (`account_token`, `account_name`) VALUES (?, ?)'
try {
mysqlConnectionPool.execute(CREATE_ACCOUNT_STATEMENT, [
account_token, account_name
])
.then(() => {
res.end(JSON.stringify({ token: account_token }))
})
.catch((e) => {
debug(1, "error while trying to create account:", e)
res.status(500).end("Internal Server Error")
})
} catch(e) {
debug(1, "error while trying to create account:", e)
res.status(500).end("Internal Server Error")
}
Actually, #Quentine was close to the right thing...
It is "sort of" a bug in mysql2,
i use sort-of because https://github.com/sidorares/node-mysql2/issues/902 suggests the development team of mysql2 is o.k. with it.
it is an issue with the way mysql2.pool passes the call to the created connection, which does not pass the exception to the wrapping promise.
I ended up making my own wrapping function to create the connection + call execute wrapped in proper promise handling.
import mysql = require('mysql2');
private async queryDB(query:string, useExecute: boolean = false, ...args:any[]) : Promise<any[]>
{
return new Promise<any[]>((resolve, reject)=>{
for(var i = 0; i < args.length; ++i)
{
if(args[i]===undefined)
args[i] = null;
}
this.dbPool.getConnection((err, conn)=>{
if(err){
reject(err);
return;
}
let cb = function(err: mysql.QueryError, results: any[], fields: mysql.FieldPacket[]) {
conn.release();
if(err)
{
reject(err);
return;
}
resolve(results);
}
if(useExecute)
conn.execute(query, args, cb);
else
conn.query(query, args, cb);
});
});
}
mysqlConnectionPool.execute is throwing the exception before creating a promise.
i.e. the exception is not thrown from within the promise.
To catch it you would need to try {} catch (e) {} around the call to mysqlConnectionPool.execute.
Well,
I'm guessing that you are using the standard mysql package which it seems not supporting Promises, instead, it accepts a standard node callback function(err, results, fields) {} as an argument of the execute method.
So since you haven't defined a valid callback the script will just throw an exception.

Meteor: Exception in delivering result of invoking method

Similar issues have been posted, but none quite match what I've run into. I'm doing a simple POST to an internal server to get back product data. The call is successful and I see the JSON data correctly logging to my terminal when I do a console.log on the server side. The issue arises on the client side, when in the callback, the result and error both are undefined.
Server:
Meteor.methods({
ProductSearch: function(searchTerm) {
var method = 'POST';
var url = 'server';
var options = {
headers:{"content-type":"application/json"},
data: {
query:"trees"
}
};
return HTTP.call(method, url, options, function (error, result) {
if (error) {
console.log("ERROR: ", result.statusCode, result.content);
} else {
var txt = JSON.parse(result.content);
console.log("SUCCESS: Found "+txt.totalResults+" products");
}
});
}
});
Client:
Meteor.call('ProductSearch', searchTerm, function (error, result) {
if (error) {
console.log("error occured on receiving data on server. ", error );
} else {
var respJson = JSON.parse(result.content);
Session.set("productSearchResults", respJson);
}
});
When I log the values of error, and result on callback, they are both undefined, and I get the following error: Exception in delivering result of invoking 'ProductSearch': TypeError: Cannot read property 'content' of undefined
In your server-side method, you're not correctly returning the result of the HTTP.call, since you're using the asynchronous version, HTTP.call will return undefined and the result will only be accessible in the callback.
Use the synchronous version of HTTP.call instead and you'll be fine.
try{
var result = HTTP.call(method, url, options);
return JSON.parse(result.content);
}
catch(exception){
console.log(exception);
}
See the corresponding docs for HTTP.call for additional information.
asyncCallback Function
Optional callback. If passed, the method runs
asynchronously, instead of synchronously, and calls asyncCallback. On
the client, this callback is required.
https://docs.meteor.com/#/full/http_call

Categories

Resources