ari.connect(ARI_URL, ARI_USER, ARI_PASS, async (err, client) => {
if (err) { throw err; }
console.log(‘[ARI] Connected’);
client.on(‘StasisStart’, async (event, channel) => {
const argsString = (event.args && event.args.length > 0) ? event.args[0] : ‘’;
const args = argsString.split(‘:’);
const type = args[0] || ‘unknown’;
const callId = args[1] || null;
const customerNum = args[2] || null;
const agent = args[3] || null;
console.log(`[StasisStart] ${type} channel: ${channel.id}, callId=${callId}, agent=${agent}`);
if (!callId) return;
try {
await channel.answer();
console.log(`[${type}] Answered: ${channel.id}`);
if (!agentCalls[callId]) {
agentCalls[callId] = {
cleanedUp: false,
udp: { in: {}, out: {} },
extMedia: { in: {}, out: {} },
recordings: {},
snoops: {},
recordingBridges: {},
sequenceNumber: { agent: 0, customer: 0 },
mediaPacketCount: { agent: 0, customer: 0 },
mediaBytesCount: { agent: 0, customer: 0 },
chunkCounters: { agent: 0, customer: 0 },
streamSid: `stream-${callId}-${Date.now()}`,
streamStartTime: Date.now()
};
}
const call = agentCalls[callId];
call[`${type}Chan`] = channel;
if (!call.ws) {
call.ws = new WebSocket(VOICEBOT_URL);
call.ws.on('open', () => {
console.log(`[WS] Connected to bot for ${callId}`);
});
call.ws.on('close', () => console.log(`[WS] Closed for ${callId}`));
call.ws.on('error', (err) => console.error(`[WS] Error for ${callId}: ${err.message}`));
call.ws.on('message', (msg) => {
//console.log(`[WS:IN] Received bot message for ${callId}: ${msg.toString().substring(0, 100)}...`);
// }
});
}
if (type === 'agent') {
const bridge = await client.bridges.create({ type: 'mixing' });
call.bridge = bridge;
await bridge.addChannel({ channel: [channel.id]});
console.log(`[Bridge] Agent channels added to bridge`);
if (customerNum) {
try {
const custChan = await client.channels.originate({
endpoint: `SIP/genesys-trunk/${customerNum}`,
app: APP_NAME,
appArgs: `customer:${callId}:${customerNum}:${agent}`,
callerId: agent,
timeout: 30,
earlyMedia: true,
statusEvents: true
});
call.custChan = custChan;
console.log(`[Originate] Customer call started: ${customerNum}`);
} catch (err) {
console.error(`[Originate] Failed to start customer: ${err.message}`);
}
}
} else if (type === 'customer' && call.bridge) {
call.custChan = channel;
console.log(`[Customer] PSTN channel created: ${channel.id}`);
try {
await call.bridge.addChannel({ channel: [call.custChan.id] });
console.log(`[Customer] Customer channels added to bridge`);
// Wait a moment for channels to be fully established
await new Promise(resolve => setTimeout(resolve, 1000));
// Create Snoop Channels to listen to individual channels
await createSnoopChannel(client, call.agentChan, 'agent', callId);
await createSnoopChannel(client, call.custChan, 'customer', callId);
console.log(`[Snoop] Snoop channels created for both agent and customer`);
} catch (err) {
console.error(`[Customer] Failed to add customer to bridge: ${err.message}`);
await channel.hangup();
}
}
} catch (err) {
console.error(`[${type}] Error in StasisStart:`, err);
await cleanupCall(callId);
}
});
client.on(‘ChannelHangupRequest’, (event, channel) => {
for (const callId in agentCalls) {
const call = agentCalls[callId];
if (call && ((call.agentChan && call.agentChan.id === channel.id) || (call.custChan && call.custChan.id === channel.id))) {
cleanupCall(callId);
break;
}
}
});
client.on(‘StasisEnd’, (event, channel) => {
for (const callId in agentCalls) {
const call = agentCalls[callId];
if (call && ((call.agentChan && call.agentChan.id === channel.id) || (call.custChan && call.custChan.id === channel.id))) {
cleanupCall(callId);
break;
}
}
});
client.start(APP_NAME);
});