I am trying to return data from an external api in my inline Lambda function but when I test this in the developer console for Alexa, I get 'There was a problem with the requested skills response' and I can't work out why.
Also, as I am doing this from the AWS console, I can't console.log to see what it actually being returned.
(I have removed the default intents for the sake of the post)
const request = require('request');
const handlers = {
'LaunchRequest': function () {
this.emit(':ask', 'Welcome');
},
'GiveUpdateIntent': function (){
var slot = this.event.request.intent.slots.line.value;
httpGet(slot, (theResult) => {
this.response.speak(theResult);
this.emit(':responseReady');
});
}
};
function httpGet(query, callback) {
var options = {
host: 'api.tfl.gov.uk',
path: '/line/' + encodeURIComponent(query) + '/status',
method: 'GET',
};
var req = http.request(options, res => {
res.setEncoding('utf8');
var responseString = "";
//accept incoming data asynchronously
res.on('data', chunk => {
responseString += chunk;
});
//return the data when streaming is complete
res.on('end', () => {
console.log(responseString[0]);
callback(responseString[0]);
});
});
req.end();
}
exports.handler = function (event, context, callback) {
const alexa = Alexa.handler(event, context, callback);
alexa.APP_ID = APP_ID;
alexa.registerHandlers(handlers);
alexa.execute();
};
"There was a problem with the requested skills response" generally means that the response from your skill was not in the expected format.
Your API request
Ex: vicotria
https://api.tfl.gov.uk/Line/victoria/Status
returns a JSON, and you can't directly pass it Alexa as response. Before you send it back to Alexa, take out status that you actually want Alexa to speak. Then put that into a meaningful sentence that any skill user will understand and send it back.
For example you can return something like:
var speech = "Status severity description for " +
this.event.request.intent.slots.line.value +
" is "
+ responseBody[0].lineStatuses.statusSeverityDescription;
this.emit(':ask',speech, "your re-prompt here");
This is a sample JSON that I got
[
{
"$type": "Tfl.Api.Presentation.Entities.Line, Tfl.Api.Presentation.Entities",
"id": "victoria",
"name": "Victoria",
"modeName": "tube",
"disruptions": [],
"created": "2018-07-31T12:11:08.477Z",
"modified": "2018-07-31T12:11:08.477Z",
"lineStatuses": [
{
"$type": "Tfl.Api.Presentation.Entities.LineStatus, Tfl.Api.Presentation.Entities",
"id": 0,
"statusSeverity": 10,
"statusSeverityDescription": "Good Service",
"created": "0001-01-01T00:00:00",
"validityPeriods": []
}
],
"routeSections": [],
"serviceTypes": [
{
"$type": "Tfl.Api.Presentation.Entities.LineServiceTypeInfo, Tfl.Api.Presentation.Entities",
"name": "Regular",
"uri": "/Line/Route?ids=Victoria&serviceTypes=Regular"
},
{
"$type": "Tfl.Api.Presentation.Entities.LineServiceTypeInfo, Tfl.Api.Presentation.Entities",
"name": "Night",
"uri": "/Line/Route?ids=Victoria&serviceTypes=Night"
}
],
"crowding": {
"$type": "Tfl.Api.Presentation.Entities.Crowding, Tfl.Api.Presentation.Entities"
}
}
]
CloudWatch:
Always make use of CloudWatch to see the logs of your Lambda function, you will get a link under Monitoring tab of your Lambda Function.
Configuring Lambda Test Events: You can test you Lambda code right from your inline editor by configuring Lambda Test Events under Test menu of your inline editor. A function can have up to 10 test events.
This is because your handler is returning before the callback is called. I strongly suggest to move away from callback based development in NodeJS and to use Promise instead.
I just answered a similar question, and provided sample code with promises. Check it here How to make an asynchronous api call for Alexa Skill application with a Lambda function?
The issue turned out to be with using http itself instead of https.
The only response back that I was getting was a status code of 302 which is a redirection because the api I was calling changes all http requests to https.
Therefore, I changed my import to https and used the https.get method (instead of http.get) to call the api and the correct response was returned.
Related
I have the following addEventToCalendar cloud function :
const { google } = require("googleapis");
const calendar = google.calendar("v3");
//const functions = require('firebase-functions');
const googleCredentials = require("./credentials.json");
const OAuth2 = google.auth.OAuth2;
const ERROR_RESPONSE = {
status: "500",
message: "There was an error adding an event to your Google calendar",
};
const TIME_ZONE = "EST";
const addEvent = (event, auth) => {
return new Promise(function (resolve, reject) {
calendar.events.insert(
{
auth: auth,
calendarId: "primary",
resource: {
summary: event.eventName,
description: event.description,
start: {
dateTime: event.startTime,
timeZone: TIME_ZONE,
},
end: {
dateTime: event.endTime,
timeZone: TIME_ZONE,
},
},
},
(err, res) => {
if (err) {
console.log("Rejecting because of error");
reject(err);
}
console.log("Request successful");
resolve(res.data);
}
);
});
}
exports.addEventToCalendar = functions.https.onRequest((request, response) => {
const eventData = {
eventName: request.body.eventName,
description: request.body.description,
startTime: request.body.startTime,
endTime: request.body.endTime,
};
console.log("Event Data: ", eventData);
const oAuth2Client = new OAuth2(
googleCredentials.web.client_id,
googleCredentials.web.client_secret,
googleCredentials.web.redirect_uris[0]
);
oAuth2Client.setCredentials({
refresh_token: googleCredentials.refresh_token,
});
addEvent(eventData, oAuth2Client)
.then((data) => {
response.status(200).send(data);
return;
})
.catch((err) => {
console.error("Error adding event: " + err.message);
response.status(500).send(ERROR_RESPONSE);
return;
});
});
URL looks like this : http://localhost:5001/prototype-dev-fadd5/us-central1/addEventToCalendar
How do I call this by passing the following data?
{
"eventName": "Firebase Event",
"description": "This is a sample description",
"startTime": "2018-12-01T10:00:00",
"endTime": "2018-12-01T13:00:00"
)
When I say call, it implies 2 things:
Call from a browser just to test it.
Call it from another firebase cloud function which runs on a trigger.
To call it from a browser, you can call it using XHR:
const data = {
eventName: 'Firebase Event',
description: 'This is a sample description',
startTime: '2018-12-01T10:00:00',
endTime: '2018-12-01T13:00:00'
}
fetch('http://localhost:5001/prototype-dev-fadd5/us-central1/addEventToCalendar', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
}).then(response => {
// ...
}).catch(error => {
// ...
});
To call it from another Cloud Function, I'd suggest exporting the addEvent function so you can import and use it elsewhere.
I'd also move the creation of the oAuth2Client object over to the addEvent() function so that you don't have to create an oAuth client before calling the addEvent() function.
If you can't re-use the addEvent() function because the two Cloud Functions live in separate projects and you can't share code between them, I'd call the function through XHR using a tool like Axios or node-fetch.
I know the tutorial you are using.
There are two good ways to test it:
Google Cloud Function Console, Testing tab
Postman
And for using it, I can think of 3 good ways to actually use it.
fetch Javascript from the frontend
Callable Firebase Functions from the frontend
export import to use from the backend.
Testing - Google Cloud Functions Console.
https://console.cloud.google.com/functions/
Testing - Postman
To test you can also use Postman.
Create a POST request with the URL of your function.
Then set the body to raw, with the type set to JSON. Once that is all setup, hit send and the response will show up if you did everything correctly.
See this image on where to click in Postman.
Using - fetch
Fetch provides a
global fetch() method that provides an easy, logical way to fetch
resources asynchronously across the network.
https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
And luckily, Postman will write the fetch code for you.
Click on the Code tab, and select Javascript - Fetch from the dropdown.
Using - Callable Functions
Callable Functions are functions you can call directly from your Javascript on the front end. They are explained here. https://firebase.google.com/docs/functions/callable
Biggest difference is instead of using
functions.https.onRequest
in your function declaration, you use:
functions.https.onCall
Using - export to use in Node.js on your server
If you want to be able to call a function directly on your Node.js server, aka your Firebase functions, if the function is in the same file, you call it like any other javascript file.
function normalFunction() {
//do work
return data;
}
exports.addEventToCalendar = functions.https.onRequest((request, response) => {
let data = normalFunction();
response.send(data);
}
You can put whatever you want in the normalFunction and call it where ever you want. Just make sure to use a promise, or await if it is doing something on a server etc.
If you want to use normalFunction from a different file, you would need to use export. https://developer.mozilla.org/en-US/docs/web/javascript/reference/statements/export
Firebase has a good doc on how you can organize multiple files here https://firebase.google.com/docs/functions/organize-functions
I have a Webhook that POSTs a JSON to my Cloud Function Trigger URL.
I want the Cloud Function to parse the JSON and write it to my Cloud Firestore.
I've tested the Webhook on webhook.site & requestbin.com : they are both receiving the POST request perfectly.
I am guessing that there is some syntax problem somewhere here, around the payload or req.body.
exports.wooCommerceWebhook = async (req, res) => {
const payload = req.body;
Additionally, this is not an authenticated request, and I deployed the function through the Google Cloud Platform - Cloud Function Console. I did not deploy this through the CLI, or through an application setup with firebase.
index.js
const admin = require('firebase-admin')
admin.initializeApp();
exports.wooCommerceWebhook = async (req, res) => {
const payload = req.body;
// Write to Firestore - People Collection
await admin.firestore().collection("people").doc().set({
people_Email: payload.billing.email,
people_FirstName: payload.billing.first_name,
people_LastName: payload.billing.last_name,
});
return res.status(200).end();
};
package.json
{
"name": "sample-http",
"version": "0.0.1",
"dependencies": {
"firebase-admin": "^9.4.2"
}
}
My Webhook that delivers a POST JSON to my Cloud Function URL:
{
"billing": {
"email": "test#test.com",
"first_name": "First",
"last_name": "Last"
}
}
EDIT:
I've added
.catch((err) => { console.log(err); })
and now my logs are returning:
Unhandled rejection
TypeError: Cannot read property 'email' of undefined at exports.wooCommerceWebhook (/workspace/index.js:18:43)
at /layers/google.nodejs.functions-framework/functions-framework/node_modules/#google-cloud/functions-framework/build/src/invoker.js:98:17
at processTicksAndRejections (internal/process/task_queues.js:77:11)
Function execution took 599 ms, finished with status: 'crash'
Error detected in test-function-1
I needed to declare each field's data type first.
let billing = "";
let people_Email = "";
let people_FirstName = "";
let people_LastName = "";
I have created an api, when called, triggers the lambda function, written in nodejs, to take the json(array of objects) and insert the data into dynamodb. For each object in the array, the function creates a PutRequest object and when finished calls the batchWriteItem function. When I test in the aws console everything works fine but when I try in postman I get a 500 error. I know that the event is different when coming from postman vs the console and you are supposed to reference "event.body" if you want to access the json however when I do that I get an error with event.body.ForEach: "Cannot read property 'forEach' of undefined" in the console and a 500 error in postman. Below is the code that works in the console
var dynamo = new AWS.DynamoDB({region: 'us-east-1',});
exports.handler = (event, context, callback) => {
const done = (err, res) => callback(null, {
statusCode: err ? '400' : '200',
body: err ? err.message : res,
});
var params = {
RequestItems: {
"Lead": []
}
}
event.forEach(x => {
params.RequestItems.Lead.push({
PutRequest: {
Item: {
"Address": {S: x.Address},
"City": {S: x.City},
"State": {S: x.State},
"Zipcode": {S: x.Zipcode},
"Owner_First_Name": {S: x.Owner_First_Name},
"Owner_Last_Name": {S: x.Owner_Last_Name}
}
}
})
})
dynamo.batchWriteItem(params, done);
};
When the lambda receive the json body from api gateway, it will be passed as a json string.
To convert the json string to json, You need to parse the event.body.
const body = JSON.parse(event.body)
Then you can do body.forEach
Hope this helps
Working on AWS Lex for creating a ChatBot and using the Node.js in AWS Lambda.
Error: An error has occurred: Received error response from Lambda:
Handled
Lambda function:
var aws = require('aws-sdk');
var ses = new aws.SES({region: 'us-east-1'});
exports.handler = function(event, context, callback) {
var eParams = {
Destination: {
ToAddresses: [event.currentIntent.slots.Email]
},
Message: {
Body: {
Text: {
Data: "Hi, How are you?"
}
},
Subject: {
Data: "Title"
}
},
Source: "abc#gmail.com"
};
var email = ses.sendEmail(eParams, function(err, data){
if(err)
else {
context.succeed(event);
}
});
};
How to get a proper response from Lambda to Lex after successful execution (Email Service works properly). I have tried context.done(); but it did not worked out.
Edit 1:
Tried adding below response test from AWS Documentation for LEX still getting the same error response.
exports.handler = (event, context, callback) => {
callback(null, {
"dialogAction": {
"type": "ConfirmIntent",
"message": {
"contentType": "PlainText or SSML",
"content": "message to convey to the user, i.e. Are you sure you want a large pizza?"
}
}
});
As mentioned in the lambda-input-response-format docs here fulfillmentState property is required in the response.
Other thing is you have to pass either PlainText OR SSML for the contentType in the response. In your case its just PlainText.
exports.handler = (event, context, callback) => {
callback(null, {
"dialogAction": {
"type": "ConfirmIntent",
"fulfillmentState": "Fulfilled", // <-- Required
"message": {
"contentType": "PlainText",
"content": "message to convey to the user, i.e. Are you sure you want a large pizza?"
}
}
});
The above code should solve your problem.
However if you see the req-res in the network tab you would receive HTTP Error 424 which says DependencyFailedException which says "Amazon Lex does not have sufficient permissions to call a Lambda function" very misleading.
This is probably a simple question but I'm new to cloud functions/Node programming and haven't found the right documentation yet.
How do I write a Google cloud function that will receive a HTTP request but then send a HTTP request to a different endpoint? For example, I can send the HTTP trigger to my cloud function (https://us-central1-plugin-check-xxxx.cloudfunctions.net/HelloWorldTest). Later in the project I'll figure out how to implement a delay. But then I want to respond with a new HTTP request to a different endpoint (https://maker.ifttt.com/trigger/arrive/with/key/xxxx). How do I do that?
exports.helloWorld = function helloWorld(req, res) {
// Example input: {"message": "Hello!"}
if (req.body.message === undefined) {
// This is an error case, as "message" is required.
res.status(400).send('No message defined!');
} else {
// Everything is okay.
console.log(req.body.message);
res.status(200).send('Success: ' + req.body.message);
// ??? send a HTTP request to IFTTT endpoint here
}
};
Here is the code that I managed to get working with help from Chetan Kanjani. When I send a text message to my Google Cloud function endpoint, it replys with a text message to IFTTT (a different endpoint).
const request = require('request');
exports.helloWorld = function helloWorld(req, res) {
// Example input: {"message": "Hello!"}
if (req.body.message === undefined) {
// This is an error case, as "message" is required.
res.status(400).send('No message defined!');
} else {
// Everything is okay.
console.log(req.body.message);
request.get('https://maker.ifttt.com/trigger/arrival/with/key/xxxx', function (error, response, body) {
console.log('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
console.log('body:', body); //Prints the response of the request.
});
res.status(200).send("Success");
}
};
I also had to change the package.json file to include the request package. It already had the sample-http package, I added the dependencies:
{
"name": "sample-http",
"version": "0.0.1",
"dependencies": {
"request": "^2.81.0"
}
}
I'm still not sure where the console.log function prints out the information. That might be helpful for future debugging.
The Request module uses callbacks. If you want to use JavaScript promises instead, the Axios module provides equivalent functionality.
Old, but I came across this while searching myself:
request module with promise support is (request-promise)
The code below worked. Not sure if Axios is the ideal module for simple requests like these, but the Google Cloud Function documentation uses Axios so it seemed sensible to also use Axios. Other answers use the request module, but it was deprecated in February 2020.
Note: GCF doesn't support ES6 natively at this time. ES6 support is coming with Node 13.
Package.json
{
"name": "YOUR_NAME",
"version": "0.0.1",
"dependencies": {
"axios": "^0.19.2"
}
}
Index.js
/**
* Required Modules
*/
const axios = require("axios");
/**
* Responds to any HTTP request.
*
* #param {!express:Request} req HTTP request context.
* #param {!express:Response} res HTTP response context.
*/
exports.run = async(req, res) => {
// Set API end point.
let apiURL = YOUR_URL;
// Wrap API parameters in convenient object.
let apiData = {
PARAM_1: PARAM_DATA,
PARAM_2: PARAM_DATA
};
// Invoke API.
axios.post(apiURL,
JSON.stringify(apiData)
)
.then((response) => {
res.status(200).send(response.data);
console.log(response);
}, (error) => {
res.status(500).send(response.data);
console.log(error);
});
};
Use https://www.npmjs.com/package/request module.
var request = require('request');
request.get('https://maker.ifttt.com/trigger/arrive/with/key/xxxx', function (error, response, body) {
console.log('error:', error); // Print the error if one occurred
console.log('statusCode:', response && response.statusCode); // Print the response status code if a response was received
console.log('body:', body); //Prints the response of the request.
});