@message-queue-toolkit/sns
Version:
SNS adapter for message-queue-toolkit
180 lines • 7.02 kB
JavaScript
import { ListTagsForResourceCommand, TagResourceCommand, paginateListTopics, } from '@aws-sdk/client-sns';
import { CreateTopicCommand, DeleteTopicCommand, GetSubscriptionAttributesCommand, GetTopicAttributesCommand, ListSubscriptionsByTopicCommand, SetTopicAttributesCommand, UnsubscribeCommand, } from '@aws-sdk/client-sns';
import { InternalError, isError } from '@lokalise/node-core';
import { calculateOutgoingMessageSize as sqsCalculateOutgoingMessageSize } from '@message-queue-toolkit/sqs';
import { generateTopicSubscriptionPolicy } from "./snsAttributeUtils.js";
import { buildTopicArn } from "./stsUtils.js";
export async function getTopicAttributes(snsClient, topicArn) {
const command = new GetTopicAttributesCommand({
TopicArn: topicArn,
});
try {
const response = await snsClient.send(command);
return {
result: {
attributes: response.Attributes,
},
};
}
catch (err) {
// @ts-ignore
if (err.Code === 'AWS.SimpleQueueService.NonExistentQueue') {
return {
// @ts-ignore
error: 'not_found',
};
}
throw err;
}
}
export async function getSubscriptionAttributes(snsClient, subscriptionArn) {
const command = new GetSubscriptionAttributesCommand({
SubscriptionArn: subscriptionArn,
});
try {
const response = await snsClient.send(command);
return {
result: {
attributes: response.Attributes,
},
};
}
catch (err) {
// @ts-ignore
if (err.Code === 'AWS.SimpleQueueService.NonExistentQueue') {
return {
// @ts-ignore
error: 'not_found',
};
}
throw err;
}
}
export async function assertTopic(snsClient, stsClient, topicOptions, extraParams) {
let topicArn;
try {
const command = new CreateTopicCommand(topicOptions);
const response = await snsClient.send(command);
if (!response.TopicArn)
throw new Error('No topic arn in response');
topicArn = response.TopicArn;
}
catch (error) {
if (!isError(error))
throw error;
// To build ARN we need topic name and error should be "topic already exist with different tags"
if (!topicOptions.Name || !isTopicAlreadyExistWithDifferentTagsError(error)) {
throw new InternalError({
message: `${topicOptions.Name} - ${error.message}`,
cause: error,
details: { topicName: topicOptions.Name },
errorCode: 'SNS_CREATE_TOPIC_COMMAND_UNEXPECTED_ERROR',
});
}
topicArn = await buildTopicArn(stsClient, topicOptions.Name);
if (!extraParams?.forceTagUpdate) {
const currentTags = await snsClient.send(new ListTagsForResourceCommand({ ResourceArn: topicArn }));
throw new InternalError({
message: `${topicOptions.Name} - ${error.message}`,
details: {
topicName: topicOptions.Name,
currentTags: JSON.stringify(currentTags),
newTags: JSON.stringify(topicOptions.Tags),
},
errorCode: 'SNS_TOPIC_ALREADY_EXISTS_WITH_DIFFERENT_TAGS',
cause: error,
});
}
}
if (extraParams?.queueUrlsWithSubscribePermissionsPrefix || extraParams?.allowedSourceOwner) {
const setTopicAttributesCommand = new SetTopicAttributesCommand({
TopicArn: topicArn,
AttributeName: 'Policy',
AttributeValue: generateTopicSubscriptionPolicy({
topicArn,
allowedSqsQueueUrlPrefix: extraParams.queueUrlsWithSubscribePermissionsPrefix,
allowedSourceOwner: extraParams.allowedSourceOwner,
}),
});
await snsClient.send(setTopicAttributesCommand);
}
if (extraParams?.forceTagUpdate && topicOptions.Tags) {
const tagTopicCommand = new TagResourceCommand({
ResourceArn: topicArn,
Tags: topicOptions.Tags,
});
await snsClient.send(tagTopicCommand);
}
return topicArn;
}
export async function deleteTopic(snsClient, stsClient, topicName) {
try {
const topicArn = await assertTopic(snsClient, stsClient, {
Name: topicName,
});
await snsClient.send(new DeleteTopicCommand({
TopicArn: topicArn,
}));
}
catch (_) {
// we don't care it operation has failed
}
}
export async function deleteSubscription(client, subscriptionArn) {
const command = new UnsubscribeCommand({
SubscriptionArn: subscriptionArn,
});
try {
await client.send(command);
}
catch (_) {
// we don't care it operation has failed
}
}
export async function findSubscriptionByTopicAndQueue(snsClient, topicArn, queueArn) {
const listSubscriptionsCommand = new ListSubscriptionsByTopicCommand({
TopicArn: topicArn,
});
const listSubscriptionResult = await snsClient.send(listSubscriptionsCommand);
return listSubscriptionResult.Subscriptions?.find((entry) => {
return entry.Endpoint === queueArn;
});
}
export async function getTopicArnByName(snsClient, topicName) {
if (!topicName) {
throw new Error('topicName is not provided');
}
// Use paginator to automatically handle NextToken
const paginator = paginateListTopics({ client: snsClient }, {});
for await (const page of paginator) {
for (const topic of page.Topics || []) {
if (topic.TopicArn?.includes(topicName)) {
return topic.TopicArn;
}
}
}
throw new Error(`Failed to resolve topic by name ${topicName}`);
}
/**
* Calculates the size of an outgoing SNS message.
*
* SNS imposes a 256 KB limit on the total size of a message, which includes both the message body and any metadata (attributes).
* This function currently computes the size based solely on the message body, as no attributes are included at this time.
* For future updates, if message attributes are added, their sizes should also be considered.
*
* Reference: https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html
*
* A wrapper around the equivalent function in the SQS package.
*/
export const calculateOutgoingMessageSize = (message) => sqsCalculateOutgoingMessageSize(message);
const isTopicAlreadyExistWithDifferentTagsError = (error) => !!error &&
isError(error) &&
'Error' in error &&
!!error.Error &&
typeof error.Error === 'object' &&
'Code' in error.Error &&
'Message' in error.Error &&
typeof error.Error.Message === 'string' &&
error.Error.Code === 'InvalidParameter' &&
error.Error.Message.includes('already exists with different tags');
//# sourceMappingURL=snsUtils.js.map