UNPKG

@brighthustle/adonisjs-whatsapp

Version:

Connect your WhatsApp Cloud API with AdonisJS

175 lines (174 loc) 7.73 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const Helpers_1 = __importDefault(require("../src/Helpers")); const WhatsAppCloudApi_1 = require("../src/WhatsAppCloudApi"); const enum_1 = require("../src/types/enum"); class WhatsAppProvider { constructor(app) { this.app = app; this.config = {}; } register() { this.app.container.singleton('Adonis/Addons/WhatsApp', () => { const drive = this.app.container.resolveBinding('Adonis/Core/Drive'); const emitter = this.app.container.resolveBinding('Adonis/Core/Event'); const database = this.app.container.resolveBinding('Adonis/Lucid/Database'); const config = this.app.container .resolveBinding('Adonis/Core/Config') .get('whatsapp', this.config); return new WhatsAppCloudApi_1.WhatsAppCloudApi(config, drive, emitter, database); }); } async boot() { const Route = this.app.container.use('Adonis/Core/Route'); const Event = this.app.container.use('Adonis/Core/Event'); const Config = this.app.container.use('Adonis/Core/Config'); const Logger = this.app.container.use('Adonis/Core/Logger'); const whatsapp = Config.get('whatsapp', this.config); // webhook verifier Route.get(`${whatsapp.config.webhookRoute}/:phoneNumberId`, async (ctx) => { const payload = ctx.request.qs(); const { phoneNumberId } = ctx.request.params(); let verifyToken = null; if (whatsapp.provider === 'lucid') { verifyToken = await this.getVerifyTokenFromDb(phoneNumberId, whatsapp); } else { verifyToken = whatsapp.config.verifyToken; } if (!verifyToken) { return ctx.response.status(403).send({ code: 403, message: 'verify token missing' }); } if (!this.isValidWebhook(payload, verifyToken)) { return ctx.response.status(403).send({ code: 403 }); } Logger.info('Webhook verified!'); return ctx.response.status(200).send(payload['hub.challenge']); }); // webhook Route.post(`${whatsapp.config.webhookRoute}/:phoneNumberId`, async (ctx) => { const payload = ctx.request.body(); const { phoneNumberId } = ctx.request.params(); const config = await this.getConfig(phoneNumberId); if (!config) { return ctx.response.status(404).send({ code: 403, message: 'Invalid phone id' }); } if (config.apiProvider === enum_1.ApiProvider.MAG91_API) { return await this.handleMag91ApiWebhook(ctx, payload, phoneNumberId, Event); } else { return await this.handleCloudApiWebhook(ctx, payload, phoneNumberId, Event); } }); } async getVerifyTokenFromDb(phoneNumberId, whatsapp) { const Database = this.app.container.resolveBinding('Adonis/Lucid/Database'); const connection = Database.connection(whatsapp.db.connectionName); try { const data = await connection .from(whatsapp.db.tableName) .select('verify_token') .where('phone_number_id', phoneNumberId) .first(); return data?.verify_token || null; } catch (error) { return null; } } isValidWebhook(payload, verifyToken) { return payload['hub.mode'] === 'subscribe' && payload['hub.verify_token'] === verifyToken; } async getConfig(phoneNumberId) { const Config = this.app.container.use('Adonis/Core/Config'); const whatsapp = Config.get('whatsapp', this.config); if (whatsapp.provider === 'db') { const Database = this.app.container.resolveBinding('Adonis/Lucid/Database'); const connection = Database.connection(whatsapp.db.connectionName); try { const data = await connection .from(whatsapp.db.tableName) .select('api_provider', 'access_token') .where('phone_number_id', phoneNumberId) .first(); return data || null; } catch (error) { return null; } } return { apiProvider: whatsapp.config.apiProvider, accessToken: whatsapp.config.accessToken, }; } async handleMag91ApiWebhook(ctx, payload, phoneNumberId, Event) { if (!payload || Number(payload.integrated_number) !== Number(phoneNumberId)) { return ctx.response.status(200).send({ code: 200 }); } if (['unsupported', 'reaction', 'order', 'system'].includes(payload.content_type)) { return ctx.response.status(200).send({ code: 200 }); } const interactive = Helpers_1.default.translateInteractiveMsg91(payload); const type = Helpers_1.default.translateType(payload.content_type); const data = { from: Number(payload.sender), sender: payload.customer_name, wamid: payload.message_uuid, data: interactive?.data || { body: payload[payload.content_type] }, timestamp: Number(payload.received_at), type, phoneNumberId, }; await this.emitEvents(Event, 'message', type, data); return ctx.response.status(200).send({ code: 200 }); } async handleCloudApiWebhook(ctx, payload, phoneNumberId, Event) { if (!payload.object) { return ctx.response.status(403).send({ code: 403 }); } const { value } = payload.entry[0].changes[0]; const message = value.messages?.[0]; const status = value.statuses?.[0]; const contact = value.contacts?.[0]; const metadata = value.metadata; if (Number(metadata.phone_number_id) !== Number(phoneNumberId)) { return ctx.response.status(200).send({ code: 200 }); } if (message && !['unsupported', 'reaction', 'order', 'system'].includes(message.type)) { const interactive = Helpers_1.default.translateInteractive(message); const type = Helpers_1.default.translateType(interactive?.type || message.type); const data = { from: Number(contact.wa_id), sender: contact.profile.name, wamid: message.id, data: interactive?.data || message[message.type], timestamp: Number(message.timestamp), type, phoneNumberId, }; await this.emitEvents(Event, 'message', type, data); } if (status) { const data = { from: Number(status.recipient_id), wamid: status.id, timestamp: Number(status.timestamp), status: status.status, phoneNumberId, }; await this.emitEvents(Event, 'status', status.status, data); } return ctx.response.status(200).send({ code: 200 }); } async emitEvents(Event, eventType, specificType, data) { await Event.emit(`wa:${eventType}:*`, data); await Event.emit(`wa:${eventType}:${specificType}`, data); await Event.emit('wa:*', data); } } WhatsAppProvider.needsApplication = true; exports.default = WhatsAppProvider;