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 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | 6x 6x 6x 2x 1x 1x 1x 1x 4x 4x 4x 4x 4x 4x 2x 2x 2x 6x | import { WhatsAppAdapter } from "./adapters/whatsapp.adapter";
import type {
AdapterErrorCode,
CommunicationQueueMessage,
WhatsAppContent,
} from "./types";
export type BoardInviteResult =
| { status: "sent"; actualRecipient: string; provider: string }
| {
status: "failed";
errorCode: AdapterErrorCode;
errorMessage: string;
};
/**
* 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 a discriminated result so callers can translate adapter errorCode
* into the right HTTP status + user-facing message instead of receiving an
* opaque thrown Error.
*/
export async function sendWhatsAppBoardInvite(
env: CloudflareBindings,
params: {
toPhone: string; // E.164, e.g. "+919876543210"
inviterFirstName: string;
boardName: string;
url: string;
},
): Promise<BoardInviteResult> {
const accessToken = env.WHATSAPP_ACCESS_TOKEN;
const templateName = env.WHATSAPP_INVITE_TEMPLATE ?? "board_invite_v1";
if (!accessToken) {
// In production, a missing token MUST surface as failure — otherwise the
// route returns 200 and the UI lies "Sent on WhatsApp" while nothing
// was delivered. Local/dev still console-fallback so devs without
// credentials aren't blocked.
if (env.ENVIRONMENT === "production") {
console.error(
"[WhatsApp BoardInvite] WHATSAPP_ACCESS_TOKEN missing in production",
);
return {
status: "failed",
errorCode: "config",
errorMessage: "WhatsApp not configured",
};
}
console.log(
`[WhatsApp BoardInvite] Phone: ${params.toPhone} | Board: "${params.boardName}" | URL: ${params.url} | Provider: console (no WhatsApp credentials)`,
);
return {
status: "sent",
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.errorCode ?? "unknown"}): ${result.errorMessage}`,
);
return {
status: "failed",
errorCode: result.errorCode ?? "unknown",
errorMessage: result.errorMessage ?? "Unknown delivery error",
};
}
console.log(
`[WhatsApp BoardInvite] Sent to ${result.actualRecipient} (provider: ${result.provider}${result.externalId ? `, message_id: ${result.externalId}` : ""})`,
);
return {
status: "sent",
actualRecipient: result.actualRecipient,
provider: result.provider,
};
}
|