{"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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"}