NodeJS: AWS SDK V3: Not receiving any response data from lambda function - aws-lambda

I'm trying to use the v3 javascript sdk to invoke a AWS Lambda function, and I'm having problems getting any meaningful response.
My code looks like so...
const { Lambda } = require("#aws-sdk/client-lambda");
const client = new Lambda();
const params = {
FunctionName: "MyLamdaFuncton",
Payload: JSON.stringify({ "action": "do_something" }),
InvocationType: "Event"
};
client.invoke(params)
.then((response) => {
console.log(JSON.stringify(response,null,4));
})
.catch((err) => {
console.error(err);
})
I can confirm from checking the CloudWatch logs that the lambda function works as exepcted. However this is the response I get in my NodeJS code...
{
"$metadata": {
"httpStatusCode": 202,
"requestId": "d6ba189d-9156-4f01-bd51-efe34a66fe34",
"attempts": 1,
"totalRetryDelay": 0
},
"Payload": {}
}
How do I get the actual response and status from the Lambda function?
If I change the payload above to intentionally throw an exception in my Lambda, the response in the console is still exactly the same.
update:
The Lambda function is written in Ruby. The response is returned like so...
{ statusCode: 200, body: JSON.generate(response.success?) }
where "response" is from another service it calls internally.

I've figured out what I was doing wrong. The issue was the "InvocationType". I got it working by changing to...
InvocationType: "RequestResponse"
Then I had to extract the response data like so...
const response_data = JSON.parse(new TextDecoder("utf-8").decode(response.Payload))

Related

SQS send message error using #aws-sdk node js at AWS lambda

