UNPKG

@digicms/cms

Version:

An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MySQL, MariaDB, PostgreSQL, SQLite

157 lines (131 loc) 3.9 kB
/** * The event hub is Strapi's event control center. */ 'use strict'; const debug = require('debug')('strapi:webhook'); const _ = require('lodash'); const fetch = require('node-fetch'); const WorkerQueue = require('./worker-queue'); const defaultConfiguration = { defaultHeaders: {}, }; class WebhookRunner { constructor({ eventHub, logger, configuration = {} }) { debug('Initialized webhook runer'); this.eventHub = eventHub; this.logger = logger; this.webhooksMap = new Map(); this.listeners = new Map(); if (typeof configuration !== 'object') { throw new Error( 'Invalid configuration provided to the webhookRunner.\nCheck your server.json -> webhooks configuration' ); } this.config = _.merge(defaultConfiguration, configuration); this.queue = new WorkerQueue({ logger, concurrency: 5 }); this.queue.subscribe(this.executeListener.bind(this)); } deleteListener(event) { debug(`Deleting listener for event '${event}'`); if (this.listeners.has(event)) { const fn = this.listeners.get(event); this.eventHub.off(event, fn); this.listeners.delete(event); } } createListener(event) { debug(`Creating listener for event '${event}'`); if (this.listeners.has(event)) { this.logger.error( `The webhook runner is already listening for the event '${event}'. Did you mean to call .register() ?` ); } const listen = (info) => { this.queue.enqueue({ event, info }); }; this.listeners.set(event, listen); this.eventHub.on(event, listen); } async executeListener({ event, info }) { debug(`Executing webhook for event '${event}'`); const webhooks = this.webhooksMap.get(event) || []; const activeWebhooks = webhooks.filter((webhook) => webhook.isEnabled === true); for (const webhook of activeWebhooks) { await this.run(webhook, event, info).catch((error) => { this.logger.error('Error running webhook'); this.logger.error(error); }); } } run(webhook, event, info = {}) { const { url, headers } = webhook; return fetch(url, { method: 'post', body: JSON.stringify({ event, createdAt: new Date(), ...info, }), headers: { ...this.config.defaultHeaders, ...headers, 'X-Strapi-Event': event, 'Content-Type': 'application/json', }, timeout: 10000, }) .then(async (res) => { if (res.ok) { return { statusCode: res.status, }; } return { statusCode: res.status, message: await res.text(), }; }) .catch((err) => { return { statusCode: 500, message: err.message, }; }); } add(webhook) { debug(`Registering webhook '${webhook.id}'`); const { events } = webhook; events.forEach((event) => { if (this.webhooksMap.has(event)) { this.webhooksMap.get(event).push(webhook); } else { this.webhooksMap.set(event, [webhook]); this.createListener(event); } }); } update(webhook) { debug(`Refreshing webhook '${webhook.id}'`); this.remove(webhook); this.add(webhook); } remove(webhook) { debug(`Unregistering webhook '${webhook.id}'`); this.webhooksMap.forEach((webhooks, event) => { const filteredWebhooks = webhooks.filter((value) => value.id !== webhook.id); // Cleanup hanging listeners if (filteredWebhooks.length === 0) { this.webhooksMap.delete(event); this.deleteListener(event); } else { this.webhooksMap.set(event, filteredWebhooks); } }); } } /** * Expose a factory function instead of the class */ module.exports = function createWebhookRunner(opts) { return new WebhookRunner(opts); };