AWS Websocket API - amazon-web-services

I am using the API Gateway Websocket.
When I send a message to the client from a Lambda function, it sometimes doesn't send a message and other times it sends 2-3 messages.
const AWS = require("aws-sdk")
const api = new AWS.ApiGatewayManagementApi({
endpoint : process.env.API_ENDPOINT
})
exports.handler = async (event) => {
console.log(event)
const body = JSON.parse(event.body)
const connectionId = event.requestContext.connectionId
sendMessage(connectionId, "My Message")
return {}
};
const sendMessage = (connectionId, response) => {
const data = { message : response }
const params = {
ConnectionId : connectionId,
Data : Buffer.from(JSON.stringify(data))
}
return api.postToConnection(params).promise()
}

Actually i had to await the sendMessage function. This solved my problem.

Related

Problem with AWS API Gateway websocket- Lambda postToConnection

I've been trying to solve this problem all day, looking everywhere on the web, even in the official AWS documentation, why does this error keep appearing when I try to send a message to the client through this code?
PS. i use SDK v3 with node.js 18
import {
ApiGatewayManagementApiClient,
PostToConnectionCommand,
} from "#aws-sdk/client-apigatewaymanagementapi";
export const handler = async (event) => {
const domain = event.requestContext.domainName;
const stage = event.requestContext.stage;
const connectionId = event.requestContext.connectionId;
const callbackUrl = `https://${domain}/${stage}`;
const client = new ApiGatewayManagementApiClient({ endpoint: callbackUrl });
const requestParams = {
ConnectionId: connectionId,
Data: "Hello!",
};
const command = new PostToConnectionCommand(requestParams);
try {
await client.send(command);
} catch (error) {
console.log(error);
}
return {
statusCode: 200,
};
};
fef17825-58ce-4ca7-8f38-85857f1aef0a Task timed out after 3.01 seconds
i tried any online guide or video, can anyone help me?

Why sending different message to AWS SQS return same message ID

I want to send messages to a FIFO sqs queue. Given a array of list different user ids, for each id, I want to call sendMessage command to send the id as message body. I'm expecting every time it will return a different message id, but actually they all return same messageId. Sample code below:
const sendMessage = async (params:ISqsRequestParam) => {
try {
const sqsResponse = await sqsClient.send(new SendMessageCommand(params));
console.log(`send message response: ${JSON.stringify(sqsResponse)}`);
return sqsResponse.MessageId; // For unit tests.
} catch (err) {
console.error('SQS sending', err);
}
};
export const handler = async function (event: IEventBridgeAddtionalParams, context: Context): Promise<string[]> {
console.info(`${context.functionName} triggered at ${event.time} under ${process.env.NODE_ENV} mode`);
console.info(`customor parameter value is ${event.custom_parameter}`);
try {
const sqsUrl: string = event.custom_parameter === 'Creator' ? process.env.BATCH_CREATOR_QUEUE_URL : process.env.BATCH_PROCESSOR_QUEUE_URL;
console.info(`SQS Url is: ${sqsUrl}`);
const tenantData: ITenantResponse = await fetchAllTenantIds();
console.info(`ResponseDate from tenant service: ${JSON.stringify(tenantData.value)}`);
// change to fix tenantId for development environment for better debugging and test
const data : ITenantDetails[] = process.env.NODE_ENV !== 'production' ? fixedTenantDataForNonProd() : tenantData.value;
const promise = data.map(async tenantDetails => {
if (!tenantDetails.tenantFailed) {
console.info(`Tenant Id in message body: ${tenantDetails.id}`);
const params: ISqsRequestParam = {
MessageBody: tenantDetails.id,
MessageDeduplicationId: `FP_Tenant_populator_${event.custom_parameter}`, // Required for FIFO queues
MessageGroupId: `FP_Tenant_populator_Group_${event.custom_parameter}`, // Required for FIFO queues
QueueUrl: sqsUrl //SQS_QUEUE_URL; e.g., 'https://sqs.REGION.amazonaws.com/ACCOUNT-ID/QUEUE-NAME'
};
const messageId:string = await sendMessage(params);
return messageId; //for unit testing
} else {
return null; //for unit testing
}
});
const messageIds:string[] = await Promise.all(promise); //for unit testing
const activeMessageIds:string[] = messageIds.filter(id=> id!= null); //for unit testing
console.info(`Success, ${activeMessageIds.length} messages sent. MessageID:${activeMessageIds[0]}`);
return activeMessageIds; //for unit testing
} catch (error) {
console.error(`Fetch tenant details error: ${error}`);
}
};

Using AWS Lambda Console to send push using SNS

