whatsapp-business-serverless
Version:
Connector for the WhatsApp Business APIs with TypeScript support. Serverless version.
231 lines (230 loc) • 9.04 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.WABAClient = void 0;
const errorHandler_1 = require("./utils/errorHandler");
const restClient_1 = require("./utils/restClient");
/**
* Connector for the Whatsapp Cloud API.
*
* documentation: https://developers.facebook.com/docs/whatsapp/cloud-api/guides
*/
class WABAClient {
constructor({ apiToken, phoneId, accountId }) {
this.phoneId = phoneId;
this.accountId = accountId;
this.restClient = (0, restClient_1.createRestClient)({
apiToken,
baseURL: "https://graph.facebook.com/v19.0",
errorHandler: (error) => (0, errorHandler_1.WABAErrorHandler)(error?.response?.data || error),
});
}
/*
*
*BUSINESS PROFILE ENDPOINTS (https://developers.facebook.com/docs/whatsapp/cloud-api/reference/business-profiles)
*/
/**
*
* Retrieves your business profile. Customers can view your business profile by clicking your business's name or number in a conversation thread.
*
* @param fields you can specify which data you want to get from your business. If not passed, defaults to all fields.
*/
getBusinessProfile(fields) {
return this.restClient.get(`${this.phoneId}/whatsapp_business_profile`, {
fields: fields?.join(",") ||
"about,address,description,email,profile_picture_url,websites,vertical",
});
}
/**
* @param payload provide the fields that you wish to update.
*/
updateBusinessProfile(payload) {
return this.restClient.post(`${this.phoneId}/whatsapp_business_profile`, {
...payload,
messaging_product: "whatsapp",
});
}
/*
*
* MEDIA ENDPOINTS (https://developers.facebook.com/docs/whatsapp/cloud-api/reference/media)
*
*/
/**
* All media files sent through this endpoint are encrypted and persist for 30 days, unless they are deleted earlier.
*
* A successful response returns an object with the uploaded media's ID.
*
* @param file - File object (File API) or Blob for Cloudflare Workers compatibility
* @param type - Media type (image, video, audio, document)
*/
uploadMedia({ file, type }) {
const formData = new FormData();
formData.append("type", type);
formData.append("file", file);
formData.append("messaging_product", "whatsapp");
return this.restClient.post(`${this.phoneId}/media`, formData);
}
/**
* Upload media from a file path (Node.js environments only)
* For Cloudflare Workers, use uploadMedia with File/Blob instead
*
* @deprecated Use uploadMedia with File/Blob for better compatibility
*/
uploadMediaFromPath({ file, type }) {
throw new Error("uploadMediaFromPath is not supported in Cloudflare Workers. Use uploadMedia with File/Blob instead.");
}
/**
* Retrieves your media’s URL. Use the returned URL to download the media file. Note that clicking this URL (i.e. performing a generic GET) will not return the media; you must include an access token.
*
* A successful response includes an object with a media url. The URL is only valid for 5 minutes.
*/
getMedia(mediaId) {
return this.restClient.get(mediaId);
}
deleteMedia(mediaId) {
return this.restClient.delete(mediaId);
}
/**
* Download media and return as ArrayBuffer (Cloudflare Workers compatible)
* @param mediaUrl your media's URL
* @returns Promise<ArrayBuffer> - The media file as ArrayBuffer
*/
async downloadMedia(mediaUrl) {
try {
const response = await this.restClient.get(mediaUrl, {}, { baseURL: "", responseType: "stream" });
// If response is already an ArrayBuffer, return it
if (response instanceof ArrayBuffer) {
return response;
}
// If response is a ReadableStream (from fetch), convert to ArrayBuffer
if (response && typeof response.getReader === 'function') {
const reader = response.getReader();
const chunks = [];
let done = false;
while (!done) {
const { value, done: readerDone } = await reader.read();
done = readerDone;
if (value) {
chunks.push(value);
}
}
// Combine chunks into single ArrayBuffer
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
const result = new Uint8Array(totalLength);
let offset = 0;
for (const chunk of chunks) {
result.set(chunk, offset);
offset += chunk.length;
}
return result.buffer;
}
// Fallback: try to convert response to ArrayBuffer
if (typeof response === 'string') {
const encoder = new TextEncoder();
return encoder.encode(response).buffer;
}
throw new Error('Unexpected response type for media download');
}
catch (err) {
return Promise.reject(err);
}
}
/**
* Download media to file path (Node.js environments only)
* For Cloudflare Workers, use downloadMedia() to get ArrayBuffer instead
*
* @deprecated Use downloadMedia() for better compatibility
*/
async downloadMediaToPath(mediaUrl, pathToSaveFile) {
throw new Error("downloadMediaToPath is not supported in Cloudflare Workers. Use downloadMedia() to get ArrayBuffer instead.");
}
/*
*
* MESSAGES ENDPOINTS (https://developers.facebook.com/docs/whatsapp/cloud-api/reference/messages)
*
*/
/**
* Yu can use the API to send the following free-form messages types:
* Text
* Reaction
* Media
* Location
* Contacts
* Interactive
* Address
* messages
* template
*
* For more information refer here: https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-messages
*
* If you are working with template messages refer here: https://developers.facebook.com/docs/whatsapp/cloud-api/guides/send-message-templates
*
*/
async sendMessage(payload) {
return this.restClient.post(`${this.phoneId}/messages`, {
...payload,
messaging_product: "whatsapp",
});
}
/**
* When you receive an incoming message from Webhooks,
* you can use the /messages endpoint to mark the message as
* read by changing its status to read. Messages marked as read display two blue check marks alongside their timestamp.
*/
async markMessageAsRead(message_id) {
return this.restClient.post(`${this.phoneId}/messages`, {
messaging_product: "whatsapp",
status: "read",
message_id,
});
}
/*
*
* PHONE NUMBERS ENDPOINTS (https://developers.facebook.com/docs/whatsapp/cloud-api/reference/phone-numbers)
*
*/
async getBusinessPhoneNumbers() {
return this.restClient.get(`${this.accountId}/phone_numbers`);
}
async getSingleBusinessPhoneNumber(phoneNumberId) {
return this.restClient.get(phoneNumberId);
}
/**
* You may want us to verify a customer's identity before we deliver your message to them.
* You can have us do this by enabling the identity change check setting on your business phone number.
*/
async updateIdentityCheckState({ enable_identity_key_check }) {
return this.restClient.post(`${this.phoneId}/settings`, {
user_identity_change: {
enable_identity_key_check,
},
});
}
async requestPhoneNumberVerificationCode({ phoneNumberId, ...payload }) {
return this.restClient.post(`${phoneNumberId}/request_code`, payload);
}
async verifyPhoneNumberCode({ phoneNumberId, ...payload }) {
return this.restClient.post(`/${phoneNumberId}/verify_code`, payload);
}
async registerPhone({ phoneNumberId, ...payload }) {
return this.restClient.post(`${phoneNumberId}/register`, { messaging_product: "whatsapp", ...payload });
}
async deregisterPhone(phoneNumber) {
return this.restClient.post(`${phoneNumber}/deregister`);
}
async setupTwoStepAuth({ phoneNumberId, ...payload }) {
return this.restClient.post(phoneNumberId, payload);
}
/*
*
* HEALTH ENDPOINTS (https://developers.facebook.com/docs/whatsapp/cloud-api/health-status)
*
*/
/**
*
* @param nodeId is optional, defaults to the account_id
*/
async getHealthStatus(nodeId) {
return this.restClient.get(`${this.accountId}?fields=health_status`);
}
}
exports.WABAClient = WABAClient;