serverless-spy
Version:
CDK-based library for writing elegant integration tests on AWS serverless architecture and an additional web console to monitor events in real time.
1 lines • 13 kB
Source Map (JSON)
{"version":3,"file":"SpyEventSender.mjs","names":["postDataPromises: Promise<any>[]","serviceKey: string","message: string","fluentEvent: Omit<SpyMessage, 'timestamp'>","body: string","v4"],"sources":["../../common/SpyEventSender.ts"],"sourcesContent":["import { unmarshall } from '@aws-sdk/util-dynamodb';\nimport iot from 'aws-iot-device-sdk';\nimport {\n DynamoDBStreamEvent,\n S3Event,\n SNSEvent,\n EventBridgeEvent,\n SQSEvent,\n} from 'aws-lambda';\nimport { v4 } from 'uuid';\nimport { DynamoDBSpyEvent } from './spyEvents/DynamoDBSpyEvent';\nimport { EventBridgeRuleSpyEvent } from './spyEvents/EventBridgeRuleSpyEvent';\nimport { EventBridgeSpyEvent } from './spyEvents/EventBridgeSpyEvent';\nimport { S3SpyEvent } from './spyEvents/S3SpyEvent';\nimport { SnsSubscriptionSpyEvent } from './spyEvents/SnsSubscriptionSpyEvent';\nimport { SnsTopicSpyEvent } from './spyEvents/SnsTopicSpyEvent';\nimport { SpyMessage } from './spyEvents/SpyMessage';\nimport { SqsSpyEvent } from './spyEvents/SqsSpyEvent';\nimport { fragment, getConnection } from '../listener/iot-connection';\nimport { getTopic } from '../listener/topic';\nimport { envVariableNames } from '../src/common/envVariableNames';\n\nexport class SpyEventSender {\n debugMode = process.env[envVariableNames.SSPY_DEBUG] === 'true';\n connection: iot.device | undefined;\n scope: string;\n iotEndpoint: string;\n\n constructor(params: {\n log?: (message: string, ...optionalParams: any[]) => void;\n logError?: (message: string, ...optionalParams: any[]) => void;\n scope: string;\n iotEndpoint: string;\n }) {\n if (params.log) {\n this.log = params.log;\n }\n\n if (params.logError) {\n this.logError = params.logError;\n }\n\n this.scope = params.scope;\n this.iotEndpoint = params.iotEndpoint;\n }\n\n public async close() {\n this.connection?.end();\n }\n\n public async connect() {\n this.connection = await getConnection(this.debugMode, this.iotEndpoint);\n }\n\n public async publishSpyEvent(event: any) {\n this.log('Event', JSON.stringify(event));\n\n const mapping = JSON.parse(\n process.env[envVariableNames.SSPY_INFRA_MAPPING]!\n );\n this.log('ARN to names mapping', JSON.stringify(mapping));\n\n const postDataPromises: Promise<any>[] = [];\n\n if (event?.Records && event.Records[0]?.Sns) {\n //console.log('*** SNS ***');\n const eventSns = event as SNSEvent;\n for (const record of eventSns.Records) {\n const subscriptionArn = record.EventSubscriptionArn;\n\n let serviceKey: string;\n if (mapping[subscriptionArn]) {\n // subscription event that could contain filter based on existing subscription\n serviceKey = mapping[subscriptionArn];\n } else {\n // catch all subscription\n const topicArn = record.Sns.TopicArn;\n serviceKey = mapping[topicArn];\n }\n\n let message: string;\n\n try {\n message = JSON.parse(record.Sns.Message);\n } catch {\n message = record.Sns.Message;\n }\n\n const spyEventType = this.getSpyEventType(serviceKey) as\n | 'FunctionSnsTopic'\n | 'FunctionSnsSubscription';\n\n const data: SnsTopicSpyEvent | SnsSubscriptionSpyEvent = {\n spyEventType,\n message,\n subject: record.Sns.Subject,\n timestamp: record.Sns.Timestamp,\n topicArn: record.Sns.TopicArn,\n messageId: record.Sns.MessageId,\n messageAttributes: record.Sns.MessageAttributes,\n };\n\n const fluentEvent: Omit<SpyMessage, 'timestamp'> = {\n data,\n serviceKey,\n };\n postDataPromises.push(this.postData(fluentEvent));\n }\n } else if (event?.Records && event.Records[0]?.eventSource === 'aws:sqs') {\n //console.log('*** SQS ***');\n const eventSqs = event as SQSEvent;\n for (const record of eventSqs.Records) {\n const subscriptionArn = record.eventSourceARN;\n\n const serviceKey = mapping[subscriptionArn];\n let body: string;\n\n try {\n body = JSON.parse(record.body);\n } catch {\n body = record.body;\n }\n\n const data: SqsSpyEvent = {\n spyEventType: 'Sqs',\n body,\n messageAttributes: record.messageAttributes,\n };\n\n const fluentEvent: Omit<SpyMessage, 'timestamp'> = {\n data,\n serviceKey,\n };\n postDataPromises.push(this.postData(fluentEvent));\n }\n } else if (event?.Records && event.Records[0]?.s3) {\n //console.log('*** S3 ***');\n const eventS3 = event as S3Event;\n for (const record of eventS3.Records) {\n const bucketArn = record.s3.bucket.arn;\n\n const data: S3SpyEvent = {\n spyEventType: 'S3',\n eventName: record.eventName,\n eventTime: record.eventTime,\n bucket: record.s3.bucket.name,\n key: record.s3.object.key,\n };\n\n const fluentEvent: Omit<SpyMessage, 'timestamp'> = {\n data,\n serviceKey: mapping[bucketArn],\n };\n postDataPromises.push(this.postData(fluentEvent));\n }\n } else if (event.Records && event.Records[0]?.dynamodb) {\n //console.log('*** DYNAMODB ***');\n const eventDynamoDB = event as DynamoDBStreamEvent;\n for (const record of eventDynamoDB.Records) {\n let arn = record.eventSourceARN!;\n arn = arn.substring(0, arn.indexOf('/stream/'));\n\n const data: DynamoDBSpyEvent = {\n spyEventType: 'DynamoDB',\n eventName: record.eventName,\n newImage: record.dynamodb?.NewImage\n ? unmarshall(record.dynamodb?.NewImage as any)\n : undefined,\n keys: unmarshall(record.dynamodb?.Keys as any),\n oldImage: record.dynamodb?.OldImage\n ? unmarshall(record.dynamodb?.OldImage as any)\n : undefined,\n };\n\n const fluentEvent: Omit<SpyMessage, 'timestamp'> = {\n data,\n serviceKey: mapping[arn],\n };\n postDataPromises.push(this.postData(fluentEvent));\n }\n } else if (\n event.detail &&\n event['detail-type'] &&\n event.version &&\n event.source\n ) {\n //console.log('*** EventBridge ***');\n const eventEb = event as EventBridgeEvent<any, any>;\n\n const serviceKey = mapping.eventBridge; // the is new lambda for each subscription\n\n const spyEventType = this.getSpyEventType(serviceKey) as\n | 'EventBridge'\n | 'EventBridgeRule';\n\n const message = eventEb.detail;\n\n const data: EventBridgeSpyEvent | EventBridgeRuleSpyEvent = {\n spyEventType,\n detail: message,\n detailType: eventEb['detail-type'],\n eventBridgeId: eventEb['id'],\n source: eventEb.source,\n time: eventEb.time,\n account: eventEb.account,\n };\n\n const fluentEvent: Omit<SpyMessage, 'timestamp'> = {\n data,\n serviceKey,\n };\n postDataPromises.push(this.postData(fluentEvent));\n } else {\n //console.log('*** OTHER ***');\n const fluentEvent: Omit<SpyMessage, 'timestamp'> = event;\n postDataPromises.push(this.postData(fluentEvent));\n }\n\n await Promise.all(postDataPromises);\n }\n\n private encode(input: any): fragment[] {\n const payload = JSON.stringify(input);\n const parts = payload.match(/.{1,50000}/g);\n if (!parts) return [];\n this.log(`Encoded iot message, ${parts.length}`);\n const id = v4();\n return parts.map((part, index) => ({\n id,\n index,\n count: parts.length,\n data: part,\n }));\n }\n\n private async postData(\n spyMessage: Omit<SpyMessage, 'timestamp'> & { timestamp?: string }\n ) {\n if (this.connection === undefined) {\n throw new Error(\n 'No IoT connection created yet, did you forget to call connect()?'\n );\n }\n\n const withTimeStamp = {\n ...spyMessage,\n timestamp: spyMessage.timestamp || new Date().toISOString(),\n };\n\n this.log('Post spy message', JSON.stringify(withTimeStamp));\n\n const connection = this.connection;\n const topic = getTopic(this.scope);\n\n try {\n for (const fragment of this.encode(withTimeStamp)) {\n await new Promise<void>((resolve) => {\n connection.publish(\n topic,\n JSON.stringify(fragment),\n {\n qos: 1,\n },\n () => {\n this.log('Publishing finished');\n resolve();\n }\n );\n });\n this.log(\n `Published fragment ${fragment.index} out of ${fragment.count} to topic ${topic}`\n );\n }\n } catch (e) {\n this.logError(`Failed to send payload to iot: ${e}`);\n }\n\n this.log('Send spy message finish');\n }\n\n private getSpyEventType(serviceKey: string) {\n if (!serviceKey) {\n throw new Error('Missing serviceKey');\n }\n\n return serviceKey.substring(0, serviceKey.indexOf('#'));\n }\n\n private log(message: string, ...optionalParams: any[]) {\n if (this.debugMode) {\n console.debug('SSPY EXTENSION', message, ...optionalParams);\n }\n }\n\n private logError(message: string, ...optionalParams: any[]) {\n if (this.debugMode) {\n console.error('SSPY EXTENSION', message, ...optionalParams);\n }\n }\n}\n"],"mappings":"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"}