UNPKG

advanced-ussd-builder

Version:

Advanced USSD Menu Builder with persistent state and navigation

1,144 lines (965 loc) 31.7 kB
# Advanced USSD Builder <div align="center"> ![npm version](https://img.shields.io/npm/v/advanced-ussd-builder.svg?style=flat-square) ![npm downloads](https://img.shields.io/npm/dm/advanced-ussd-builder.svg?style=flat-square) ![license](https://img.shields.io/npm/l/advanced-ussd-builder.svg?style=flat-square) ![typescript](https://img.shields.io/badge/TypeScript-4.5+-blue.svg?style=flat-square) **A powerful TypeScript library for building interactive USSD menus with persistent state management** [Installation](#installation) • [Quick Start](#quick-start) • [Documentation](#core-concepts) • [Examples](#usage-examples) • [API](#api-reference) </div> ## 📋 Table of Contents - [Features](#features) - [Installation](#installation) - [Quick Start](#quick-start) - [Provider Support](#provider-support) - [Core Concepts](#core-concepts) - [UssdMenu](#ussdmenu) - [UssdBuilder](#ussdbuilder) - [Session Management](#session-management) - [Usage Examples](#usage-examples) - [Render Menu](#render-menu-display-options) - [Render Menu with Function Handler](#render-menu-with-function-handler) - [Basic Menu](#basic-menu) - [Dynamic Menus](#dynamic-menus) - [Proxy Menu](#proxy-menu-external-service-integration) - [Handler Menus](#handler-menus) - [Conditional Menu Rendering](#conditional-menu-rendering) - [API Reference](#api-reference) - [Provider Integration](#provider-integration) - [MTN](#mtn) - [Nalo](#nalo) - [Telecel](#telecel) - [Airtel-Tigo](#airtel-tigo) - [Cross-Switch](#cross-switch) - [Custom Provider](#custom-provider) - [Advanced Features](#advanced-features) - [Session State Management](#session-state-management) - [Middleware Support](#middleware-support) - [Authentication Handler](#authentication-handler) - [Proxy Service Integration](#proxy-service-integration) - [Proxy Configuration Options](#proxy-configuration-options) - [Request Formats](#request-formats) - [XML Payload Support](#xml-payload-support) - [Backend Implementation Examples](#backend-implementation-examples) - [Use Cases](#use-cases) - [Path-based Handler Modules](#path-based-handler-modules) - [License](#license) ## ✨ Features - 🚀 **Multi-Provider Support** - Works with MTN, Nalo, Telecel, Airtel-Tigo, Cross-Switch, and Custom providers - 🔐 **Built-in Authentication** - PIN-based authentication with customizable handlers - 🔌 **Middleware Support** - Request/response interceptors for logging, validation, and transformation - 🎨 **Custom Provider Setup** - Easy integration with any USSD gateway through flexible configuration - 🔀 **Advanced Proxy Support** - Forward requests to external USSD services with automatic network detection, flexible request formats (telco-specific or normalized), and custom transformations - 💾 **Persistent State Management** - Redis-powered session management for stateful interactions - 📱 **Dynamic Menu Generation** - Create menus programmatically with render and handler functions - 🔄 **Navigation History** - Track and navigate through menu paths seamlessly - 🎯 **TypeScript Support** - Full type definitions for better development experience - ⚡ **High Performance** - Optimized for speed with minified production builds - 🔧 **Flexible Configuration** - Customizable session prefixes, navigation keys, and more ## 📦 Installation ```bash npm install advanced-ussd-builder ``` or using yarn: ```bash yarn add advanced-ussd-builder ``` ### Prerequisites - Node.js >= 14.x - Redis server (for session management) - TypeScript (for development) ## 🚀 Quick Start ```typescript import { UssdBuilder, UssdMenu, iUssdMenu, iUssdMenuOptions, iMTNUSSDArgs } from 'advanced-ussd-builder'; // Define menu configuration const menuOptions: iUssdMenuOptions = { provider: 'mtn', require_pin: false, service_code: '*123#', next_page_key: '#', back_page_key: '0', redis_connection_url: 'redis://localhost:6379', state: { user_id: '', session_data: {} } }; // Create handler menus with path-based handlers const registrationMenu: iUssdMenu = { title: 'Register', type: 'handler', handler_type: 'path', handler_name_in_destination: 'root_menu', handler: path.join(__dirname, '/handlers/registration.js') }; // Create handler menus with function handlers const balanceMenu: iUssdMenu = { title: 'Check Balance', type: 'handler', handler_type: 'function', handler: async (args) => { const balance = await getUserBalance(args.msisdn); return `Your balance is $${balance}`; } }; // Create menu array const menus = [registrationMenu, balanceMenu]; // Initialize USSD Builder with root menu const ussdBuilder = new UssdBuilder( menuOptions, new UssdMenu('Welcome to MyApp', menus) ); // Process USSD request (Express.js example) app.post('/ussd/mtn', async (req, res) => { const ussdArgs = req.body as iMTNUSSDArgs; const sendResponse = (response) => { return res.json(response); }; await ussdBuilder.pipe<'mtn'>(ussdArgs, sendResponse); }); ``` ## 🌐 Provider Support Advanced USSD Builder supports multiple telecom providers out of the box: | Provider | Status | Request Interface | Response Interface | |----------|--------|------------------|-------------------| | MTN | ✅ Supported | `iMTNUSSDArgs` | `iMTNUssdCallback` | | Nalo | ✅ Supported | `iNaloUSSDArgs` | `iNaloUssdCallback` | | Telecel | ✅ Supported | `iTelecelUssdArgs` | `iTelecelUssdCallBack` | | Airtel-Tigo | ✅ Supported | `iAirtelTigoUssdArgs` | `iAirtelTigoUssdCallBack` | | Cross-Switch | ✅ Supported | `iCrossSwitchUssdArgs` | `iCrossSwitchUssdCallBack` | | Custom | ✅ Supported | Custom Interface | Custom Response | ## 📚 Core Concepts ### UssdMenu The `UssdMenu` class represents a menu container that can hold multiple menu items. Each menu item follows the `iUssdMenu` interface structure. ```typescript // Create a root menu with multiple options const menuItems: iUssdMenu[] = [ { title: 'Option 1', type: 'handler', handler_type: 'function', handler: async (args) => { return 'Response for option 1'; } }, { title: 'Option 2', type: 'handler', handler_type: 'path', handler_name_in_destination: 'default', handler: './handlers/option2.js' } ]; const rootMenu = new UssdMenu('Select an option:', menuItems); ``` ### UssdBuilder The main orchestrator that manages menus, sessions, and request processing. It requires both configuration options and a root menu. ```typescript const options: iUssdMenuOptions = { provider: 'mtn', // or 'nalo', 'telecel', 'airtel-tigo', 'cross-switch', 'custom' require_pin: false, service_code: '*123#', next_page_key: '#', back_page_key: '0', redis_connection_url: 'redis://localhost:6379', state: {}, // Custom state object middleware: async (args) => { // Optional middleware }, authentication_handler: async (args) => { // Optional authentication return true; } }; const builder = new UssdBuilder(options, rootMenu); ``` ### Session Management Sessions are automatically managed using Redis with the following structure: - **Session Key**: `{prefix}:{session_id}` - **Session Data**: Stores navigation path, current menu, and custom data - **TTL**: Configurable time-to-live for session expiration ## 💡 Usage Examples ### Render Menu (Display Options) ```typescript // Render menu displays a list of options to the user const renderMenus: iUssdMenu[] = [ { title: 'Account Services', type: 'render', // This will display as an option render: { success_message: 'Select an option:', error_message: 'Invalid selection' } }, { title: 'Transfer Money', type: 'render', render: { success_message: 'Choose transfer type:' } }, { title: 'Buy Airtime', type: 'render', render: { success_message: 'Select amount:' } } ]; const ussdBuilder = new UssdBuilder( options, new UssdMenu('Main Menu\n1. Account Services\n2. Transfer Money\n3. Buy Airtime', renderMenus) ); ``` ### Render Menu with Function Handler ```typescript // When type is 'render' with a handler, the handler controls the response const dynamicRenderMenu: iUssdMenu = { title: 'Account List', type: 'render', handler_type: 'function', handler: async (args) => { // Dynamically generate menu options const accounts = await getUserAccounts(args.msisdn); const menuOptions = accounts.map((acc, idx) => `${idx + 1}. ${acc.name} - ${acc.balance}` ).join('\n'); // Handler returns the complete response return { message: `Your Accounts:\n${menuOptions}\n\nSelect account:`, continue_session: true }; } // No render object needed when handler is provided }; ``` ### Basic Menu ```typescript const bankingMenus: iUssdMenu[] = [ { title: 'Check Balance', type: 'handler', handler_type: 'function', handler: async (args) => { const balance = await getBalance(args.msisdn); return `Your balance: $${balance}`; } }, { title: 'Transfer Funds', type: 'handler', handler_type: 'path', handler_name_in_destination: 'root_menu', handler: './handlers/transfer.js' }, { title: 'Mini Statement', type: 'handler', handler_type: 'function', handler: async (args) => { const statement = await getStatement(args.msisdn); return statement; } } ]; const ussdBuilder = new UssdBuilder( options, new UssdMenu('Welcome to Banking Services', bankingMenus) ); ``` ### Dynamic Menus ```typescript // Dynamically create menus based on user data const createDynamicMenu = async (userPhone: string) => { const user = await getUserData(userPhone); const menuItems: iUssdMenu[] = []; // Add different menus based on user status if (user.isRegistered) { menuItems.push({ title: 'My Account', type: 'handler', handler_type: 'path', handler_name_in_destination: 'root_menu', handler: './handlers/account.js' }); } else { menuItems.push({ title: 'Register', type: 'handler', handler_type: 'path', handler_name_in_destination: 'root_menu', handler: './handlers/registration.js' }); } return new UssdMenu('Welcome', menuItems); }; // Use in request handler app.post('/ussd', async (req, res) => { const rootMenu = await createDynamicMenu(req.body.msisdn); const builder = new UssdBuilder(options, rootMenu); await builder.pipe<'mtn'>(req.body, (response) => res.json(response)); }); ``` ### Proxy Menu (External Service Integration) **New in v2.4+**: Enhanced proxy support with automatic network detection and flexible request formats. #### Basic Proxy Configuration ```typescript // Simple proxy to external service const basicProxy: iUssdMenu = { title: 'Loan Services', type: 'proxy', proxy_config: { target_url: 'https://loan-service.example.com/ussd', timeout: 25000, headers: { 'API-Key': process.env.LOAN_API_KEY } } }; ``` #### Telco-Specific Endpoints (Default Behavior) ```typescript // Forward requests in original telco format to telco-specific endpoints const telcoSpecificProxy: iUssdMenu = { title: 'Payment Service', type: 'proxy', proxy_config: { target_url: 'https://payment.example.com/v1/ussd/mtn', // MTN-specific endpoint // forward_original_format: true (default) // Forwards MTN request format directly to the endpoint } }; ``` #### Unified Endpoint with Network Detection ```typescript // Use single endpoint for all networks with automatic network detection const unifiedProxy: iUssdMenu = { title: 'Unified Service', type: 'proxy', proxy_config: { target_url: 'https://api.example.com/ussd', // Single endpoint for all networks forward_original_format: false, // Use normalized format // Network is automatically detected from phone number } }; // The proxy sends normalized request with network field: // { // msisdn: '0241234567', // session_id: 'session-123', // user_input: '1', // service_code: '*123#', // is_new_request: false, // provider: 'mtn', // network: 'MTN', // Auto-detected from phone number prefix // session_data: {...}, // original_request: {...} // } ``` #### Advanced Proxy with Transformations ```typescript const advancedProxy: iUssdMenu = { title: 'Insurance Portal', type: 'proxy', proxy_config: { target_url: 'https://insurance.example.com/ussd', forward_original_format: false, timeout: 20000, retry_attempts: 2, retry_delay: 1500, session_bridge: true, // Share session state headers: { 'Authorization': `Bearer ${process.env.TOKEN}`, 'X-Partner-ID': 'partner-123' }, // Custom request transformation transform_request: (data) => ({ phone: data.msisdn, network: data.network, // Includes auto-detected network session: data.session_id, input: data.user_input, metadata: { provider: data.provider, timestamp: Date.now() } }), // Custom response transformation transform_response: (response) => ({ message: response.text, require_feedback: response.continue }) } }; ``` #### Custom Proxy Handler ```typescript // Use custom function instead of HTTP request const customProxy: iUssdMenu = { title: 'Custom Service', type: 'proxy', proxy_config: { proxy_handler: async (builder, user_input) => { // Custom logic here const result = await customServiceCall(builder.session, user_input); return { message: result.display, require_feedback: result.needsInput }; } } }; ``` #### Network Detection The library automatically detects the network from Ghana phone numbers: - **MTN**: 024, 054, 055, 059, 025 - **Telecel**: 020, 050 - **AirtelTigo**: 026, 056, 027, 057, 028 ```typescript // Network detection works with various formats getNetworkFromPhoneNumber('0241234567'); // Returns: 'MTN' getNetworkFromPhoneNumber('233241234567'); // Returns: 'MTN' getNetworkFromPhoneNumber('+233241234567'); // Returns: 'MTN' getNetworkFromPhoneNumber('0201234567'); // Returns: 'TELECEL' getNetworkFromPhoneNumber('0561234567'); // Returns: 'AIRTEL-TIGO' ``` // Combine local and proxy menus const mainMenus: iUssdMenu[] = [ { title: 'My Account', type: 'handler', handler_type: 'path', handler: './handlers/account.js' }, ...proxyMenus // External services appear as regular menu items ]; const ussdBuilder = new UssdBuilder( options, new UssdMenu('Select Service:', mainMenus) ); ``` ### Handler Menus ```typescript // Simple handler const balanceHandler = new UssdMenu('check_balance', async (args) => { const balance = await getBalance(args.msisdn); return `Your balance: $${balance}\n\nPress 0 to go back`; }); // Handler with module path const complexHandler = new UssdMenu('process_transfer', './handlers/transfer.js'); builder.register_menu(balanceHandler); builder.register_menu(complexHandler); ``` ### Paginated Menus Pagination is automatic when menu items exceed the configured limit: ```typescript const builder = new UssdBuilder({ redis_url: 'redis://localhost:6379', max_menu_items: 5 // Show 5 items per page }); const largeMenu = new UssdMenu('products', 'Select Product'); // Adding more than 5 items will trigger pagination for (let i = 1; i <= 20; i++) { largeMenu.add_child(`product_${i}`, `Product ${i}`); } ``` ## 📖 API Reference ### UssdBuilder #### Constructor Options ```typescript interface UssdBuilderOptions { redis_url: string; // Redis connection URL session_prefix?: string; // Session key prefix for multi-instance isolation (default: 'ussd') session_ttl?: number; // Session TTL in seconds (default: 120 seconds / 2 minutes) max_menu_items?: number; // Max items per page (default: 5) end_session_text?: string; // Custom end session message } ``` #### Methods | Method | Description | Parameters | Returns | |--------|-------------|------------|---------| | `pipe<T>(args, callback)` | Process USSD request | `args`: provider args, `callback`: response handler | Promise<void> | | `getSession()` | Get current session | - | iUssdSession | | `saveSession()` | Save session to Redis | - | Promise<void> | | `endSession()` | End current session | - | Promise<void> | ### UssdMenu #### Constructor ```typescript new UssdMenu( id: string, title: string | Function | undefined, opts?: { custom_input?: boolean; module_path?: string; } ) ``` #### Menu Item Types | Property | Description | Type | |----------|-------------|------| | `title` | Menu item display text | string | | `type` | Menu type | 'render' \| 'handler' \| 'proxy' | | `handler_type` | Handler type (for handler menus) | 'path' \| 'function' | | `handler` | Handler implementation | string \| Function | | `handler_name_in_destination` | Exported function name in module | string | | `proxy_config` | Proxy configuration (for proxy menus) | ProxyConfig object | ## 🔌 Provider Integration ### MTN ```typescript app.post('/ussd/mtn', async (req, res) => { const ussdArgs = req.body as iMTNUSSDArgs; // Response callback const sendResponse = <iMTNUssdCallback>(response: iMTNUssdCallback) => { return res.json(response); }; // Configure and build menu const options: iUssdMenuOptions = { provider: 'mtn', service_code: '*123#', next_page_key: '#', back_page_key: '0', require_pin: false, redis_connection_url: REDIS_URL, state: { /* custom state */ } }; const menus: iUssdMenu[] = [/* your menus */]; const ussdBuilder = new UssdBuilder( options, new UssdMenu('Welcome', menus) ); await ussdBuilder.pipe<'mtn'>(ussdArgs, sendResponse); }); ``` ### Nalo ```typescript app.post('/ussd/nalo', async (req, res) => { const ussdArgs = req.body as iNaloUSSDArgs; const sendResponse = <iNaloUssdCallback>(response: iNaloUssdCallback) => { return res.json(response); }; const options: iUssdMenuOptions = { provider: 'nalo', service_code: '*123#', next_page_key: '#', back_page_key: '0', require_pin: false, redis_connection_url: REDIS_URL }; const ussdBuilder = new UssdBuilder(options, rootMenu); await ussdBuilder.pipe<'nalo'>(ussdArgs, sendResponse); }); ``` ### Telecel ```typescript app.post('/ussd/telecel', async (req, res) => { const ussdArgs = req.body as iTelecelUssdArgs; const sendResponse = <iTelecelUssdCallBack>(response: iTelecelUssdCallBack) => { return res.json(response); }; const options: iUssdMenuOptions = { provider: 'telecel', service_code: '*123#', next_page_key: '#', back_page_key: '0', require_pin: false, redis_connection_url: REDIS_URL }; const ussdBuilder = new UssdBuilder(options, rootMenu); await ussdBuilder.pipe<'telecel'>(ussdArgs, sendResponse); }); ``` ### Airtel-Tigo ```typescript app.post('/ussd/at', async (req, res) => { const ussdArgs = req.body as iAirtelTigoUssdArgs; const sendResponse = <iAirtelTigoUssdCallBack>(response: iAirtelTigoUssdCallBack) => { return res.json(response); }; const options: iUssdMenuOptions = { provider: 'airtel-tigo', service_code: '*123#', next_page_key: '#', back_page_key: '0', require_pin: false, redis_connection_url: REDIS_URL }; const ussdBuilder = new UssdBuilder(options, rootMenu); await ussdBuilder.pipe<'airtel-tigo'>(ussdArgs, sendResponse); }); ``` ### Cross-Switch ```typescript app.post('/ussd/cross-switch', async (req, res) => { const ussdArgs = req.body as iCrossSwitchUssdArgs; const sendResponse = <iCrossSwitchUssdCallBack>(response: iCrossSwitchUssdCallBack) => { return res.json(response); }; const options: iUssdMenuOptions = { provider: 'cross-switch', service_code: '*123#', next_page_key: '#', back_page_key: '0', require_pin: false, redis_connection_url: REDIS_URL }; const ussdBuilder = new UssdBuilder(options, rootMenu); await ussdBuilder.pipe<'cross-switch'>(ussdArgs, sendResponse); }); ``` ### Custom Provider The library supports custom providers for any USSD gateway not listed above: ```typescript // Custom provider implementation app.post('/ussd/custom', async (req, res) => { const customArgs = { msisdn: req.body.phoneNumber, session_id: req.body.sessionId, msg: req.body.userInput, // Map your custom fields }; const sendResponse = (response) => { // Transform response to your custom format const customResponse = { text: response.message || response.msg, continueSession: response.msgtype !== false, sessionId: response.session_id }; return res.json(customResponse); }; const options: iUssdMenuOptions = { provider: 'custom', service_code: req.body.serviceCode, next_page_key: '#', back_page_key: '0', require_pin: false, redis_connection_url: REDIS_URL, make_provider_response: false, // Handle response formatting manually state: {} }; const menus: iUssdMenu[] = [/* your menus */]; const ussdBuilder = new UssdBuilder( options, new UssdMenu('Welcome', menus) ); await ussdBuilder.pipe<'custom'>(customArgs, sendResponse); }); ``` ## 🚀 Advanced Features ### Session State Management ```typescript // Pass state through options that persists across menus const options: iUssdMenuOptions = { provider: 'mtn', service_code: '*123#', next_page_key: '#', back_page_key: '0', require_pin: false, redis_connection_url: REDIS_URL, state: { user_id: '12345', user_name: 'John Doe', account_type: 'premium', // Any custom data you need } }; // Access state in handlers const menuHandler: iUssdMenu = { title: 'Account Info', type: 'handler', handler_type: 'function', handler: async (args) => { // Access state passed through options const userId = args.state?.user_id; const userName = args.state?.user_name; return `Welcome ${userName}, your ID is ${userId}`; } }; ``` ### Middleware Support ```typescript const options: iUssdMenuOptions = { provider: 'mtn', service_code: '*123#', next_page_key: '#', back_page_key: '0', require_pin: false, redis_connection_url: REDIS_URL, middleware: async (args) => { // Log every request console.log('USSD Request:', args); // Validate user const user = await validateUser(args.msisdn); if (!user) { throw new Error('Unauthorized'); } // Add user to args args.user = user; } }; ``` ### Authentication Handler ```typescript const options: iUssdMenuOptions = { provider: 'mtn', service_code: '*123#', next_page_key: '#', back_page_key: '0', require_pin: true, // Enable PIN requirement redis_connection_url: REDIS_URL, authentication_handler: async (args) => { // Custom authentication logic const isValid = await validateUserPin(args.msisdn, args.input); return isValid; } }; ``` ### Proxy Service Integration #### Proxy Configuration Options | Option | Type | Default | Description | |--------|------|---------|-------------| | `target_url` | string | - | URL of the target USSD service (required unless using proxy_handler) | | `forward_original_format` | boolean | true | Forward telco-specific format or use normalized format | | `timeout` | number | 25000 | Request timeout in milliseconds | | `retry_attempts` | number | 1 | Number of retry attempts for failed requests | | `retry_delay` | number | 1000 | Delay between retries in milliseconds | | `session_bridge` | boolean | true | Share session state with target service | | `headers` | object | {} | Additional HTTP headers to send | | `transform_request` | function | - | Custom request transformation function | | `transform_response` | function | - | Custom response transformation function | | `proxy_handler` | function | - | Custom handler function instead of HTTP request | #### Request Formats ##### 1. Original Telco Format (default) ```typescript // Forwards the exact format from the telco const telcoProxy: iUssdMenu = { title: 'Service', type: 'proxy', proxy_config: { target_url: 'https://api.example.com/v1/ussd/mtn', forward_original_format: true // default } }; // MTN format example: // { // sessionId: 'session-123', // messageType: '1', // msisdn: '233241234567', // ussdString: '1', // serviceCode: '*123#', // cellId: 'cell-id', // imsi: 'imsi-id', // ... // } ``` ##### 2. Normalized Format with Network Detection ```typescript // Sends normalized format with auto-detected network const normalizedProxy: iUssdMenu = { title: 'Service', type: 'proxy', proxy_config: { target_url: 'https://api.example.com/ussd', // Single endpoint forward_original_format: false } }; // Normalized format: // { // msisdn: '0241234567', // session_id: 'session-123', // user_input: '1', // service_code: '*123#', // is_new_request: false, // provider: 'mtn', // network: 'MTN', // Auto-detected from phone number // session_data: {...}, // original_request: {...} // Original telco request // } ``` #### Backend Implementation Examples ##### Single Endpoint with Network Routing ```javascript // Express.js example app.post('/ussd', (req, res) => { const { network, msisdn, user_input, session_id } = req.body; // Route based on detected network switch(network) { case 'MTN': return handleMTN(req, res); case 'TELECEL': return handleTelecel(req, res); case 'AIRTEL-TIGO': return handleAirtelTigo(req, res); default: return handleUnknown(req, res); } }); ``` ##### Separate Telco Endpoints ```javascript // MTN endpoint - receives MTN format app.post('/v1/ussd/mtn', (req, res) => { const { sessionId, msisdn, ussdString, messageType } = req.body; // Handle MTN-specific format }); // Telecel endpoint - receives Telecel format app.post('/v1/ussd/telecel', (req, res) => { const { sessionid, msisdn, msg, type } = req.body; // Handle Telecel-specific format }); ``` #### XML Payload Support The proxy handler automatically preserves XML payloads when forwarding requests to target endpoints. This is particularly important for Telecel and Airtel-Tigo providers which use XML formats. ##### XML Preservation Features - **Automatic Detection**: Content-type headers are automatically detected and preserved - **Raw Body Forwarding**: XML bodies are forwarded as-is without conversion to JSON - **Provider Support**: Full support for Telecel and Airtel-Tigo XML formats - **Response Parsing**: XML responses are properly parsed and converted to the expected format ##### Example: XML Provider Proxy ```typescript // Telecel XML format proxy const telecelProxy: iUssdMenu = { title: 'Telecel Service', type: 'proxy', proxy_config: { target_url: 'https://api.example.com/v1/ussd/telecel', forward_original_format: true // Preserves XML format } }; // When receiving Telecel XML: // <request> // <msisdn>233241234567</msisdn> // <sessionid>session-123</sessionid> // <type>1</type> // <msg>1</msg> // </request> // The XML is forwarded as-is to the target endpoint ``` ##### Example: Airtel-Tigo XML Support ```typescript // Airtel-Tigo XML format proxy const airtelTigoProxy: iUssdMenu = { title: 'Airtel-Tigo Service', type: 'proxy', proxy_config: { target_url: 'https://api.example.com/v1/ussd/airtel-tigo', forward_original_format: true // Preserves XML format } }; // Airtel-Tigo XML with nested structure: // <dataSet> // <param> // <name>sessionId</name> // <value>session-123</value> // </param> // <param> // <name>msisdn</name> // <value>233241234567</value> // </param> // </dataSet> ``` ##### Backend Handling for XML ```javascript // Express.js with XML body parser const xmlParser = require('express-xml-bodyparser'); app.use(xmlParser()); app.post('/v1/ussd/telecel', (req, res) => { // req.body contains parsed XML const { msisdn, sessionid, msg, type } = req.body.request; // Process and return XML response res.set('Content-Type', 'text/xml'); res.send(` <response> <message>Response message</message> <type>1</type> </response> `); }); ``` #### Use Cases - **Third-party Integration**: Connect to external USSD services (loans, insurance, payments) - **Microservices Architecture**: Route to different backend services - **Partner Applications**: Seamlessly integrate partner USSD applications - **Load Balancing**: Distribute requests across multiple backends - **Network Portability**: Handle users who port numbers between networks - **Testing**: Easy testing with different network configurations - **XML Service Integration**: Seamlessly integrate with XML-based USSD services ```typescript // Complete example with all features const advancedProxyMenu: iUssdMenu = { title: 'Payment Gateway', type: 'proxy', proxy_config: { target_url: process.env.PAYMENT_API_URL, forward_original_format: false, // Use normalized format timeout: 25000, retry_attempts: 2, retry_delay: 1000, session_bridge: true, headers: { 'Authorization': `Bearer ${process.env.AUTH_TOKEN}`, 'X-Partner-ID': 'partner-123' }, transform_request: (data) => ({ phoneNumber: data.msisdn, network: data.network, // Includes auto-detected network sessionId: data.session_id, userInput: data.user_input, metadata: { provider: data.provider, detectedNetwork: data.network, timestamp: Date.now() } }), transform_response: (response) => ({ message: response.display_text, require_feedback: response.await_input === true, session_data: response.session_state }) } }; ``` ### Path-based Handler Modules ```typescript // menu-handler.js - External handler module export const root_menu = async (args) => { // Handler logic const userData = await fetchUserData(args.msisdn); return `Welcome ${userData.name}`; }; export const process_payment = async (args) => { // Another handler in same module return 'Payment processed'; }; // Usage in menu definition const menus: iUssdMenu[] = [ { title: 'User Info', type: 'handler', handler_type: 'path', handler_name_in_destination: 'root_menu', // Function to call handler: path.join(__dirname, '/handlers/menu-handler.js') }, { title: 'Process Payment', type: 'handler', handler_type: 'path', handler_name_in_destination: 'process_payment', // Different function handler: path.join(__dirname, '/handlers/menu-handler.js') } ]; ``` ## 📄 License This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. ## 👤 Author ### Mohammed Bashiru - GitHub: [@iammohammedb](https://github.com/MohammedBashiru/advanced-ussd-builder) ## 🙏 Acknowledgments - Redis for reliable session management - TypeScript for type safety - Jest for testing framework - All contributors and users of this library ## 📞 Support - 🌐 Website: [www.bashtech.solutions](https://www.bashtech.solutions) - 📧 Email: <info@bashtech.solutions> - 🐛 Issues: [GitHub Issues](https://github.com/MohammedBashiru/advanced-ussd-builder/issues) - 💬 Discussions: [GitHub Discussions](https://github.com/MohammedBashiru/advanced-ussd-builder/discussions) --- Made with ❤️ by the BashTech Solutions Team