Search code examples
node.jsfacebook-graph-apichatbotfacebook-messengeramazon-lex

Amazon Lex Facebook integration using Node.js request module


I've got my Lex bot configured with a Lambda function and integrated with Facebook messenger. That is all working well. Now I'm trying to get the Facebook user information in order to create a personalized welcome response using the user's name.

I can get the user information, so that is working well. Here's my Lambda code:

exports.handler = (event, context, callback) => {
console.log("EVENT= "+ JSON.stringify(event));
var start = new Date().getTime();

if (event.sessionAttributes == null){ event.sessionAttributes = {}; } 
if (event.requestAttributes == null){ event.requestAttributes = {}; }

// get messenger platform type that user used to access this Lex bot
if (event.requestAttributes['x-amz-lex:channel-type']){
    var accessType = event.requestAttributes['x-amz-lex:channel-type']; // 'Facebook' or 'Twilio-SMS'
} else {
    var accessType = "other";
}
if (!event.sessionAttributes['userInfo']) {

    if (accessType == "Facebook"){
        var pageAccessToken = event.requestAttributes['x-amz-lex:facebook-page-id'];
        var PSID = event.requestAttributes['x-amz-lex:user-id'];
    request({
                uri: 'https://graph.facebook.com/v2.6/'+PSID+'?fields=first_name,last_name,gender&access_token='+pageAccessToken
            },
            function (error, response, body){
                var end = new Date().getTime();
                console.log(end - start);

                body = JSON.parse(body);

                if (!error && response.statusCode == 200) {
                    event.sessionAttributes['userInfo'] = {
                        "first_name": body['first_name'],
                        "last_name": body['last_name'],
                        "gender": body['gender'],
                        "id": body['id']
                    };

                    console.log("FB USERINFO:"+ body);
                } else {
                    console.log("FB ERROR: "+error+" +++++++RESPONSE: "+response);
                }
            }
        );
    try {
            intentProcessor(event,
                (response) => {
                    console.log("RESPONSE= "+ JSON.stringify(response));
                    callback(null, response);
                });
        } catch (err) {
            callback(err);
        }
} else {
    try {
        intentProcessor(event,
            (response) => {
                console.log("RESPONSE= "+ JSON.stringify(response));
                callback(null, response);
            });
    } catch (err) {
        callback(err);
    }
}
};

In the code above I am doing try synchronously and Lex is responding to facebook before the callback (the no name function) even starts, and so sessionAttributes are never set with the userInfo.

But if I do this asynchronously, and place the try inside the callback by the time the user info is received and handled in the callback function, Facebook times out and the response never makes it back to the user.

Why does the asynchronous way time out in Facebook? Is there something wrong with this method? Is there a way to stall Facebook with a "one moment please" message until the callback returns?


Solution

  • Apparently you were not the only one with this problem. The issue is here:

    event.sessionAttributes['userInfo'] = {
                        "first_name": body['first_name'],
                        "last_name": body['last_name'],
                        "gender": body['gender'],
                        "id": body['id']
                    };
    

    Lex does not support multi-depth arrays/objects in the sessionAttributes value. Remove the ['userInfo'] level (both here and in any referencing code, of course) and everything should work.

    Source: https://docs.aws.amazon.com/lex/latest/dg/context-mgmt.html#context-mgmt-complex-attributes

    Disclaimer: I know @Jay personally and we spent a few hours video-chatting about this problem tonight to debug it. That doesn't appear to be against the rules, but I defer to the mods/admins for how to handle reputation.