I tried every possible solution on the internet with no hope
What I am trying to do is simply use aws lambda functions (through the aws console) to fetch user fcm token from lets say DynamoDB (not included in the question), use that token to create endpointArn, send push to that specific device
I tested to send Using SNS console and the push gets to the device successfully but I failed to get it to the device using Lambda functions although it gives success status and message ID
Here is the code I used
// Load the AWS SDK for Node.js
var AWS = require('aws-sdk');
// Set region
AWS.config.update({region: 'us-east-1'});
const sns = new AWS.SNS()
const sampleMessage = {
"GCM": {
"notification": {
"body": "Sample message for Android endpoints",
"title":"Title Test"
}
}
}
exports.handler = async (event) => {
const snsPayload = JSON.stringify(sampleMessage);
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
const params = {
PlatformApplicationArn: '<Platform Arn>',
Token: '<FCM Token>'
};
try {
const endpointData = await sns.createPlatformEndpoint(params).promise();
const paramsMessage = {
Message: snsPayload,
TargetArn: endpointData.EndpointArn
};
var publishTextPromise = await sns.publish(paramsMessage).promise();
response.MessageId = publishTextPromise.MessageId;
response.result = 'Success';
}
catch (e) {
console.log(e.stack)
response.result = 'Error'
}
return response;
};
After some trials and errors I figured out the solution for my own question
1- The GCM part of the payload should be a string not a json
2- The message parameter should have an attribute that explicitly sets the mime type of the payload to Json
Taking all that into consideration
const GCM_data = {
'notification': {
'body': 'Hellow from lambda function',
'title': 'Notification Title'
}
}
const data = {
"GCM": JSON.stringify(GCM_data)
}
const snsPayload = JSON.stringify(data)
and the params should look like
const paramsMessage = {
Message: snsPayload,
TargetArn: endpointData.EndpointArn,
MessageStructure: 'json'
};
and this will work :)

Validation error when I replace body in Lambda#Edge origin request function

I have Cloudfront in front of an s3 bucket that serves HLS videos. I'm trying to dynamically modify the manifest files to add an auth token to the segments inside of them.
What I would really like to do is modify the body I send back to the client in a viewer response function, but since that isn't possible, I'm attempting to use a origin request function to manually fetch the object from S3, modify it, and return a Cloudfront request with the new body. I get a 503 error of "The Lambda function result failed validation: The body is not a string, is not an object, or exceeds the maximum size"
My body is under 8kb (1MB is the limit in the docs). As far as I can tell the cloudfront request object I'm generating looks good and the base64 data decodes to what I want. I've also tried using text instead of base64. I have "include body" enabled in Cloudfront.
const fs = require('fs');
const querystring = require('querystring');
const AWS = require('aws-sdk');
const S3 = new AWS.S3();
exports.handler = async (event) => {
const cfrequest = event.Records[0].cf.request;
const queryString = querystring.parse(event.Records[0].cf.request.querystring);
const jwtToken = queryString.token;
if (cfrequest.uri.match(/\.m3u8?$/mi)) {
const s3Response = await (new Promise((resolve, reject) => {
S3.getObject({
Bucket: 'bucket',
Key: cfrequest.uri.substring(1)
}, (err, data) => {
if (err) {
reject(err)
} else {
resolve(data);
}
});
}));
const manifestFile = s3Response.Body.toString('utf8');
const newManifest = manifestFile.replace(/^((\S+)\.(m3u8|ts|vtt))$/gmi, (_, url) => `${url}?token=${jwtToken}`);
const base64NewManifest = Buffer.from(newManifest, 'utf8').toString('base64');
const tokenizedCfRequest = {
...cfrequest,
body: {
action: 'replace',
data: base64NewManifest,
encoding: 'base64'
}
};
return tokenizedCfRequest;
}
return cfrequest;
}
If you want to generate your own response you need to use a viewer request or origin request event and return a response like this:
exports.handler = async (event) => {
const cfRequest = event.Records[0].cf.request;
const queryString = querystring.parse(event.Records[0].cf.request.querystring);
const jwtToken = queryString.token;
if (cfrequest.uri.match(/\.m3u8?$/mi)) {
// ... your code here ...
const response = {
status: 200, // only mandatory field
body: base64NewManifest,
bodyEncoding: 'base64',
};
return response;
}
// Return original request if no uri match
return cfRequest;
}
See also Generating HTTP Responses in Request Triggers.

Cloud Function (trigger by HTTP) that would publish a message to PubSub

I am trying to create HTTP API in Cloud Function - that eventually published a message t PubSub. Understood, that there is PubSub REST API - but it enforced me to set up the authentication (in client side) - that I would like to skip and move it to the server side.
Below code is deployed as Google Cloud Function with this command gcloud functions deploy helloGET --runtime nodejs8 --trigger-http
But while tested in browser, it is errored out Error: could not handle the request
Any suggestion is appreciated, thanks!
"use strict";
// [START functions_pubsub_setup]
const { PubSub } = require("#google-cloud/pubsub");
// Instantiates a client
const pubsub = new PubSub();
// [END functions_pubsub_setup]
const Buffer = require("safe-buffer").Buffer;
exports.helloGET = (req, res) => {
const topic = pubsub.topic("projects/myproject/topics/openit");
const message = {
data: {
message: "req.body.message"
}
};
// Publishes a message
res.send(
topic
.publish(message)
.then(() => res.status(200).send("Message published."))
.catch(err => {
err = `Catch block ... ${err}`;
console.error(err);
res.status(500).send(err);
return Promise.reject(err);
})
);
};
Below code will work. But it will take around 30 seconds or plus for the subscriber to receive the event - it is way too slow for my used case :S
"use strict";
const { PubSub } = require("#google-cloud/pubsub");
const pubsub = new PubSub();
const Buffer = require("safe-buffer").Buffer;
exports.helloGET = async (req, res) => {
var toPublish = `hello ${Date.now()}`;
publishMessage("_REPLACE_WITH_TOPIC_NAME_", toPublish);
res.send(`Published ${toPublish}`);
};
async function publishMessage(topicName, data) {
console.log("=> publishMessage, data = ", data);
const dataBuffer = Buffer.from(data);
const topic = pubsub.topic(topicName);
const publisher = topic.publisher();
publisher.publish(dataBuffer, { a: "XYZ" }, function() {
console.log("Published eventually ...");
});
}