playbook/antigravity-awesome-skills/skills/whatsapp-cloud-api/assets/boilerplate/nodejs/src/whatsapp-client.ts

194 lines
5.2 KiB
TypeScript

import axios, { AxiosInstance } from 'axios';
import { WhatsAppConfig, SendMessagePayload, SendMessageResponse } from './types';
export class WhatsAppClient {
private client: AxiosInstance;
private phoneNumberId: string;
private wabaId: string;
constructor(config: WhatsAppConfig) {
const version = config.graphApiVersion || 'v21.0';
this.phoneNumberId = config.phoneNumberId;
this.wabaId = config.wabaId;
this.client = axios.create({
baseURL: `https://graph.facebook.com/${version}`,
headers: {
Authorization: `Bearer ${config.token}`,
'Content-Type': 'application/json',
},
});
}
async sendMessage(payload: SendMessagePayload): Promise<SendMessageResponse> {
return this.sendWithRetry(payload);
}
async sendText(to: string, body: string, previewUrl = false): Promise<SendMessageResponse> {
return this.sendMessage({
messaging_product: 'whatsapp',
to,
type: 'text',
text: { body, preview_url: previewUrl },
});
}
async sendTemplate(
to: string,
templateName: string,
languageCode: string,
components?: SendMessagePayload['template']
): Promise<SendMessageResponse> {
return this.sendMessage({
messaging_product: 'whatsapp',
to,
type: 'template',
template: {
name: templateName,
language: { code: languageCode },
...components,
},
});
}
async sendImage(to: string, imageUrl: string, caption?: string): Promise<SendMessageResponse> {
return this.sendMessage({
messaging_product: 'whatsapp',
to,
type: 'image',
image: { link: imageUrl, caption },
});
}
async sendDocument(
to: string,
documentUrl: string,
filename: string,
caption?: string
): Promise<SendMessageResponse> {
return this.sendMessage({
messaging_product: 'whatsapp',
to,
type: 'document',
document: { link: documentUrl, filename, caption },
});
}
async sendInteractiveButtons(
to: string,
bodyText: string,
buttons: Array<{ id: string; title: string }>,
headerText?: string,
footerText?: string
): Promise<SendMessageResponse> {
return this.sendMessage({
messaging_product: 'whatsapp',
to,
type: 'interactive',
interactive: {
type: 'button',
...(headerText && { header: { type: 'text', text: headerText } }),
body: { text: bodyText },
...(footerText && { footer: { text: footerText } }),
action: {
buttons: buttons.map((b) => ({
type: 'reply' as const,
reply: { id: b.id, title: b.title },
})),
},
},
});
}
async sendInteractiveList(
to: string,
bodyText: string,
buttonText: string,
sections: Array<{
title: string;
rows: Array<{ id: string; title: string; description?: string }>;
}>,
headerText?: string,
footerText?: string
): Promise<SendMessageResponse> {
return this.sendMessage({
messaging_product: 'whatsapp',
to,
type: 'interactive',
interactive: {
type: 'list',
...(headerText && { header: { type: 'text', text: headerText } }),
body: { text: bodyText },
...(footerText && { footer: { text: footerText } }),
action: { button: buttonText, sections },
},
});
}
async sendReaction(to: string, messageId: string, emoji: string): Promise<SendMessageResponse> {
return this.sendMessage({
messaging_product: 'whatsapp',
to,
type: 'reaction',
reaction: { message_id: messageId, emoji },
});
}
async sendLocation(
to: string,
latitude: number,
longitude: number,
name?: string,
address?: string
): Promise<SendMessageResponse> {
return this.sendMessage({
messaging_product: 'whatsapp',
to,
type: 'location',
location: { latitude, longitude, name, address },
});
}
async markAsRead(messageId: string): Promise<void> {
await this.client.post(`/${this.phoneNumberId}/messages`, {
messaging_product: 'whatsapp',
status: 'read',
message_id: messageId,
});
}
private async sendWithRetry(
payload: SendMessagePayload,
maxRetries = 3
): Promise<SendMessageResponse> {
const nonRetryableCodes = [100, 131026, 131051, 132000, 132001, 132005, 133010];
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
const response = await this.client.post<SendMessageResponse>(
`/${this.phoneNumberId}/messages`,
payload
);
return response.data;
} catch (error: any) {
const errorCode = error.response?.data?.error?.code;
const errorMessage = error.response?.data?.error?.message || error.message;
if (nonRetryableCodes.includes(errorCode)) {
throw new Error(`WhatsApp API Error ${errorCode}: ${errorMessage}`);
}
if (attempt < maxRetries) {
const delay = Math.pow(2, attempt) * 1000;
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}
throw new Error(`WhatsApp API Error after ${maxRetries} retries: ${errorMessage}`);
}
}
throw new Error('Unexpected: retry loop exited without return or throw');
}
}