whatsapp-business-api
Version:
A Wrapper for Whatsapp Business Cloud API hosted by Meta.
202 lines (201 loc) • 8.44 kB
JavaScript
;
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;