/opt/mawid/apps/api/src/whatsapp
Edit: /opt/mawid/apps/api/src/whatsapp/wa-http.client.ts (1736B)
import { Logger } from '@nestjs/common';
import { randomUUID } from 'node:crypto';
import type { WaOutboundRequest, WaSendResponse } from './wa-types';
export const WA_HTTP_CLIENT = Symbol('WA_HTTP_CLIENT');
/**
* Transport for the Meta Cloud API. Kept behind an interface so uncertain
* Meta details stay in one place and tests/dev can stub it (PROJECT_PLAN §7.6).
*/
export interface WaHttpClient {
send(phoneNumberId: string, body: WaOutboundRequest): Promise
;
}
const GRAPH_BASE = 'https://graph.facebook.com/v21.0';
export class MetaWaHttpClient implements WaHttpClient {
constructor(private readonly accessToken: string) {}
async send(phoneNumberId: string, body: WaOutboundRequest): Promise {
const res = await fetch(`${GRAPH_BASE}/${phoneNumberId}/messages`, {
method: 'POST',
headers: {
Authorization: `Bearer ${this.accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
if (!res.ok) {
const text = await res.text().catch(() => '');
// Thrown errors are retried by the BullMQ worker with backoff.
throw new Error(`WhatsApp send failed (${res.status}): ${text}`);
}
return (await res.json()) as WaSendResponse;
}
}
/** Used when WA_ACCESS_TOKEN is not configured (local dev / tests). */
export class StubWaHttpClient implements WaHttpClient {
private readonly logger = new Logger(StubWaHttpClient.name);
async send(phoneNumberId: string, body: WaOutboundRequest): Promise {
this.logger.log(`[stub] would send to ${body.to} via ${phoneNumberId}: ${JSON.stringify(body)}`);
return { messages: [{ id: `stub-wamid-${randomUUID()}` }] };
}
}