I am trying to send message from my AWS Lambda to AWS SQS but it isn't quiet working and throwing me the error.
2022-12-26T14:58:31.651Z 282ada00-ea4a-45b6-afe4-e3a7f16e8c5a INFO MissingParameter: The request must contain the parameter Label.
at throwDefaultError (/var/task/node_modules/#aws-sdk/smithy-client/dist-cjs/default-error-handler.js:8:22)
at deserializeAws_queryAddPermissionCommandError (/var/task/node_modules/#aws-sdk/client-sqs/dist-cjs/protocols/Aws_query.js:292:51)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async /var/task/node_modules/#aws-sdk/middleware-serde/dist-cjs/deserializerMiddleware.js:7:24
at async /var/task/node_modules/#aws-sdk/middleware-signing/dist-cjs/middleware.js:14:20
at async /var/task/node_modules/#aws-sdk/middleware-retry/dist-cjs/retryMiddleware.js:27:46
at async /var/task/node_modules/#aws-sdk/middleware-logger/dist-cjs/loggerMiddleware.js:5:22
at async sendToSQS (/var/task/sendToSqs.js:28:20)
at async exports.handler (/var/task/index.js:19:22) {
'$fault': 'client',
'$metadata': {
httpStatusCode: 400,
requestId: 'bf137e9a-24bc-52bd-9416-22b99c6b82f5',
extendedRequestId: undefined,
cfId: undefined,
attempts: 1,
totalRetryDelay: 0
},
Type: 'Sender',
Code: 'MissingParameter',
Detail: ''
}
I am not sure what parameters and in which way I need to include to make it work.
This is my code for sending message, where from my main module I send a simple data value as part of my message to be sent to SQS.
const { SQSClient, AddPermissionCommand } = require("#aws-sdk/client-sqs");
const sendToSQS=async(data)=>{
const client = new SQSClient({ region: "eu-west-1" });
var params = {
DelaySeconds: 0,
MessageAttributes: {
"Author": {
DataType: "String",
StringValue: "event params"
},
},
MessageGroupId:`${data}`,
MessageBody:JSON.stringify(data),
QueueUrl:"https://sqs.eu-west-1.amazonaws.com/000011110000/Salesforce-cqd-orders-delayer-retry"
};
try{
const command = new AddPermissionCommand(params);
let queueRes = await client.send(command);
console.info("[LAMBDA/#sqs] retry mechanism has succeeded. Data sent to SQS successfully")
console.log(queueRes)
const response = {
statusCode: 200,
body: "Data sent from lambda to sqs successfully.",
};
return response
}catch(error){
console.error("[LAMBDA/#s] retry mechanism has failed. Data wasn't sent to SQS")
console.log(error)
const response = {
statusCode: 200,
body: "Lambda to SQS error",
};
return response;
}
}
module.exports={sendToSQS}
Your message has delaySeconds which is not required and MessageGroupId which is only required for FIFO queue.
You can check sendMessage code reference from here AWS Wiki
Also, check this API reference for Send Message

Fitbit URL callback giving a response of NULL

I'm having trouble getting a response from a callback uri and I would really appreciate any help you could give me.
I am trying to use the Fitbit API which requires you to use a callback url to get an Auth Code.
Workflow:
1. Go to Fitbit url to get user to allow the app access to their personal data.
2. User agrees to the conditions
3. User gets redirected to my API
4. The API returns the code from (Code is located in URL and I can access it)
5. I console.log the code out to verify it
6. API returns the code
7. I work with code then exchanging it for an access token.
The problem is that I don't return the code (Or anything )when I return to the app even though I can console.log it on the API. The response I get is NULL
Here is the URL:
url = "https://www.fitbit.com/oauth2/authorize?response_type=code&client_id=CLIENT_ID&redirect_uri=https://REDIRECT_URL&scope=activity%20heartrate%20location%20nutrition%20profile%20settings%20sleep%20social%20weight&expires_in=604800";
I then open the URL in the InAPPBrowser successfully:
if (url !== "") {
const canOpen = await Linking.canOpenURL(url)
if (canOpen) {
try {
const isAvailable = await InAppBrowser.isAvailable()
if (isAvailable) {
const result =InAppBrowser.open(url, {
// iOS Properties
dismissButtonStyle: 'done',
preferredBarTintColor: 'gray',
preferredControlTintColor: 'white',
// Android Properties
showTitle: true,
toolbarColor: '#6200EE',
secondaryToolbarColor: 'black',
enableDefaultShare: true,
}).then((result) => {
console.log("Response:",JSON.stringify(result))
Linking.getInitialURL().then(url => {
console.log("Tests: ",url)
this._setTracker(url as string);
});
})
} else Linking.openURL(url)
} catch (error) {
console.log("Error: ",error)
}
}
}
From here the URL opens successfully.
Here is the API now which is done in Typescript on AWS serverless and Lambda
export const handler: APIGatewayProxyHandler = async (event, _context, callback) =>{
let provider = event.path
//prints code
let x = event.queryStringParameters
console.log("Code: ",x)
const response = {
statusCode: 200,
body: "Success"
};
return response;
}
Please let me know if further detail is required?
Thank you!
Right so it turns out what I was doing was correct apart from the response should have been 301 which is a redirect response.
const response= {
statusCode: 301,
headers: {
"location": `app://CALLBACK RESPONSE ADDRESS?type=${provider}`
},
body: "Boom"
}

Mock Graphql server with multiple stubs in Cypress

Problem:
I’m using cypress with angular and apollo graphQl. I’m trying to mock the graph server so I write my tests using custom responses. The issue here is that all graph calls go on a single endpoint and that cypress doesn’t have default full network support yet to distinguish between these calls.
An example scenario would be:
access /accounts/account123
when the api is hit two graph calls are sent out - a query getAccountDetails and another one with getVehicles
Tried:
Using one stub of the graph endpoint per test. Not working as it stubs with the same stub all calls.
Changing the app such that the query is appended 'on the go' to the url where I can intercept it in cypress and therefore have a unique url for each query. Not possible to change the app.
My only bet seems to be intercepting the XHR call and using this, but I don't seem to be able to get it working Tried all options using XHR outlined here but to no luck (it picks only the stub declared last and uses that for all calls) https://github.com/cypress-io/cypress-documentation/issues/122.
The answer from this question uses Fetch and therefore doesn't apply:
Mock specific graphql request in cypress when running e2e tests
Anyone got any ideas?
With cypress 6.0 route and route2 are deprecated, suggesting the use of intercept. As written in the docs (https://docs.cypress.io/api/commands/intercept.html#Aliasing-individual-GraphQL-requests) you can mock the GraphQL requests in this way:
cy.intercept('POST', '/api', (req) => {
if (req.body.operationName === 'operationName') {
req.reply({ fixture: 'mockData.json'});
}
For anyone else hitting this issue, there is a working solution with the new cypress release using cy.route2()
The requests are sent to the server but the responses are stubbed/ altered on return.
Later Edit:
Noticed that the code version below doesn't alter the status code. If you need this, I'd recommend the version I left as a comment below.
Example code:
describe('account details', () => {
it('should display the account details correctly', () => {
cy.route2(graphEndpoint, (req) => {
let body = req.body;
if (body == getAccountDetailsQuery) {
req.reply((res) => {
res.body = getAccountDetailsResponse,
res.status = 200
});
} else if (body == getVehiclesQuery) {
req.reply((res) => {
res.body = getVehiclesResponse,
res.status = 200
});
}
}).as('accountStub');
cy.visit('/accounts/account123').wait('#accountStub');
});
});
Both your query and response should be in string format.
This is the cy command I'm using:
import * as hash from 'object-hash';
Cypress.Commands.add('stubRequest', ({ request, response, alias }) => {
const previousInteceptions = Cypress.config('interceptions');
const expectedKey = hash(
JSON.parse(
JSON.stringify({
query: request.query,
variables: request.variables,
}),
),
);
if (!(previousInteceptions || {})[expectedKey]) {
Cypress.config('interceptions', {
...(previousInteceptions || {}),
[expectedKey]: { alias, response },
});
}
cy.intercept('POST', '/api', (req) => {
const interceptions = Cypress.config('interceptions');
const receivedKey = hash(
JSON.parse(
JSON.stringify({
query: req.body.query,
variables: { ...req.body.variables },
}),
),
);
const match = interceptions[receivedKey];
if (match) {
req.alias = match.alias;
req.reply({ body: match.response });
}
});
});
With that is posible to stub exact request queries and variables:
import { MUTATION_LOGIN } from 'src/services/Auth';
...
cy.stubRequest({
request: {
query: MUTATION_LOGIN,
variables: {
loginInput: { email: 'test#user.com', password: 'test#user.com' },
},
},
response: {
data: {
login: {
accessToken: 'Bearer FakeToken',
user: {
username: 'Fake Username',
email: 'test#user.com',
},
},
},
});
...
Cypress.config is what make it possible, it is kind of a global key/val getter/setter in tests which I'm using to store interceptions with expected requests hash and fake responses
This helped me https://www.autoscripts.net/stubbing-in-cypress/
But I'm not sure where the original source is
A "fix" that I use is to create multiple aliases, with different names, on the same route, with wait on the alias between the different names, as many as requests you have.
I guess you can use aliases as already suggested in Answer by #Luis above like this. This is given in documentation too. Only thing you need to use here is multiple aliases as you have multiple calls and have to manage the sequence between them . Please correct me if i understood you question in other way ??
cy.route({
method: 'POST',
url: 'abc/*',
status: 200.
response: {whatever response is needed in mock }
}).as('mockAPI')
// HERE YOU SHOULD WAIT till the mockAPI is resolved.
cy.wait('#mockAPI')

Returning custom http status codes on AWS Lambda error

I'm working with an AWS Lambda with serverless framework and I want to return a custom http status code when an error occurs but I'm always getting a 502 status code when I call my endpoint with axios.
module.exports.handler = async (event, context, callback) => {
try {
// some stuff
} catch (err) {
// error here
let myErrorObj = {
errorType : "InternalServerError",
httpStatus : 500,
requestId : context.awsRequestId,
trace : {
"function": "abc()",
"line": 123,
"file": "abc.js"
},
body: err
}
callback(JSON.stringify(myErrorObj));
}
}
But the object I'm getting back contains the property status: 502 and data.message: "Internal server error"
Any ideas of what is going on here?
status code 502 indicates the lambda's response to API Gateway is in an incorrect format.
The correct response for an async function (if not integration method stated in serverless YAML file, it would use Lambda Proxy Integration):
export const dummyFunction = async (event, context, callback) =>
{
// ... logic
return {
statusCode: 500,
body: JSON.stringify({...data}),
}
};
Callback is only for non-async function only. Please refer to full documentation.
Are you using API Gateway with your Lambda? If so, what you are returning in the callback is incorrect. The object you return must match this format: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format
So you can either just callback(err) and let AWS generate a 500 or if you want the added error context something like:
let myErrorObj = {
statusCode : 500,
body: JSON.stringify({
requestId : context.awsRequestId,
trace : {
"function": "abc()",
"line": 123,
"file": "abc.js"
}
error: err
}
})
}
callback(null, myErrorObj);
If you want requestId and trace attributes you would need to add them into the body.

AWS lambda function failing in Amazon Connect

I have written an AWS lambda function in node.js to send an email, which is invoked in an Amazon Connect contact flow. In the error branch it plays a prompt saying "lambda function failed." I verified the IAM role has permission to send email with SES, the sender/receiver emails are verified in SES, and also the lambda function has permissions for Amazon Connect.
The email does actually get sent out, but oddly I still hear the prompt "lambda function failed." Here is the code:
"use strict";
const aws = require('aws-sdk');
const sender = "Sender Name <sender#email.com>";
const recipient = "recipient#email.com";
const subject = "ALERT: no agents are logged in";
const body_text = "There are no agents logged in";
const body_html =
`<html>
<head></head>
<body>
<h1>ALERT</h1>
<p>Ther are no agents logged in to take calls in the queue.</p>
</body>
</html>`;
const charset = "UTF-8";
let params = {
Source: sender,
Destination: {
ToAddresses: [
recipient
],
},
Message: {
Subject: {
Data: subject,
Charset: charset
},
Body: {
Text: {
Data: body_text,
Charset: charset
},
Html: {
Data: body_html,
Charset: charset
}
}
},
};
const ses = new aws.SES({apiVersion: '2010-12-01'});
exports.handler = function(event, context, callback) {
ses.sendEmail(params, function(err, data) {
if(err) {
console.log("fail");
callback(err, err.message);
}
else {
console.log("success");
callback(null);
}
});
};
I checked the cloudwatch logs and don't see any error:
00:17:24
START RequestId: 17f1e239-990e-11e8-96bb-a1980f44db91 Version: $LATEST
00:17:24
2018-08-06T00:17:24.723Z 17f1e239-990e-11e8-96bb-a1980f44db91 success
00:17:24
END RequestId: 17f1e239-990e-11e8-96bb-a1980f44db91
00:17:24
REPORT RequestId: 17f1e239-990e-11e8-96bb-a1980f44db91 Duration: 226.51 ms Billed Duration: 300 ms Memory Size: 128 MB Max Memory Used: 32 MB
How do I troubleshoot this?
EDIT:
I enabled contact flow logs. In CloudWatch I noticed this:
{
"Parameters": { "FunctionArn": "arn:aws:lambda:us-west-2:769182588423:function:noAgentEmail", "TimeLimit": "8000" },
"Timestamp": "2018-08-06T06:36:31.786Z",
"ContactFlowModuleType": "InvokeExternalResource",
"Results": "The Lambda Function Returned An Error.",
"ExternalResults": { "forceClose": "false" },
"ContactId": "458027b0-d895-439e-bc06-114500dce64a",
"ContactFlowId": "arn:aws:connect:us-west-2:769182588423:instance/1e2ddedd-8335-42fe-89de-1e986fc016ef/contact-flow/2329af39-682c-4dc8-b3a2-5e7fe64de5d2"
}
What's confusing is it indicates the lambda function returned something:
"ExternalResults": { "forceClose": "false" }
But this is clearly not the case given the code. What's going on?
There's a couple of things to ensure your working Lambda function can be used for Connect.
As mentioned in the above answer, the return value should be a flat JSON object. As the guide says:
Nested and complex objects are not supported.
So, it should look something like this when you execute the lambda directly:
Response:
{
"statusPop": "success",
"id": "1",
"name": "me",
}
The property names and values need to be
alphanumeric, dash, and underscore characters.
Oh, and here's what tripped me up:
Amazon Connect sends paramters as part of a hierarchical structure, so any parameters you're expecting need to be referenced as
event['Details']['Parameters']['statusPop'];
instead of
event['statusPop'];
And don't forget to add permissions using:
aws lambda add-permission --function-name function:my-lambda-function --statement-id 1 \
--principal connect.amazonaws.com --action lambda:InvokeFunction --source-account 123456789012 \
--source-arn arn:aws:connect:us-east-1:123456789012:instance/def1a4fc-ac9d-11e6-b582-06a0be38cccf
More info and in depth details of what I mentioned here: https://docs.aws.amazon.com/connect/latest/adminguide/connect-lambda-functions.html
Your lambda needs to return a flat filed JSON object. It looks like on success your lambda callbacks with null.
Update your exports to this:
exports.handler = function(event, context, callback) {
ses.sendEmail(params, function(err, data) {
if(err) {
console.log("fail");
//callback(err, err.message);
callback(null, {"status":"error"});
}
else {
console.log("success");
callback(null, {"status":"success"});
}
});
});
More information on lambda/connect requirements here: https://docs.aws.amazon.com/connect/latest/adminguide/connect-lambda-functions.html
Note:
The output returned from the function must be a flat object of key/value pairs, with values that include only alphanumeric, dash, and underscore characters. Nested and complex objects are not supported. The size of the returned data must be less than 32 Kb of UTF-8 data.
you must return a valid JSON object such as below example
export const handler = async(event) => {
console.log("event" + JSON.stringify(event));
let amount = event['Details']['Parameters']['amount'];
console.log("amount:" + amount);
let resultMap = {
"amountValid": "valid"
};
console.log("resultMap" + JSON.stringify(resultMap));
return resultMap;
};

Resources