function xmlEscape(value: string): string { return value .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } /** * Applications de dialplan permitidas (agente.md secao 180: nunca deixar * input de tenant virar comando arbitrário no FreeSWITCH — "system", * "exec", "socket" etc. ficam de fora de propósito, mesmo que existam * módulos capazes de rodá-las). */ export const ALLOWED_DIALPLAN_APPLICATIONS = [ "answer", "pre_answer", "bridge", "hangup", "park", "playback", "ring_ready", "respond", "set", "export", "transfer", "sleep", "record_session", ] as const; export type AllowedDialplanApplication = (typeof ALLOWED_DIALPLAN_APPLICATIONS)[number]; export const ALLOWED_CONDITION_FIELDS = [ "destination_number", "caller_id_number", "caller_id_name", "context", "network_addr", "source", ] as const; export type AllowedConditionField = (typeof ALLOWED_CONDITION_FIELDS)[number]; export interface DialplanAction { application: AllowedDialplanApplication; data?: string; } export interface DialplanExtensionInput { name: string; conditionField: AllowedConditionField; conditionExpr: string; actions: DialplanAction[]; antiActions?: DialplanAction[]; continueOnFalse: boolean; order: number; } function actionsXml(tag: "action" | "anti-action", actions: DialplanAction[] | undefined): string { if (!actions || actions.length === 0) return ""; return actions .map( (a) => ` <${tag} application="${xmlEscape(a.application)}"${ a.data !== undefined ? ` data="${xmlEscape(a.data)}"` : "" }/>`, ) .join("\n"); } /** * XML de dialplan (agente.md secao 43-44). Uma condição por extension — * simplificação deliberada em relação ao FreeSWITCH puro (que permite * múltiplas por extension); cobre o editor estruturado descrito * na especificação sem a complexidade de encadeamento arbitrário. */ export function buildDialplanXml(context: string, extensions: DialplanExtensionInput[]): string { const sorted = [...extensions].sort((a, b) => a.order - b.order); const extensionsXml = sorted .map((ext) => { const actionsBlock = actionsXml("action", ext.actions); const antiActionsBlock = actionsXml("anti-action", ext.antiActions); return ` ${actionsBlock}${antiActionsBlock ? `\n${antiActionsBlock}` : ""} `; }) .join("\n"); return `
${extensionsXml}
`; }