btrz-webhooks-emitter
Version:
Betterez simple client to emit events to the webhooks pipeline
150 lines (129 loc) • 4.49 kB
JavaScript
const Queue = require("btrz-sqs").Queue;
const {trace} = require("btrz-monitoring");
const zlib = require("zlib");
const config = require("../config.js")(process.env);
const uuid = require("uuid");
const VALID_COMPRESS = new Set(["zstd", "gzip"]);
function getCompressAlgo() {
const v = (process.env.WEBHOOK_COMPRESS || "").toLowerCase();
return VALID_COMPRESS.has(v) ? v : null;
}
function compressData(data, algo) {
const json = JSON.stringify(data);
const buf = Buffer.from(json, "utf8");
const compressed = algo === "zstd" ?
zlib.zstdCompressSync(buf) :
zlib.gzipSync(buf);
return compressed.toString("base64");
}
function getDeniedFields(eventName) {
const deniedFields = require("btrz-webhooks-denied-fields").getFields();
let result = deniedFields["*"] || [];
const wildcard = `${eventName.split(".")[0]}.*`;
if (deniedFields[wildcard]) {
result = result.concat(deniedFields[wildcard]);
}
if (deniedFields[eventName]) {
result = result.concat(deniedFields[eventName]);
}
return result;
}
const originalStdoutWrite = process.stdout.write;
function emitForTesting(object) {
// We want our test suites to be able to intercept webhook events, however we don't want the webhook events to be logged to the console
// as they tend to be very large and can create a lot of noise when running tests. Only log the webhook events to the console if it looks
// like someone is intercepting the console output (listening to it).
const stdoutIsBeingIntercepted = process.stdout.write !== originalStdoutWrite;
if (process.env.NODE_ENV === "test" && stdoutIsBeingIntercepted) {
console.log("WHE-PRE");
console.log(JSON.stringify(object));
}
}
module.exports = {
async emitEvent(eventName, attrs, logger) {
function logError(err) {
if (logger && logger.error) {
logger.error(`btrz-webhooks-emitter::emitEvent() - ${err.toString()}`);
} else {
console.log(`ERROR - btrz-webhooks-emitter::emitEvent() - ${err.toString()}`);
}
}
function logInfo(msg) {
if (logger && logger.info) {
logger.info(`btrz-webhooks-emitter::emitEvent() - ${msg.toString()}`);
} else {
console.log(`INFO - btrz-webhooks-emitter::emitEvent() - ${msg.toString()}`);
}
}
try {
await trace("btrz-webhooks-emitter emitEvent", async () => {
if (!config.aws.key || !config.aws.secret || !config.aws.sqs.webhooks.queueUrl) {
if (process.env.NODE_ENV === "test") {
emitForTesting({eventName, attrs});
return;
}
throw new Error("invalid aws configuration.");
}
const queue = new Queue(config.aws, "webhooks");
const msg = Queue.createMessage(uuid.v4(), this.buildMessage(eventName, attrs));
await queue.send([msg]);
logInfo(`${eventName} emitted!`);
});
return undefined;
} catch (error) {
logError(error);
return undefined;
}
},
buildMessage(eventName, attrs) {
if (eventName === "") {
throw new Error("event name can not be empty.");
}
if (!eventName) {
throw new Error("event name is missing.");
}
if (typeof eventName !== "string") {
throw new Error("event name must be a string.");
}
if (!attrs.providerId) {
throw new Error("providerId is missing in attrs.");
}
// clone data to avoid mutation
const data = this.filterFields(eventName, Object.assign({}, attrs.data)) || {};
const algo = getCompressAlgo();
const msg = {
id: uuid.v4(),
ts: Date.now(),
providerId: attrs.providerId,
event: eventName,
data: algo ? compressData(data, algo) : data
};
if (algo) {
msg.enc = algo;
}
return msg;
},
filterFields(eventName, data) {
const deniedFields = getDeniedFields(eventName);
function fieldIsAllowed(field) {
return deniedFields.findIndex((denied) => {
return denied.toString().toLowerCase() === field.toString().toLowerCase();
}) === -1;
}
if (Array.isArray(data)) {
return data.filter((elem, field) => {
return fieldIsAllowed(field);
});
}
if (typeof data === "object" && data !== null) {
const result = Object.assign({}, data);
Object.keys(result).forEach((key) => {
if (!fieldIsAllowed(key)) {
delete result[key];
}
});
return result;
}
return data;
}
};