All files / lib/communication whatsapp-board-invite.ts

100% Statements 15/15
100% Branches 8/8
100% Functions 1/1
100% Lines 15/15

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87                                                      4x 4x   4x     1x     1x     3x   3x                             3x                 3x 3x   3x 1x     1x         2x       4x          
import { WhatsAppAdapter } from "./adapters/whatsapp.adapter";
import type { CommunicationQueueMessage, WhatsAppContent } from "./types";
 
/**
 * Send a mood-board share invite via WhatsApp.
 *
 * Mirrors whatsapp-otp.ts: routes through WhatsAppAdapter so non-prod safety
 * guards (override number + dry-run in local/dev) apply. Falls back to
 * console logging when WHATSAPP_ACCESS_TOKEN is unset so dev envs never
 * fail on this flow.
 *
 * The template is expected to take 3 body params:
 *   {{1}} inviter first name
 *   {{2}} board name
 *   {{3}} invite URL
 *
 * Returns delivery metadata so callers can log actualRecipient / provider.
 */
export async function sendWhatsAppBoardInvite(
	env: CloudflareBindings,
	params: {
		toPhone: string; // E.164, e.g. "+919876543210"
		inviterFirstName: string;
		boardName: string;
		url: string;
	},
): Promise<{ actualRecipient: string; provider: string }> {
	const accessToken = env.WHATSAPP_ACCESS_TOKEN;
	const templateName = env.WHATSAPP_INVITE_TEMPLATE ?? "board_invite_v1";
 
	if (!accessToken) {
		// DEV ONLY: Console fallback so local/dev without WA creds doesn't fail.
		// In production, WHATSAPP_ACCESS_TOKEN must be set as a secret.
		console.log(
			`[WhatsApp BoardInvite] Phone: ${params.toPhone} | Board: "${params.boardName}" | URL: ${params.url} | Provider: console (no WhatsApp credentials)`,
		);
		return { actualRecipient: params.toPhone, provider: "console" };
	}
 
	const strippedPhone = params.toPhone.replace("+", "");
 
	const content: WhatsAppContent = {
		templateName,
		languageCode: "en",
		components: [
			{
				type: "body",
				parameters: [
					{ type: "text", text: params.inviterFirstName },
					{ type: "text", text: params.boardName },
					{ type: "text", text: params.url },
				],
			},
		],
	};
 
	const message: CommunicationQueueMessage = {
		logId: 0, // Not queued — transactional direct send, no log entry yet
		channel: "whatsapp",
		recipient: strippedPhone,
		eventType: "team_invitation",
		content,
		transactional: true,
	};
 
	const adapter = new WhatsAppAdapter();
	const result = await adapter.send(message, env);
 
	if (result.status === "failed") {
		console.error(
			`[WhatsApp BoardInvite] Failed: ${result.errorMessage}`,
		);
		throw new Error(
			`WhatsApp board invite delivery failed: ${result.errorMessage}`,
		);
	}
 
	console.log(
		`[WhatsApp BoardInvite] Sent to ${result.actualRecipient} (provider: ${result.provider}${result.externalId ? `, message_id: ${result.externalId}` : ""})`,
	);
 
	return {
		actualRecipient: result.actualRecipient,
		provider: result.provider,
	};
}