cloudonix-js
Version:
A JavaScript library for building and serving Cloudonix Voice Application XML (CXML) documents with support for AI voice agents, media streaming, and advanced telephony features
452 lines (399 loc) • 13.6 kB
JavaScript
/**
* @file Examples demonstrating the Gather verb functionality
* @copyright 2025 Nir Simionovich, nirs@cloudonix.io
* @license MIT
*/
'use strict';
const { CXMLBuilder, CXMLServer } = require('../');
/**
* Creates a simple Gather with DTMF input
* @returns {CXMLBuilder} - CXML builder with a simple gather
*/
function createSimpleGather() {
return new CXMLBuilder()
.createResponse()
.addGather({
numDigits: 1,
timeout: 5,
action: "/handle-gather"
})
.done();
}
/**
* Creates a Gather with Say nested element
* @returns {CXMLBuilder} - CXML builder with a gather containing say elements
*/
function createGatherWithSay() {
return new CXMLBuilder()
.createResponse()
.addGather({
input: 'dtmf',
timeout: 3,
numDigits: 1,
action: '/handle-gather',
method: 'POST'
})
.addSay('Please enter a digit', { voice: 'Google:en-US-Neural2-F', language: 'en-US' })
.done();
}
/**
* Creates a Gather with speech recognition
* @returns {CXMLBuilder} - CXML builder with a speech recognition gather
*/
function createSpeechGather() {
return new CXMLBuilder()
.createResponse()
.addGather({
input: 'speech',
speechTimeout: 5,
speechEngine: 'google',
language: 'en-US',
actionOnEmptyResult: true,
action: '/handle-speech'
})
.addSay('Please tell us why you are calling', { voice: 'Google:en-US-Neural2-F' })
.done();
}
/**
* Creates a Gather with both DTMF and speech
* @returns {CXMLBuilder} - CXML builder with a dual-mode gather
*/
function createDualModeGather() {
return new CXMLBuilder()
.createResponse()
.addGather({
input: 'dtmf speech',
timeout: 3,
speechTimeout: 5,
language: 'en-US',
action: '/handle-dual-input',
maxTimeout: 10,
maxDuration: 60
})
.addPlay('http://example.com/welcome-message.mp3')
.done();
}
/**
* Creates a Gather with multiple nested elements
* @returns {CXMLBuilder} - CXML builder with a gather containing multiple elements
*/
function createMultiElementGather() {
return new CXMLBuilder()
.createResponse()
.addGather({
input: 'dtmf speech',
timeout: 5,
speechDetection: 'normal',
interruptible: false,
action: '/handle-gather'
})
.addSay('Welcome to our phone system', { voice: 'Google:en-US-Neural2-F' })
.addPause(1)
.addSay('Please enter your account number or say it aloud', { voice: 'Google:en-US-Neural2-F' })
.addPlay('http://example.com/beep.mp3')
.done();
}
/**
* Creates a complex Gather with all options
* @returns {CXMLBuilder} - CXML builder with a feature-rich gather
*/
function createComplexGather() {
return new CXMLBuilder()
.createResponse()
.addGather({
action: '/process-input',
method: 'POST',
input: 'dtmf speech',
finishOnKey: '#',
numDigits: 6,
maxTimeout: 15,
timeout: 5,
speechTimeout: 'auto',
speechEngine: 'google',
language: 'en-US',
actionOnEmptyResult: true,
maxDuration: 120,
speechDetection: 'high',
interruptible: true,
hints: 'one,two,three,four,five,six,seven,eight,nine,zero'
})
.addSay('Please provide your 6-digit verification code', { voice: 'Google:en-US-Neural2-F' })
.addPause(1)
.addSay('Press pound when finished or simply say the code', { voice: 'Google:en-US-Neural2-F' })
.done();
}
// Run as standalone script if invoked directly
if (require.main === module) {
// If running as a script, just output the XML for all examples
console.log("Example 1: Simple Gather with DTMF");
console.log(createSimpleGather().build());
console.log("\n");
console.log("Example 2: Gather with Say nested element");
console.log(createGatherWithSay().build());
console.log("\n");
console.log("Example 3: Gather with speech recognition");
console.log(createSpeechGather().build());
console.log("\n");
console.log("Example 4: Gather with both DTMF and speech");
console.log(createDualModeGather().build());
console.log("\n");
console.log("Example 5: Gather with multiple nested elements");
console.log(createMultiElementGather().build());
console.log("\n");
console.log("Example 6: Complex Gather with all options");
console.log(createComplexGather().build());
console.log("\n");
// Run the server if the --server flag is passed
if (process.argv.includes('--server')) {
runServer();
}
} else {
// Export the functions for use in other modules
module.exports = {
createSimpleGather,
createGatherWithSay,
createSpeechGather,
createDualModeGather,
createMultiElementGather,
createComplexGather
};
}
/**
* Runs a CXML server with the Gather examples
*/
function runServer() {
const PORT = process.env.PORT || 3004;
// Create a new CXML server
const server = new CXMLServer({
port: PORT,
verbose: true
});
// Example routes
server.route('/simple-gather', async (context) => {
return createSimpleGather();
});
server.route('/gather-with-say', async (context) => {
return createGatherWithSay();
});
server.route('/speech-gather', async (context) => {
return createSpeechGather();
});
server.route('/dual-mode-gather', async (context) => {
return createDualModeGather();
});
server.route('/multi-element-gather', async (context) => {
return createMultiElementGather();
});
server.route('/complex-gather', async (context) => {
return createComplexGather();
});
// Handle gather inputs
server.route('/handle-gather', async (context) => {
console.log('Gather input received:');
if (context.body && typeof context.body === 'object') {
console.log(JSON.stringify(context.body, null, 2));
} else {
console.log(context.body);
}
let digits = '';
// Extract digits from the request body
if (typeof context.body === 'object' && context.body.Digits) {
digits = context.body.Digits;
} else if (typeof context.body === 'string') {
// Try to parse form data or JSON
if (context.body.includes('Digits=')) {
const match = context.body.match(/Digits=(\d+)/);
if (match) digits = match[1];
} else {
try {
const data = JSON.parse(context.body);
if (data.Digits) digits = data.Digits;
} catch (e) {
// Not JSON, ignore
}
}
}
// Respond based on the gathered input
return new CXMLBuilder()
.createResponse()
.addSay(`You entered ${digits || 'no digits'}. Thank you for your input.`, {
voice: "Google:en-US-Neural2-F"
})
.addHangup();
});
// Handle speech inputs
server.route('/handle-speech', async (context) => {
console.log('Speech input received:');
if (context.body && typeof context.body === 'object') {
console.log(JSON.stringify(context.body, null, 2));
} else {
console.log(context.body);
}
let speechResult = 'no speech detected';
// Extract speech from the request body
if (typeof context.body === 'object') {
if (context.body.SpeechResult) {
speechResult = context.body.SpeechResult;
} else if (context.body.speech_result) {
speechResult = context.body.speech_result;
}
}
// Respond with the recognized speech
return new CXMLBuilder()
.createResponse()
.addSay(`You said: ${speechResult}. Thank you for your input.`, {
voice: "Google:en-US-Neural2-F"
})
.addHangup();
});
// Handle dual mode inputs
server.route('/handle-dual-input', async (context) => {
console.log('Dual mode input received:');
if (context.body && typeof context.body === 'object') {
console.log(JSON.stringify(context.body, null, 2));
} else {
console.log(context.body);
}
let inputType = 'unknown';
let inputValue = 'nothing';
// Determine the type of input and extract the value
if (typeof context.body === 'object') {
if (context.body.Digits) {
inputType = 'DTMF';
inputValue = context.body.Digits;
} else if (context.body.SpeechResult || context.body.speech_result) {
inputType = 'speech';
inputValue = context.body.SpeechResult || context.body.speech_result;
}
}
// Respond based on the input type
return new CXMLBuilder()
.createResponse()
.addSay(`You provided input via ${inputType}. The value was: ${inputValue}.`, {
voice: "Google:en-US-Neural2-F"
})
.addHangup();
});
// Handle complex gather processing
server.route('/process-input', async (context) => {
console.log('Complex gather input received:');
if (context.body && typeof context.body === 'object') {
console.log(JSON.stringify(context.body, null, 2));
} else {
console.log(context.body);
}
let verificationCode = '';
// Extract the verification code
if (typeof context.body === 'object') {
if (context.body.Digits) {
verificationCode = context.body.Digits;
} else if (context.body.SpeechResult || context.body.speech_result) {
// For speech, try to normalize the result to digits
const speech = context.body.SpeechResult || context.body.speech_result || '';
verificationCode = speech.replace(/[^0-9]/g, '');
}
}
// Check if we got a 6-digit code
const isValidCode = /^\d{6}$/.test(verificationCode);
if (isValidCode) {
return new CXMLBuilder()
.createResponse()
.addSay(`Verification code ${verificationCode} accepted. Your account is verified.`, {
voice: "Google:en-US-Neural2-F"
})
.addHangup();
} else {
return new CXMLBuilder()
.createResponse()
.addSay("Invalid verification code. Please try again.", {
voice: "Google:en-US-Neural2-F"
})
.addRedirect('/complex-gather');
}
});
// Home page with links to all examples
server.route('/', async (context) => {
// Return a simple HTML page for browser navigation
context.setHeader('Content-Type', 'text/html; charset=utf-8');
return `
<!DOCTYPE html>
<html>
<head>
<title>Gather Verb Examples</title>
<style>
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
h1 { color: #333; }
a { display: block; margin: 10px 0; padding: 10px; background: #f4f4f4; text-decoration: none; color: #333; border-radius: 4px; }
a:hover { background: #e0e0e0; }
pre { background: #f8f8f8; padding: 10px; border-radius: 4px; overflow-x: auto; }
.section { margin-bottom: 30px; }
.description { color: #666; margin-bottom: 5px; }
</style>
</head>
<body>
<h1>Gather Verb Examples</h1>
<p>Click the links below to see different Gather verb examples:</p>
<div class="section">
<div class="description">Basic Gather waiting for DTMF input</div>
<a href="/simple-gather">Simple Gather with DTMF</a>
</div>
<div class="section">
<div class="description">Gather with voice prompt</div>
<a href="/gather-with-say">Gather with Say nested element</a>
</div>
<div class="section">
<div class="description">Gather using speech recognition</div>
<a href="/speech-gather">Gather with speech recognition</a>
</div>
<div class="section">
<div class="description">Gather accepting both DTMF and speech input</div>
<a href="/dual-mode-gather">Gather with both DTMF and speech</a>
</div>
<div class="section">
<div class="description">Gather with multiple nested voice elements</div>
<a href="/multi-element-gather">Gather with multiple nested elements</a>
</div>
<div class="section">
<div class="description">Complex Gather with all available options</div>
<a href="/complex-gather">Complex Gather with all options</a>
</div>
<h2>How It Works</h2>
<p>These examples demonstrate how to use the Gather verb in Cloudonix CXML. In a real telephony application, these would collect user input via keypresses or speech.</p>
<h3>Example Code:</h3>
<pre>
.addGather({
action: '/process-input',
method: 'POST',
input: 'dtmf speech',
finishOnKey: '#',
numDigits: 6,
timeout: 5,
speechTimeout: 'auto',
speechEngine: 'google',
language: 'en-US'
})
.addSay('Please provide your verification code')
.addSay('Press pound when finished or say the code')
.done()
</pre>
</body>
</html>
`;
});
// Start the server
async function startServer() {
try {
await server.start();
console.log(`Gather Examples server running at http://localhost:${PORT}/`);
console.log('Open the above URL in your browser to see available examples');
} catch (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
}
startServer();
}
// For running with --server flag from command line
if (process.argv.includes('--server')) {
runServer();
}