Add TWILIO_SENDER_SID override and better funnel/setup error messages
This commit is contained in:
@@ -7,6 +7,7 @@ Small TypeScript CLI to send, monitor, and webhook WhatsApp messages via Twilio.
|
||||
1. `pnpm install`
|
||||
2. Copy `.env.example` to `.env` and fill in `TWILIO_ACCOUNT_SID`, `TWILIO_AUTH_TOKEN`, and `TWILIO_WHATSAPP_FROM` (use your approved WhatsApp-enabled Twilio number, prefixed with `whatsapp:`).
|
||||
- Alternatively, use API keys: `TWILIO_API_KEY` + `TWILIO_API_SECRET` instead of `TWILIO_AUTH_TOKEN`.
|
||||
- Optional: `TWILIO_SENDER_SID` to skip auto-discovery of the WhatsApp sender in Twilio.
|
||||
3. Build once for the runnable bin: `pnpm build`
|
||||
|
||||
## Commands
|
||||
|
||||
81
src/index.ts
81
src/index.ts
@@ -46,6 +46,7 @@ type GlobalOptions = {
|
||||
type EnvConfig = {
|
||||
accountSid: string;
|
||||
whatsappFrom: string;
|
||||
whatsappSenderSid?: string;
|
||||
auth: AuthMode;
|
||||
};
|
||||
|
||||
@@ -53,6 +54,7 @@ function readEnv(): EnvConfig {
|
||||
// Load and validate Twilio auth + sender configuration from env.
|
||||
const accountSid = process.env.TWILIO_ACCOUNT_SID;
|
||||
const whatsappFrom = process.env.TWILIO_WHATSAPP_FROM;
|
||||
const whatsappSenderSid = process.env.TWILIO_SENDER_SID;
|
||||
const authToken = process.env.TWILIO_AUTH_TOKEN;
|
||||
const apiKey = process.env.TWILIO_API_KEY;
|
||||
const apiSecret = process.env.TWILIO_API_SECRET;
|
||||
@@ -79,6 +81,7 @@ function readEnv(): EnvConfig {
|
||||
return {
|
||||
accountSid,
|
||||
whatsappFrom,
|
||||
whatsappSenderSid,
|
||||
auth
|
||||
};
|
||||
}
|
||||
@@ -445,28 +448,39 @@ async function ensureFunnel(port: number) {
|
||||
|
||||
async function findWhatsappSenderSid(client: ReturnType<typeof createClient>, from: string) {
|
||||
// Fetch sender SID that matches configured WhatsApp from number.
|
||||
const resp = await (client as unknown as { request: (options: Record<string, unknown>) => Promise<{ data?: unknown }> }).request({
|
||||
method: 'get',
|
||||
uri: 'https://messaging.twilio.com/v2/Channels/Senders',
|
||||
qs: { Channel: 'whatsapp', PageSize: 50 }
|
||||
});
|
||||
const data = resp?.data as Record<string, unknown> | undefined;
|
||||
const senders = Array.isArray((data as Record<string, unknown> | undefined)?.senders)
|
||||
? (data as { senders: unknown[] }).senders
|
||||
: undefined;
|
||||
if (!senders) {
|
||||
throw new Error('Unable to list WhatsApp senders');
|
||||
try {
|
||||
const resp = await (client as unknown as { request: (options: Record<string, unknown>) => Promise<{ data?: unknown }> }).request({
|
||||
method: 'get',
|
||||
uri: 'https://messaging.twilio.com/v2/Channels/Senders',
|
||||
qs: { Channel: 'whatsapp', PageSize: 50 }
|
||||
});
|
||||
const data = resp?.data as Record<string, unknown> | undefined;
|
||||
const senders = Array.isArray((data as Record<string, unknown> | undefined)?.senders)
|
||||
? (data as { senders: unknown[] }).senders
|
||||
: undefined;
|
||||
if (!senders) {
|
||||
throw new Error('List senders response missing "senders" array');
|
||||
}
|
||||
const match = senders.find(
|
||||
(s) =>
|
||||
typeof s === 'object' &&
|
||||
s !== null &&
|
||||
(s as Record<string, unknown>).sender_id === withWhatsAppPrefix(from)
|
||||
) as { sid?: string } | undefined;
|
||||
if (!match || typeof match.sid !== 'string') {
|
||||
throw new Error(`Could not find sender ${withWhatsAppPrefix(from)} in Twilio account`);
|
||||
}
|
||||
return match.sid;
|
||||
} catch (err) {
|
||||
console.error(danger('Unable to list WhatsApp senders via Twilio API.'));
|
||||
if (globalVerbose) console.error(err);
|
||||
console.error(
|
||||
info(
|
||||
'Provide TWILIO_SENDER_SID in .env to skip lookup (find it in Twilio Console → Messaging → Senders → WhatsApp).'
|
||||
)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
const match = senders.find(
|
||||
(s) =>
|
||||
typeof s === 'object' &&
|
||||
s !== null &&
|
||||
(s as Record<string, unknown>).sender_id === withWhatsAppPrefix(from)
|
||||
) as { sid?: string } | undefined;
|
||||
if (!match || typeof match.sid !== 'string') {
|
||||
throw new Error(`Could not find sender ${withWhatsAppPrefix(from)} in Twilio account`);
|
||||
}
|
||||
return match.sid;
|
||||
}
|
||||
|
||||
async function updateWebhook(
|
||||
@@ -476,15 +490,22 @@ async function updateWebhook(
|
||||
method: 'POST' | 'GET' = 'POST'
|
||||
) {
|
||||
// Point Twilio sender webhook at the provided URL.
|
||||
await (client as unknown as { request: (options: Record<string, unknown>) => Promise<unknown> }).request({
|
||||
method: 'post',
|
||||
uri: `https://messaging.twilio.com/v2/Channels/Senders/${senderSid}`,
|
||||
form: {
|
||||
CallbackUrl: url,
|
||||
CallbackMethod: method
|
||||
}
|
||||
});
|
||||
console.log(`✅ Twilio webhook set to ${url}`);
|
||||
await (client as unknown as { request: (options: Record<string, unknown>) => Promise<unknown> })
|
||||
.request({
|
||||
method: 'post',
|
||||
uri: `https://messaging.twilio.com/v2/Channels/Senders/${senderSid}`,
|
||||
form: {
|
||||
CallbackUrl: url,
|
||||
CallbackMethod: method
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(danger('Failed to set Twilio webhook.'));
|
||||
if (globalVerbose) console.error(err);
|
||||
console.error(info('Double-check your sender SID and credentials; you can set TWILIO_SENDER_SID to force a specific sender.'));
|
||||
process.exit(1);
|
||||
});
|
||||
console.log(success(`✅ Twilio webhook set to ${url}`));
|
||||
}
|
||||
|
||||
function sleep(ms: number) {
|
||||
|
||||
Reference in New Issue
Block a user