AWS Lambda: "Cannot read property '0' of undefined" - node.js

Here is my AWS Lambda function. However, when running it, I get Cannot read property '0' of undefined.
const AWS = require('aws-sdk');
const SES = new AWS.SES();
const FROM_EMAIL_ADDRESS = process.env.FROM_EMAIL_ADDRESS;
const TO_EMAIL_ADDRESS = process.env.TO_EMAIL_ADDRESS;
function sendEmailToMe(formData) {
const emailParams = {
Source: FROM_EMAIL_ADDRESS,
ReplyToAddresses: ['keshijemi478#gmail.com'],
Destination: {
ToAddresses: [TO_EMAIL_ADDRESS],
},
Message: {
Body: {
Text: {
Charset: 'UTF-8',
Data: `Thanks for subscribe: ${formData.message}\n\n Name: ${formData.name}\n Email: ${formData.email}\n I will reply as soon as possible`,
},
},
Subject: {
Charset: 'UTF-8',
Data: 'New message from your_site.com',
},
},
};
console.log(emailParams);
const promise = SES.sendEmail(emailParams).promise();
console.log(promise);
return promise;
}
exports.handler = async(event) => {
console.log('SendEmail called');
const dynamodb = event.Records[0].dynamodb;
console.log(dynamodb);
const formData = {
name : dynamodb.NewImage.name.S,
message : dynamodb.NewImage.message.S,
email : dynamodb.NewImage.email.S
};
console.log(formData);
return sendEmailToMe(formData).then(data => {
console.log(data);
}).catch(error => {
console.log(error);
});
};

It appears that your code is an AWS Lambda function.
When a Lambda function is called, information is passed to the function via the event parameter. The information passed via event varies depending upon how the function was triggered. For example, if the function is triggered by an Amazon S3 Event, then S3 provides information in the event parameter that describes the object that caused the event to be triggered.
If, however, you trigger this event 'manually', then Amazon S3 is not involved and the event parameter only contains the information that you provided when you invoked the function.
If you are testing the function in the AWS Lambda management console, you can supply an event via the "Configure Test" option. The event provided in this configuration will then be passed to the function being tested.

Related

How to call Firebase onRequest Cloud Function?

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

#aws-sdk/client-lambda] - Invoke Lambda - Payload Response in Unit8Array - Convert to String

I am using the #aws-sdk/client-lambda npm package for invoking lambdas. I have two Lambdas. Lambda A & Lambda B. Lambda A is trying to invoke Lambda B.
Lambda A invokes Lambda B by running the following code:
const { LambdaClient, InvokeCommand } = require('#aws-sdk/client-lambda');
module.exports = {
getGitHubToken: async () => {
const client = new LambdaClient({ region: process.env.REGION });
const params = {
FunctionName: process.env.GITHUB_TOKEN_FUNCTION,
LogType: 'Tail',
Payload: '',
};
const command = new InvokeCommand(params);
try {
const { Payload } = await client.send(command);
console.log(Payload);
return Payload;
} catch (error) {
console.error(error.message);
throw error;
}
},
};
The expected response from Lambda B should look like this:
{
statusCode: 200,
body: JSON.stringify({
token: '123',
}),
};
However, Payload looks to be returning this from the line console.log(Payload);:
I looked on the AWS SDK Website and it looks like Payload returns a Uint8Array. I guess this is because it's from a promise?
I have tried doing Payload.toString() however that comes back as simply a string of the values in the Unit8Array. Example being:
2021-04-13T14:32:04.874Z worker:success Payload: 123,34,115,116,97,116,117,115,67,111,100,101,34,58,50,48,48,44,34,98,111,100,121,34,58,34,123,92,34,116,111,107,101,110,92,34,58,92,34,103,104,115,95,111,114,101,51,65,109,99,122,86,85,74,122,66,52,90,68,104,57,122,122,85,118,119,52,51,50,111,67,71,48,50,75,121,79,69,72,92,34,125,34,125
My Question:
How do I resolve data from Unit8Array to the data I was expecting from the Lambda response? Which is a JSON Object?
I have confirmed the requested Lambda (Lambda B in this case) is returning the data correctly by going to CloudWatch. Thanks.
Okay, I found a way to get this working.
You have to specify a text encoder:
const asciiDecoder = new TextDecoder('ascii');
Then decode it so it looks like this:
const data = asciiDecoder.decode(Payload);
I have logged an issue on their repository asking why this isn't included in the module. I will post an update on any movement on this.

