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 | 17x 17x 12x 17x 2x 2x 15x 15x 17x 17x 17x 17x 15x 2x 2x 8x 17x | import { normalizeToE164 } from "@interioring/utils/validation/phone";
import { TPL_AUTH_OTP } from "../../config/whatsapp-templates";
import type { Dal } from "../../dal";
import { WhatsAppAdapter } from "./adapters/whatsapp.adapter";
import type { CommunicationQueueMessage, WhatsAppContent } from "./types";
/**
* Send OTP via WhatsApp, routed through WhatsAppAdapter for safety guards.
*
* In non-prod environments, the adapter redirects non-allowlisted numbers
* to the configured override number (see env-config.ts).
* In local/dev + redirected, the adapter performs a dry run (no API call).
*
* Falls back to console logging if WHATSAPP_ACCESS_TOKEN is not configured.
*
* Returns delivery metadata so callers can log actualRecipient and provider.
*/
export async function sendWhatsAppOtp(
env: CloudflareBindings,
phoneNumber: string,
code: string,
dal?: Dal,
): Promise<{ actualRecipient: string; provider: string }> {
const accessToken = env.WHATSAPP_ACCESS_TOKEN;
// Print the code in local and dev, on EVERY path, before anything can fail.
//
// This used to happen only in the `!accessToken` branch below, which meant
// it never fired for anyone whose .dev.vars had a token — i.e. anyone with
// a working local setup. With a token present the send goes to the adapter,
// which correctly dry-runs in non-prod and logs `[WA_DRY_RUN] ... message
// NOT sent`, naming the recipient but never the code. So the message is not
// delivered AND the code is invisible: the login is untestable in dev, which
// is the environment the fallback existed to serve.
//
// Logged BEFORE the send rather than after, so a delivery failure still
// leaves the developer able to log in — the code is already valid at this
// point, since storeOtp ran before we were called.
//
// Fails CLOSED. Only the two environments the adapter itself treats as
// dry-run (`local`, `dev`) print. `preview` carries real prospects, and an
// unset ENVIRONMENT prints nothing rather than assuming it is safe — the
// wrong way round would put one-time codes in production logs.
if (env.ENVIRONMENT === "local" || env.ENVIRONMENT === "dev") {
console.log(
`[WhatsApp OTP] ${env.ENVIRONMENT.toUpperCase()} — Phone: ${phoneNumber} | Code: ${code}`,
);
}
if (!accessToken) {
// No credentials at all: there is nothing to send with, so the console
// IS the transport. Unlike the block above this must log regardless of
// environment — a production worker with no token is broken, and the
// line is how that gets noticed.
console.log(
`[WhatsApp OTP] Phone: ${phoneNumber} | Code: ${code} | Provider: console (no WhatsApp credentials)`,
);
return { actualRecipient: phoneNumber, provider: "console" };
}
// WhatsApp Cloud API expects digits-only with country code (e.g., "919876543210").
// Normalize first so any input format (E.164, formatted, raw) ends up consistent.
const e164 = normalizeToE164(phoneNumber);
const strippedPhone = e164 ? e164.slice(1) : phoneNumber.replace(/\D/g, "");
const content: WhatsAppContent = {
templateName: TPL_AUTH_OTP,
languageCode: "en",
components: [
{
type: "body",
parameters: [{ type: "text", text: code }],
},
{
type: "button",
sub_type: "url",
index: 0,
parameters: [{ type: "text", text: code }],
},
],
};
const message: CommunicationQueueMessage = {
logId: 0, // Not queued — no log entry yet
channel: "whatsapp",
recipient: strippedPhone,
eventType: "otp_verification",
content,
transactional: true,
};
const adapter = new WhatsAppAdapter();
const result = await adapter.send(message, env, dal);
if (result.status === "failed") {
console.error(`[WhatsApp OTP] Failed: ${result.errorMessage}`);
throw new Error(`WhatsApp OTP delivery failed: ${result.errorMessage}`);
}
console.log(
`[WhatsApp OTP] Sent to ${result.actualRecipient} (provider: ${result.provider}${result.externalId ? `, message_id: ${result.externalId}` : ""})`,
);
return {
actualRecipient: result.actualRecipient,
provider: result.provider,
};
}
|