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.
181 lines (179 loc) • 5.97 kB
JavaScript
const __dirname = import.meta.dirname;
import { envVariableNames, init_envVariableNames } from "../src/common/envVariableNames.mjs";
import { getConnection } from "../listener/iot-connection.mjs";
import { getTopic } from "../listener/topic.mjs";
import v4_default from "../node_modules/uuid/dist/esm-node/v4.mjs";
import { unmarshall } from "@aws-sdk/util-dynamodb";
//#region common/SpyEventSender.ts
init_envVariableNames();
var SpyEventSender = class {
constructor(params) {
this.debugMode = process.env[envVariableNames.SSPY_DEBUG] === "true";
if (params.log) this.log = params.log;
if (params.logError) this.logError = params.logError;
this.scope = params.scope;
this.iotEndpoint = params.iotEndpoint;
}
async close() {
this.connection?.end();
}
async connect() {
this.connection = await getConnection(this.debugMode, this.iotEndpoint);
}
async publishSpyEvent(event) {
this.log("Event", JSON.stringify(event));
const mapping = JSON.parse(process.env[envVariableNames.SSPY_INFRA_MAPPING]);
this.log("ARN to names mapping", JSON.stringify(mapping));
const postDataPromises = [];
if (event?.Records && event.Records[0]?.Sns) {
const eventSns = event;
for (const record of eventSns.Records) {
const subscriptionArn = record.EventSubscriptionArn;
let serviceKey;
if (mapping[subscriptionArn]) serviceKey = mapping[subscriptionArn];
else serviceKey = mapping[record.Sns.TopicArn];
let message;
try {
message = JSON.parse(record.Sns.Message);
} catch {
message = record.Sns.Message;
}
const fluentEvent = {
data: {
spyEventType: this.getSpyEventType(serviceKey),
message,
subject: record.Sns.Subject,
timestamp: record.Sns.Timestamp,
topicArn: record.Sns.TopicArn,
messageId: record.Sns.MessageId,
messageAttributes: record.Sns.MessageAttributes
},
serviceKey
};
postDataPromises.push(this.postData(fluentEvent));
}
} else if (event?.Records && event.Records[0]?.eventSource === "aws:sqs") {
const eventSqs = event;
for (const record of eventSqs.Records) {
const serviceKey = mapping[record.eventSourceARN];
let body;
try {
body = JSON.parse(record.body);
} catch {
body = record.body;
}
const fluentEvent = {
data: {
spyEventType: "Sqs",
body,
messageAttributes: record.messageAttributes
},
serviceKey
};
postDataPromises.push(this.postData(fluentEvent));
}
} else if (event?.Records && event.Records[0]?.s3) {
const eventS3 = event;
for (const record of eventS3.Records) {
const bucketArn = record.s3.bucket.arn;
const fluentEvent = {
data: {
spyEventType: "S3",
eventName: record.eventName,
eventTime: record.eventTime,
bucket: record.s3.bucket.name,
key: record.s3.object.key
},
serviceKey: mapping[bucketArn]
};
postDataPromises.push(this.postData(fluentEvent));
}
} else if (event.Records && event.Records[0]?.dynamodb) {
const eventDynamoDB = event;
for (const record of eventDynamoDB.Records) {
let arn = record.eventSourceARN;
arn = arn.substring(0, arn.indexOf("/stream/"));
const fluentEvent = {
data: {
spyEventType: "DynamoDB",
eventName: record.eventName,
newImage: record.dynamodb?.NewImage ? unmarshall(record.dynamodb?.NewImage) : void 0,
keys: unmarshall(record.dynamodb?.Keys),
oldImage: record.dynamodb?.OldImage ? unmarshall(record.dynamodb?.OldImage) : void 0
},
serviceKey: mapping[arn]
};
postDataPromises.push(this.postData(fluentEvent));
}
} else if (event.detail && event["detail-type"] && event.version && event.source) {
const eventEb = event;
const serviceKey = mapping.eventBridge;
const fluentEvent = {
data: {
spyEventType: this.getSpyEventType(serviceKey),
detail: eventEb.detail,
detailType: eventEb["detail-type"],
eventBridgeId: eventEb["id"],
source: eventEb.source,
time: eventEb.time,
account: eventEb.account
},
serviceKey
};
postDataPromises.push(this.postData(fluentEvent));
} else {
const fluentEvent = event;
postDataPromises.push(this.postData(fluentEvent));
}
await Promise.all(postDataPromises);
}
encode(input) {
const parts = JSON.stringify(input).match(/.{1,50000}/g);
if (!parts) return [];
this.log(`Encoded iot message, ${parts.length}`);
const id = v4_default();
return parts.map((part, index) => ({
id,
index,
count: parts.length,
data: part
}));
}
async postData(spyMessage) {
if (this.connection === void 0) throw new Error("No IoT connection created yet, did you forget to call connect()?");
const withTimeStamp = {
...spyMessage,
timestamp: spyMessage.timestamp || (/* @__PURE__ */ new Date()).toISOString()
};
this.log("Post spy message", JSON.stringify(withTimeStamp));
const connection = this.connection;
const topic = getTopic(this.scope);
try {
for (const fragment of this.encode(withTimeStamp)) {
await new Promise((resolve) => {
connection.publish(topic, JSON.stringify(fragment), { qos: 1 }, () => {
this.log("Publishing finished");
resolve();
});
});
this.log(`Published fragment ${fragment.index} out of ${fragment.count} to topic ${topic}`);
}
} catch (e) {
this.logError(`Failed to send payload to iot: ${e}`);
}
this.log("Send spy message finish");
}
getSpyEventType(serviceKey) {
if (!serviceKey) throw new Error("Missing serviceKey");
return serviceKey.substring(0, serviceKey.indexOf("#"));
}
log(message, ...optionalParams) {
if (this.debugMode) console.debug("SSPY EXTENSION", message, ...optionalParams);
}
logError(message, ...optionalParams) {
if (this.debugMode) console.error("SSPY EXTENSION", message, ...optionalParams);
}
};
//#endregion
export { SpyEventSender };
//# sourceMappingURL=SpyEventSender.mjs.map