@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
JavaScript
;
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