// NOTE: Twilio is now used only for the in-browser dial pad (Access Token // minting + browser-Device outbound-call TwiML). Everything else — SMS, // inbound/AI voice, staff-triggered outbound calls — moved to Telnyx, see // routes/telnyx.ts and routes/telnyx-webhooks.ts. import express, { Request, Response } from "express"; import twilio from "twilio"; import { storage } from "../storage"; const router = express.Router(); // GET /api/twilio/settings router.get("/settings", async (req: Request, res: Response): Promise => { try { const userId = req.user?.id; if (!userId) return res.status(401).json({ message: "Unauthorized" }); const settings = await storage.getTwilioSettings(userId); if (!settings) return res.status(200).json(null); const templates = (settings.templates as Record) || {}; return res.status(200).json({ id: settings.id, accountSid: settings.accountSid, authToken: settings.authToken, phoneNumber: settings.phoneNumber, twimlAppSid: templates["_twiml_app_sid"] || null, }); } catch (err) { return res.status(500).json({ error: "Failed to fetch Twilio settings", details: String(err) }); } }); // PUT /api/twilio/settings router.put("/settings", async (req: Request, res: Response): Promise => { try { const userId = req.user?.id; if (!userId) return res.status(401).json({ message: "Unauthorized" }); const { accountSid, authToken, phoneNumber, twimlAppSid } = req.body; if (!accountSid?.trim() || !authToken?.trim() || !phoneNumber?.trim()) { return res.status(400).json({ message: "accountSid, authToken, and phoneNumber are required" }); } const settings = await storage.upsertTwilioSettings(userId, { accountSid: accountSid.trim(), authToken: authToken.trim(), phoneNumber: phoneNumber.trim(), twimlAppSid: twimlAppSid?.trim() || null, }); const templates = (settings.templates as Record) || {}; return res.status(200).json({ id: settings.id, accountSid: settings.accountSid, authToken: settings.authToken, phoneNumber: settings.phoneNumber, twimlAppSid: templates["_twiml_app_sid"] || null, }); } catch (err) { return res.status(500).json({ error: "Failed to save Twilio settings", details: String(err) }); } }); // POST /api/twilio/voice-token // Returns a short-lived Twilio Access Token so the browser Voice SDK can place calls. router.post("/voice-token", async (req: Request, res: Response): Promise => { try { const userId = req.user?.id; if (!userId) return res.status(401).json({ message: "Unauthorized" }); const settings = await storage.getTwilioSettings(userId); if (!settings) { return res.status(400).json({ message: "Twilio is not configured. Please add your Twilio credentials in Settings." }); } const templates = (settings.templates as Record) || {}; const twimlAppSid = templates["_twiml_app_sid"]?.trim(); if (!twimlAppSid) { return res.status(400).json({ message: "TwiML App SID is not configured. Please add it in Twilio Settings." }); } const AccessToken = twilio.jwt.AccessToken; const VoiceGrant = AccessToken.VoiceGrant; const token = new AccessToken( settings.accountSid, settings.accountSid, settings.authToken, { identity: `user-${userId}`, ttl: 3600 } ); const voiceGrant = new VoiceGrant({ outgoingApplicationSid: twimlAppSid, incomingAllow: false }); token.addGrant(voiceGrant); return res.status(200).json({ token: token.toJwt(), phoneNumber: settings.phoneNumber }); } catch (err: any) { return res.status(500).json({ error: err.message || "Failed to generate voice token" }); } }); export default router;