feat(call, voicemail, ivr): add voicemail and IVR call flows with DTMF handling, prompt playback, recording, and dashboard management
This commit is contained in:
@@ -13,6 +13,7 @@ import https from 'node:https';
|
||||
import { WebSocketServer, WebSocket } from 'ws';
|
||||
import type { CallManager } from './call/index.ts';
|
||||
import { handleWebRtcSignaling } from './webrtcbridge.ts';
|
||||
import type { VoiceboxManager } from './voicebox.ts';
|
||||
|
||||
const CONFIG_PATH = path.join(process.cwd(), '.nogit', 'config.json');
|
||||
|
||||
@@ -84,6 +85,7 @@ async function handleRequest(
|
||||
onHangupCall: (callId: string) => boolean,
|
||||
onConfigSaved?: () => void,
|
||||
callManager?: CallManager,
|
||||
voiceboxManager?: VoiceboxManager,
|
||||
): Promise<void> {
|
||||
const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
|
||||
const method = req.method || 'GET';
|
||||
@@ -242,6 +244,8 @@ async function handleRequest(
|
||||
}
|
||||
}
|
||||
if (updates.contacts !== undefined) cfg.contacts = updates.contacts;
|
||||
if (updates.voiceboxes !== undefined) cfg.voiceboxes = updates.voiceboxes;
|
||||
if (updates.ivr !== undefined) cfg.ivr = updates.ivr;
|
||||
|
||||
fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2) + '\n');
|
||||
log('[config] updated config.json');
|
||||
@@ -252,6 +256,50 @@ async function handleRequest(
|
||||
}
|
||||
}
|
||||
|
||||
// API: voicemail - list messages.
|
||||
const vmListMatch = url.pathname.match(/^\/api\/voicemail\/([^/]+)$/);
|
||||
if (vmListMatch && method === 'GET' && voiceboxManager) {
|
||||
const boxId = vmListMatch[1];
|
||||
return sendJson(res, { ok: true, messages: voiceboxManager.getMessages(boxId) });
|
||||
}
|
||||
|
||||
// API: voicemail - unheard count.
|
||||
const vmUnheardMatch = url.pathname.match(/^\/api\/voicemail\/([^/]+)\/unheard$/);
|
||||
if (vmUnheardMatch && method === 'GET' && voiceboxManager) {
|
||||
const boxId = vmUnheardMatch[1];
|
||||
return sendJson(res, { ok: true, count: voiceboxManager.getUnheardCount(boxId) });
|
||||
}
|
||||
|
||||
// API: voicemail - stream audio.
|
||||
const vmAudioMatch = url.pathname.match(/^\/api\/voicemail\/([^/]+)\/([^/]+)\/audio$/);
|
||||
if (vmAudioMatch && method === 'GET' && voiceboxManager) {
|
||||
const [, boxId, msgId] = vmAudioMatch;
|
||||
const audioPath = voiceboxManager.getMessageAudioPath(boxId, msgId);
|
||||
if (!audioPath) return sendJson(res, { ok: false, error: 'not found' }, 404);
|
||||
const stat = fs.statSync(audioPath);
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'audio/wav',
|
||||
'Content-Length': stat.size.toString(),
|
||||
'Accept-Ranges': 'bytes',
|
||||
});
|
||||
fs.createReadStream(audioPath).pipe(res);
|
||||
return;
|
||||
}
|
||||
|
||||
// API: voicemail - mark as heard.
|
||||
const vmHeardMatch = url.pathname.match(/^\/api\/voicemail\/([^/]+)\/([^/]+)\/heard$/);
|
||||
if (vmHeardMatch && method === 'POST' && voiceboxManager) {
|
||||
const [, boxId, msgId] = vmHeardMatch;
|
||||
return sendJson(res, { ok: voiceboxManager.markHeard(boxId, msgId) });
|
||||
}
|
||||
|
||||
// API: voicemail - delete message.
|
||||
const vmDeleteMatch = url.pathname.match(/^\/api\/voicemail\/([^/]+)\/([^/]+)$/);
|
||||
if (vmDeleteMatch && method === 'DELETE' && voiceboxManager) {
|
||||
const [, boxId, msgId] = vmDeleteMatch;
|
||||
return sendJson(res, { ok: voiceboxManager.deleteMessage(boxId, msgId) });
|
||||
}
|
||||
|
||||
// Static files.
|
||||
const file = staticFiles.get(url.pathname);
|
||||
if (file) {
|
||||
@@ -288,6 +336,7 @@ export function initWebUi(
|
||||
onHangupCall: (callId: string) => boolean,
|
||||
onConfigSaved?: () => void,
|
||||
callManager?: CallManager,
|
||||
voiceboxManager?: VoiceboxManager,
|
||||
): void {
|
||||
const WEB_PORT = 3060;
|
||||
|
||||
@@ -303,12 +352,12 @@ export function initWebUi(
|
||||
const cert = fs.readFileSync(certPath, 'utf8');
|
||||
const key = fs.readFileSync(keyPath, 'utf8');
|
||||
server = https.createServer({ cert, key }, (req, res) =>
|
||||
handleRequest(req, res, getStatus, log, onStartCall, onHangupCall, onConfigSaved, callManager).catch(() => { res.writeHead(500); res.end(); }),
|
||||
handleRequest(req, res, getStatus, log, onStartCall, onHangupCall, onConfigSaved, callManager, voiceboxManager).catch(() => { res.writeHead(500); res.end(); }),
|
||||
);
|
||||
useTls = true;
|
||||
} catch {
|
||||
server = http.createServer((req, res) =>
|
||||
handleRequest(req, res, getStatus, log, onStartCall, onHangupCall, onConfigSaved, callManager).catch(() => { res.writeHead(500); res.end(); }),
|
||||
handleRequest(req, res, getStatus, log, onStartCall, onHangupCall, onConfigSaved, callManager, voiceboxManager).catch(() => { res.writeHead(500); res.end(); }),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user