Fix issues and add linting

This commit is contained in:
khalid@traclabs.com
2026-04-22 22:44:03 -05:00
parent 498d873c47
commit bcd047bc54
21 changed files with 10634 additions and 134 deletions

View File

@@ -1,30 +1,95 @@
import { Vonage } from '@vonage/server-sdk';
import { SMS } from '@vonage/messages';
import { Auth } from '@vonage/auth';
import { logger, maskPhone } from '../logger.js';
const TELNYX_API_KEY = process.env.TELNYX_API_KEY || '';
const VONAGE_API_KEY = process.env.VONAGE_API_KEY || '';
const VONAGE_API_SECRET = process.env.VONAGE_API_SECRET || '';
const VONAGE_APPLICATION_ID = process.env.VONAGE_APPLICATION_ID || '';
const VONAGE_PRIVATE_KEY_PATH = process.env.VONAGE_PRIVATE_KEY_PATH || '';
const VONAGE_PRIVATE_KEY = process.env.VONAGE_PRIVATE_KEY || '';
let vonageClient: Vonage | null = null;
/**
* Resolve the private key for the Vonage SDK.
*
* Supports three modes (checked in order):
* 1. VONAGE_PRIVATE_KEY — raw PEM string (starts with "-----BEGIN")
* 2. VONAGE_PRIVATE_KEY — base64-encoded PEM (decode first)
* 3. VONAGE_PRIVATE_KEY_PATH — path to a .key file on disk
*
* The Vonage Auth constructor accepts either a file path (string) or
* the key content directly (string / Buffer).
*/
function resolvePrivateKey(): string | undefined {
if (VONAGE_PRIVATE_KEY) {
// Raw PEM passed directly
if (VONAGE_PRIVATE_KEY.startsWith('-----BEGIN')) {
return VONAGE_PRIVATE_KEY;
}
// Base64-encoded PEM (common for PaaS env vars)
try {
const decoded = Buffer.from(VONAGE_PRIVATE_KEY, 'base64').toString('utf-8');
if (decoded.startsWith('-----BEGIN')) return decoded;
} catch { /* fall through */ }
// Treat as raw content anyway
return VONAGE_PRIVATE_KEY;
}
if (VONAGE_PRIVATE_KEY_PATH) {
return VONAGE_PRIVATE_KEY_PATH;
}
return undefined;
}
function getVonageClient(): Vonage | null {
if (vonageClient) return vonageClient;
const privateKey = resolvePrivateKey();
if (!VONAGE_APPLICATION_ID || !privateKey) {
return null;
}
vonageClient = new Vonage(new Auth({
apiKey: VONAGE_API_KEY,
apiSecret: VONAGE_API_SECRET,
applicationId: VONAGE_APPLICATION_ID,
privateKey,
}));
return vonageClient;
}
/**
* Send an SMS reply via the Vonage Messages API.
* Phone numbers should be in E.164 format (e.g. +14155550100).
* Vonage expects numbers without the leading '+'.
*/
export async function sendSms(to: string, from: string, body: string): Promise<void> {
if (!TELNYX_API_KEY) {
logger.warn({ event: 'sms.send_skipped', to: maskPhone(to) }, 'No TELNYX_API_KEY, skipping SMS send');
const client = getVonageClient();
if (!client) {
logger.warn({ event: 'sms.send_skipped', to: maskPhone(to) }, 'No Vonage credentials configured, skipping SMS send');
logger.info({ event: 'sms.would_send', body }, 'SMS body (dev mode)');
return;
}
try {
const resp = await fetch('https://api.telnyx.com/v2/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${TELNYX_API_KEY}`,
},
body: JSON.stringify({ from, to, text: body }),
});
// Strip leading '+' — Vonage expects bare numbers
const toNumber = to.replace(/^\+/, '');
const fromNumber = from.replace(/^\+/, '');
if (!resp.ok) {
logger.error({ event: 'sms.send_failed', status: resp.status }, 'Failed to send SMS');
} else {
logger.info({ event: 'sms.sent', to: maskPhone(to) }, 'SMS sent');
}
try {
await client.messages.send(
new SMS({ to: toNumber, from: fromNumber, text: body })
);
logger.info({ event: 'sms.sent', to: maskPhone(to) }, 'SMS sent');
} catch (err) {
logger.error({ event: 'sms.send_error', error: (err as Error).message }, 'SMS send error');
const error = err as Error & { response?: { data?: unknown; status?: number } };
logger.error({
event: 'sms.send_error',
error: error.message,
status: error.response?.status,
}, 'SMS send error');
}
}