UNPKG

@nestjs-mod/webhook

Version:

Webhook module with an error filter, guard, controller, database migrations and rest-sdk for work with module from other nodejs appliaction

190 lines 8.19 kB
"use strict"; var WebhookService_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebhookService = void 0; const tslib_1 = require("tslib"); const prisma_1 = require("@nestjs-mod/prisma"); const axios_1 = require("@nestjs/axios"); const common_1 = require("@nestjs/common"); const axios_2 = require("axios"); const rxjs_1 = require("rxjs"); const prisma_client_1 = require("../generated/prisma-client"); const webhook_configuration_1 = require("../webhook.configuration"); const webhook_constants_1 = require("../webhook.constants"); const webhook_decorators_1 = require("../webhook.decorators"); const webhook_errors_1 = require("../webhook.errors"); let WebhookService = WebhookService_1 = class WebhookService { constructor(prismaClient, webhookFeatureConfigurations, webhookConfiguration, httpService) { this.prismaClient = prismaClient; this.webhookFeatureConfigurations = webhookFeatureConfigurations; this.webhookConfiguration = webhookConfiguration; this.httpService = httpService; this.logger = new common_1.Logger(WebhookService_1.name); this.eventsStream$ = new rxjs_1.Subject(); } async sendEvent({ eventName, eventBody, eventHeaders, }) { if (this.webhookConfiguration.syncMode) { await this.sendSyncEvent({ eventName, eventBody, eventHeaders, }); } else { await this.sendAsyncEvent({ eventName, eventBody, eventHeaders, }); } } async sendAsyncEvent({ eventName, eventBody, eventHeaders, }) { const event = this.getAllEvents().find((e) => e.eventName === eventName); if (!event) { throw new webhook_errors_1.WebhookError(webhook_errors_1.WebhookErrorEnum.EVENT_NOT_FOUND); } this.eventsStream$.next({ eventName, eventBody, eventHeaders }); } getAllEvents() { return [ ...(this.webhookConfiguration.events || []), ...this.webhookFeatureConfigurations.map(({ featureConfiguration }) => featureConfiguration.events).flat(), ]; } async sendSyncEvent({ eventName, eventBody, eventHeaders, }) { this.logger.debug({ eventName, eventBody, eventHeaders }); const [{ now }] = await this.getCurrentDatabaseDate(); const webhooks = await this.prismaClient.webhook.findMany({ where: { eventName: { contains: eventName }, enabled: true }, }); for (const webhook of webhooks) { if (!webhook.workUntilDate || (now && now <= webhook.workUntilDate)) { const headers = { // eslint-disable-next-line @typescript-eslint/no-explicit-any ...(webhook.headers || {}), ...(eventHeaders || {}), }; const webhookLog = await this.prismaClient.webhookLog.create({ select: { id: true }, data: { externalTenantId: webhook.externalTenantId, request: { url: webhook.endpoint, body: eventBody, headers, }, responseStatus: '', webhookStatus: 'Pending', response: {}, webhookId: webhook.id, }, }); await this.prismaClient.webhookLog.update({ where: { id: webhookLog.id }, data: { webhookStatus: 'Process', createdAt: new Date(), updatedAt: new Date(), }, }); try { const { response, responseStatus, webhookStatus } = await this.httpRequest({ endpoint: webhook.endpoint, eventBody, headers, requestTimeout: webhook.requestTimeout || 5000, }); await this.prismaClient.webhookLog.update({ where: { id: webhookLog.id }, data: { responseStatus, response, webhookStatus, updatedAt: new Date(), }, }); // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err) { this.logger.debug({ where: { id: webhookLog.id }, data: { webhookStatus: err instanceof rxjs_1.TimeoutError ? 'Timeout' : 'Error', updatedAt: new Date(), }, }); this.logger.error(err, err.stack); } } } } async getCurrentDatabaseDate() { try { // return [{ now: new Date() }]; // todo: https://github.com/prisma/prisma/issues/27257, https://github.com/prisma/prisma/issues/27263 return await this.prismaClient.$queryRaw `SELECT NOW() as now;`; } catch (error) { console.log({ ...error }); throw error; } } async httpRequest({ endpoint, eventBody, headers, requestTimeout, }) { let webhookStatus = prisma_client_1.WebhookStatus.Process; // eslint-disable-next-line @typescript-eslint/no-explicit-any let response, responseStatus; try { const request = await (0, rxjs_1.firstValueFrom)(this.httpService .post(endpoint, eventBody, { ...(Object.keys(headers || {}) ? { headers: new axios_2.AxiosHeaders({ ...headers }) } : {}), }) .pipe((0, rxjs_1.timeout)(requestTimeout))); try { response = request.data; responseStatus = `${request.status} ${request.statusText}`; webhookStatus = prisma_client_1.WebhookStatus.Success; // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err) { this.logger.error(err, err.stack); response = String(err.message); responseStatus = 'unhandled'; webhookStatus = prisma_client_1.WebhookStatus.Error; } // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err) { this.logger.error(err, err.stack); try { response = err.response?.data || String(err.message); responseStatus = err.response?.statusText; // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (err2) { this.logger.error(err2, err2.stack); response = String(err2.message); responseStatus = 'unhandled'; } webhookStatus = err instanceof rxjs_1.TimeoutError ? prisma_client_1.WebhookStatus.Timeout : prisma_client_1.WebhookStatus.Error; } return { response, responseStatus, webhookStatus, request: { url: endpoint, body: eventBody, headers, }, }; } }; exports.WebhookService = WebhookService; exports.WebhookService = WebhookService = WebhookService_1 = tslib_1.__decorate([ (0, common_1.Injectable)(), tslib_1.__param(0, (0, prisma_1.InjectPrismaClient)(webhook_constants_1.WEBHOOK_FEATURE)), tslib_1.__param(1, (0, webhook_decorators_1.InjectWebhookFeatures)()), tslib_1.__metadata("design:paramtypes", [prisma_client_1.PrismaClient, Array, webhook_configuration_1.WebhookConfiguration, axios_1.HttpService]) ], WebhookService); //# sourceMappingURL=webhook.service.js.map