AWS Lambda - unable to make call to 3rd resource

I have Lambda Node function behind API Gateway, what I am trying to do is:
Make call to API
Do some logic
Send a message to Slack
Problem:
When I run default test request, the function finishes successfully but it doesn't send a message to Slack (logs only print Slack and not HAHA1/2),however, it does when I run this function from local machine which leads me to believe that AWS stops any none client-server traffic. My function is not in VPC. What can I do to allow outgoing traffic to Slack? Thanks.
const AWS = require('aws-sdk');
const Slack = require('slack-node');
const dynamo = new AWS.DynamoDB.DocumentClient();
/**
* Demonstrates a simple HTTP endpoint using API Gateway. You have full
* access to the request and response payload, including headers and
* status code.
*
* To scan a DynamoDB table, make a GET request with the TableName as a
* query string parameter. To put, update, or delete an item, make a POST,
* PUT, or DELETE request respectively, passing in the payload to the
* DynamoDB API as a JSON body.
*/
sendSlack()
function sendSlack() {
webhookUri = "https://hooks.slack.com/services/....";
slack = new Slack();
slack.setWebhook(webhookUri);
console.log('Slack');
slack.webhook({
channel: "...",
username: "...",
text: "..."
}, function(err, response) {
console.log("HAHA1");
console.log(response);
console.log("HAHA2");
});
}
exports.handler = async (event, context) => {
console.log('Received event:', JSON.stringify(event, null, 2));
sendSlack()
let body;
let statusCode = '200';
const headers = {
'Content-Type': 'application/json',
};
try {
switch (event.httpMethod) {
case 'DELETE':
body = await dynamo.delete(JSON.parse(event.body)).promise();
break;
case 'GET':
body = await dynamo.scan({ TableName: event.queryStringParameters.TableName }).promise();
break;
case 'POST':
body = await dynamo.put(JSON.parse(event.body)).promise();
break;
case 'PUT':
body = await dynamo.update(JSON.parse(event.body)).promise();
break;
default:
throw new Error(`Unsupported method "${event.httpMethod}"`);
}
} catch (err) {
statusCode = '400';
body = err.message;
} finally {
body = JSON.stringify(body);
}
return {
statusCode,
body,
headers,
};
};
You will need to wait for the slack call by either providing a callback or promisifying it:
function sendSlack() {
return new Promise((resolve, reject) => {
webhookUri = "https://hooks.slack.com/services/....";
slack = new Slack();
slack.setWebhook(webhookUri);
console.log('Slack');
slack.webhook({
channel: "...",
username: "...",
text: "..."
}, function(err, response) {
if(err) return reject(err)
resolve(response)
});
})
}
Then in your code
await sendSlack()
It could be that your lambda handler is completing before your call to Slack is successfully executed.
Since the call to Slack is async, I would suggest you try to await its response before your handler returns a response.
I'm not 100% certain, by my guess is that the async Slack call gets scheduled but not run until the main body of the function has completed, at which point the lambda is "spun down" and nothing further is run.

AWS Lambda :: How to test my code on my local ubuntu machine?

