UNPKG

whatsapp-business-api

Version:

A Wrapper for Whatsapp Business Cloud API hosted by Meta.

202 lines (201 loc) 8.44 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const axios_1 = __importStar(require("axios")); const body_parser_1 = __importDefault(require("body-parser")); const crypto_1 = __importDefault(require("crypto")); const express_1 = __importDefault(require("express")); const form_data_1 = __importDefault(require("form-data")); const fs_1 = __importDefault(require("fs")); const mime_types_1 = __importDefault(require("mime-types")); const constants_1 = require("./constants"); const errors_1 = require("./errors"); class WhatsappAPI { constructor(options) { this.accountPhoneNumberId = options.accountPhoneNumberId; this.accessToken = options.accessToken; if (options.webhook) { this.expressApp = options.webhook.expressApp; this.fbAppSecret = options.webhook.fbAppSecret; this.webhookPort = options.webhook.port || 1337; this.webhookVerifyToken = options.webhook.verifyToken; } } initWebhook(callback) { let shouldAppListen = false; if (!this.expressApp) { this.expressApp = (0, express_1.default)(); shouldAppListen = true; } this.webhookRouter = express_1.default.Router().use(body_parser_1.default.json({ verify(req, res, buf) { req.rawBody = buf.toString(); }, })); this.webhookRouter.get('/webhook', (req, res) => { const mode = req.query['hub.mode'], token = req.query['hub.verify_token'], challenge = req.query['hub.challenge']; if (mode && token) { if (mode === 'subscribe' && token === this.webhookVerifyToken) { res.status(200).send(challenge); } else { res.sendStatus(403); } } }); this.webhookRouter.post('/webhook', (req, res, next) => { var _a; const hmac = crypto_1.default.createHmac('sha1', (this === null || this === void 0 ? void 0 : this.fbAppSecret) || 'N/A'); hmac.update(req.rawBody || '', 'ascii'); const expectedSignature = hmac.digest('hex'); if (((_a = req.headers['x-hub-signature']) === null || _a === void 0 ? void 0 : _a.toString()) === `sha1=${expectedSignature}`) { next(); } else { res.status(401).send('Invalid signature'); } }, (req, res) => { let payload = req.body; const messages = this.parseIncomingMessage(payload); messages.map((message) => { if (message.type === constants_1.MessageTypes.TEXT) { callback(message); } }); if (payload) { res.sendStatus(200); } else { res.sendStatus(404); } }); this.expressApp.use('/whatsapp', this.webhookRouter); if (shouldAppListen) { this.expressApp.listen(this.webhookPort, () => console.log(`Whatsapp Webhook is listening on port ${this.webhookPort}`)); } } parseIncomingMessage(payload) { const messages = []; payload.entry.map(entry => { entry.changes.map(change => { if (change.field === 'messages') { change.value.messages.map(message => { messages.push(Object.assign(Object.assign({}, message), { contact: change.value.contacts.filter(contact => contact.wa_id === message.from)[0] })); }); } }); }); return messages; } async sendRequest(url, payload) { var _a, _b, _c, _d; const fullUrl = `${constants_1.GraphAPIBaseUrl}/${url}`; const headers = { Authorization: `Bearer ${this.accessToken}`, }; try { const response = await axios_1.default.post(fullUrl, payload, { headers, }); return response.data; } catch (err) { if (err instanceof axios_1.AxiosError) { if ((_b = (_a = err.response) === null || _a === void 0 ? void 0 : _a.data) === null || _b === void 0 ? void 0 : _b.error) { throw new errors_1.WhatsappApiError((_d = (_c = err.response) === null || _c === void 0 ? void 0 : _c.data) === null || _d === void 0 ? void 0 : _d.error); } } throw err; } } extractMediaType(path) { const mimeType = mime_types_1.default.lookup(path); if (!mimeType || !constants_1.SupportedMediaTypes[mimeType]) { throw new Error(`Unsupported media type: ${mimeType}`); } return [mimeType, constants_1.SupportedMediaTypes[mimeType]]; } async uploadMedia(path) { const [mimeType, messageType] = this.extractMediaType(path); const data = new form_data_1.default(); data.append("file", fs_1.default.createReadStream(path)); data.append("type", mimeType); data.append("messaging_product", "whatsapp"); const mediaResponse = await this.sendRequest(`${this.accountPhoneNumberId}/media`, data); return [mediaResponse.id, messageType]; } async sendTextMessage(to, options) { const payload = { messaging_product: "whatsapp", recipient_type: "individual", to, type: constants_1.MessageTypes.TEXT, text: { body: options.message, preview_url: options.preview_url || true, }, }; return this.sendRequest(`${this.accountPhoneNumberId}/messages`, payload); } async sendMediaMessage(to, options) { if (options.local_path && options.external_link) { throw new Error("You must choose local_path or external_link to send image message!"); } let media; let messageType = constants_1.MessageTypes.IMAGE; // default if (options.local_path) { const [mediaId, uploadedMediaType] = await this.uploadMedia(options.local_path); media = { caption: options.caption, id: mediaId, filename: options.filename, }; messageType = uploadedMediaType; } else if (options.external_link) { const [_, extractedMediaType] = this.extractMediaType(options.external_link); media = { link: options.external_link, caption: options.caption, filename: options.filename, }; messageType = extractedMediaType; } else { throw new Error("Either local_path or external_link parameter must be set to send image message!"); } if (messageType !== constants_1.MessageTypes.DOCUMENT && options.filename) { throw new Error("filename parameter is only applicable for sending Documents!"); } const payload = { messaging_product: "whatsapp", recipient_type: "individual", to, type: messageType, [messageType]: media, }; return this.sendRequest(`${this.accountPhoneNumberId}/messages`, payload); } } exports.default = WhatsappAPI;