UNPKG

@message-queue-toolkit/sns

Version:
182 lines 7.17 kB
import { CreateTopicCommand, DeleteTopicCommand, GetSubscriptionAttributesCommand, GetTopicAttributesCommand, ListSubscriptionsByTopicCommand, ListTagsForResourceCommand, SetTopicAttributesCommand, TagResourceCommand, 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) { // SNS returns NotFoundException for non-existent topics // @ts-expect-error if (err.name === 'NotFoundException' || err.name === 'NotFound') { return { 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) { // SNS returns NotFoundException for non-existent subscriptions // @ts-expect-error if (err.name === 'NotFoundException' || err.name === 'NotFound') { return { 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; }); } /** * 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); /** * Checks if a topic name indicates a FIFO topic (ends with .fifo) */ export function isFifoTopicName(topicName) { return topicName.endsWith('.fifo'); } /** * Validates that topic name matches the FIFO configuration flag */ export function validateFifoTopicName(topicName, isFifoTopic) { const hasFifoName = isFifoTopicName(topicName); if (isFifoTopic && !hasFifoName) { throw new Error(`FIFO topic names must end with .fifo suffix. Topic name: ${topicName}`); } if (!isFifoTopic && hasFifoName) { throw new Error(`Topic name ends with .fifo but fifoTopic option is not set to true. Topic name: ${topicName}`); } } 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