I have this Lambda function code which is invoked by an SQS.
SQS triggers my Lambda function ( in nodeJS).
Lambda will also send out an SES email. Is there a way I can test this on my local Ubuntu rather than always using AWS web console?
Any help is appreciated.
Here is my Lambda NodeJS code: This code works only on AWS Lambda. When I run
$node index.js , it does not send out SES email.
var aws = require("aws-sdk");
var nodemailer = require("nodemailer");
aws.config.loadFromPath('aws_config.json');
var ses = new aws.SES();
var s3 = new aws.S3();
// Set the region
aws.config.update({region: 'us-west-2'});
exports.handler = function (event, context, callback) {
const response = {
statusCode: 200,
body: JSON.stringify({
message: 'SQS event processed.',
input: event,
}),
};
console.log('event: ', JSON.stringify(event.Records));
result = JSON.stringify(event.Records)
result = result.replace(/(^\[)/, '');
result = result.replace(/(\]$)/, '');
var resultObj = JSON.parse(result);
var idCustomer = resultObj.body;
console.log('===SENDING EMAIL====');
// Create sendEmail paramssd
var params = {
Destination: {
/* required */
CcAddresses: [
'XXXXX#gmail.com',
/* more items */
]
},
Message: {
/* required s*/
Body: {
/* required */
Html: {
Charset: "UTF-8",
Data: "BODY:"
},
Text: {
Charset: "UTF-8",
Data: "TEXT_FORMAT_BODY"
}
},
Subject: {
Charset: 'UTF-8',
Data: idCustomer
}
},
Source: 'xxxx#eeeee.com', /* required */
ReplyToAddresses: [
'wwwwww#wwwwwwwww.com',
/* more items */
],
};
// Create the promise and SES service object
var sendPromise = new aws.SES({apiVersion: '2010-12-01'}).sendEmail(params).promise();
// Handle promise's fulfilled/rejected states s
sendPromise.then(
function (data) {
console.log("Successfully sent using SES");
console.log(data.MessageId);
}).catch(
function (err) {
console.log("An Error occured while senting using using SES");
console.error(err, err.stack);
});
};
You should definetely take a look at SAM LOCAL. It is a tool developed by the AWS team specifically for testing lambdas.
https://github.com/awslabs/aws-sam-cli
Publishes a version of your function from the current snapshot of
$LATEST. That is, AWS Lambda takes a snapshot of the function code and
configuration information from $LATEST and publishes a new version.
The code and configuration cannot be modified after publication. For
information about the versioning feature, see
It is easy to use, you just type
sam local invoke --event event.json
And behind the scenes it will run a docker cotnainer for your lambda and call it.
Regarding your SES, you should put a small if(SAM_LOCAL) condition in the code and call the real one only if not in local mode. Note that SAM_LOCAL is env variable set by the SAM LOCAL tool when you run a function locally.
Good luck !
If you want to use aws as a backend - serverless framework is probably what you looking for https://serverless.com/ If you want to test your code without executing lambda on aws backend take a look at localastack framework https://github.com/localstack/localstack

AWS SES create template with lambda function always return null

so on my first time learning AWS stuff (it is a beast), I'm trying to create e-mail templates, I have this lambda function:
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set the region
AWS.config.update({ region: "us-east-1" });
exports.handler = async (event, context, callback) => {
// Create createTemplate params
var params = {
Template: {
TemplateName: "notification" /* required */,
HtmlPart: "HTML_CONTENT",
SubjectPart: "SUBJECT_LINE",
TextPart: "sending emails with aws lambda"
}
};
// Create the promise and SES service object
const templatePromise = new AWS.SES({ apiVersion: "2010-12-01" })
.createTemplate(params)
.promise();
// Handle promise's fulfilled/rejected states
templatePromise
.then((data) => {
console.log(data);
callback(null, JSON.stringify(data) );
// also tried callback(null, data);
}, (err) => {
console.error(err, err.stack);
callback(JSON.stringify(err) );
});
as far as I am understanding, this function should return me a template? an object, anything? when I use the lambda test functionality I always got null in the request response
does anyone know what I am doing wrong here?
edit: and It is not creating the e-mail template, I check the SES Panel - email templates and it is empty
edit2: if I try to return a string eg: callback(null, "some success message"); it does return the string, so my guess is something wrong with the SES, but this function is exactly what we have in the AWS docs, so I assume it should just work..
Try not to resolve the Promise and change your code to just returning it as-is:
return await templatePromise;
which should present you some more detail of what is really going wrong in your code - it might be some hidden access issue - so you might need to adjust the role your lambda function is using. createTemplate on the other side should not return much in case of successful execution but just create the template.
Also try to follow the following try/catch pattern when using async (as described here in more detail: https://aws.amazon.com/de/blogs/compute/node-js-8-10-runtime-now-available-in-aws-lambda/)
exports.handler = async (event) => {
try {
data = await lambda.getAccountSettings().promise();
}
catch (err) {
console.log(err);
return err;
}
return data;
};

Resources