最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - twilio browser hold, add, merge calls - Stack Overflow

programmeradmin3浏览0评论

Currently i have integrated agent(from browser) to client(phone) call integration using twilio js sdk but now i want to integrate hold first call, add new call and merge call feature i have tried everything but no resource or conclusions found.

Following my browser js

// Fetch Twilio token from PHP backend
    async function fetchToken(agentId, conductedFrom) {
        try {
            const response = await fetch(`${crm_url}/twilio-generate-in-browser-call-token.php`, {
                method: 'POST',
                headers: {
                  'Accept': 'application/json',
                  'Content-Type': 'application/x-www-form-urlencoded'
                },
                body: "agentId=" + agentId + "&conductedFrom=" + conductedFrom
            });
            if (!response.ok) {
                throw new Error('Failed to fetch token');
            }
            const data = await response.json();
            return data.token;
        } catch (error) {
            console.error('Error fetching token:', error);
            alert('Failed to fetch token. Please try again.');
            return null;
        }
    }

    // Global variables
    let twilioDevice = null;
    let activeConnection = null;
    let callStartTime = null;
    let timerInterval = null;

    // Initialize Twilio Client
    async function initializeTwilio(agentId, conductedFrom) {
        const token = await fetchToken(agentId, conductedFrom);
        if (!token) {
            console.error("Token generation failed");
            return;
        }

        try {
            twilioDevice = new Twilio.Device(token, {
                codecPreferences: ['opus', 'pcmu'],
                enableRingingState: true,
                debug: true,  // Enable debugging logs
                closeProtection: true // Prevent accidental page close during calls
            });

            // console.log('Twilio Device initialized:', twilioDevice.on);

            twilioDevice.on('registered', () => {
                console.log('Twilio Device is registered and ready to make calls');
            });

            twilioDevice.on('error', (error) => {
                console.error('Twilio.Device Error:', error);
                updateCallStatus('Error: ' + error.message);
            });

            twilioDevice.on('incoming', (connection) => {
                console.log('Incoming call from:', connection.parameters.From);
                updateCallStatus('Incoming call...');
                showIncomingCallModal(connection);
                jQuery("#newCallingDialerModal").modal('hide');
            });

            twilioDevice.on('disconnect', (connection) => {
                console.log('Call disconnected');
                updateCallStatus('Call ended');
                stopTimer();
                activeConnection = null; // Reset the active connection
                jQuery('#newCallingStatusModal').modal('hide'); // Close the modal immediately
                jQuery("#newCallingDialerModal").modal('hide');
                jQuery('#newCallingIncomingCallModal').modal('hide');
            });

            twilioDevice.on('offline', () => {
                console.log('Twilio device is offline');
                jQuery('#newCallingIncomingCallModal').modal('hide');
            });

            // Listen for the 'tokenWillExpire' event there is 1 hr time limit but even for fallback
            twilioDevice.on('tokenWillExpire', async () => {
                refreshTwilioToken(agentId, conductedFrom);
            });


            // Check if registration is successful, and if not, try to explicitly call register()
            await new Promise(resolve => {
                twilioDevice.on('registered', resolve);
                setTimeout(() => {
                    console.log("Device didn't register automatically, trying explicit register...");
                    twilioDevice.register();
                }, 3000); // If not registered within 5 seconds, try explicit register()
            });
        } catch (error) {
            console.error("Error initializing Twilio Device:", error);
        }
    }

    async function updateParentCallStatus(callSid = null, directionFrom = "outgoing") {
        if (callSid == null) {
            console.error('CallSid Needed');
            return;
        }

        try {
            const response = await fetch(`${crm_url}/twilio-current-call-status-callback.php`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
                body: "firstCallUpdateUpdateSid=" + callSid + "&directionFrom=" + directionFrom
            });

            const data = await response.text();
            console.log('Parent call status updated:', data);
        } catch (error) {
            console.error('Error updating parent call status:', error);
        }
    }

    // Initialize Twilio when the page loads
    initializeTwilio(agentId, conductedFrom);

    // Make an outgoing call
    jQuery(document).on('click', '.phone_dialpad_call_btn', async function () {
        const thisElement = jQuery(this);
        const dialpadParent = thisElement.closest("div.phone_dialpad_parent");
        const countryCode = dialpadParent.find('select.phone_dialpad_countrycode_select_val').val();
        const phoneNumber = dialpadParent.find('input.phone_dialpad_number_val').val();

        if (!countryCode) {
            alert('Select country code');
            return;
        }
        if (!phoneNumber || !phoneNumber.match(/^\+?[1-9]\d{1,14}$/)) {
            alert('Please enter a valid phone number');
            return;
        }

        let dialingNumberString = countryCode + phoneNumber;
        const token = await fetchToken(agentId, conductedFrom);
        if (!token) {
            console.log('Failed to fetch token');
            return;
        }

        // Ensure Twilio Device is initialized
        if (!twilioDevice) {
            await initializeTwilio(agentId, conductedFrom);
            if (!twilioDevice) {
                console.error('Twilio Device initialization failed');
                return;
            }
        }
        
        try {
            // Initiate the call
            activeConnection = await twilioDevice.connect({ 
                params: { To: dialingNumberString }
            });

            updateCallStatus('Calling...');
            jQuery('#newCallingStatusModal').modal('show');
            jQuery("#newCallingDialerModal").modal('hide');

            // Timer starts only when the call is accepted
            activeConnection.on('accept', () => {
                console.log('Call connected');
                updateCallStatus('Call in progress...');
                startTimer(); // Start timer only when the call is accepted
                // console.log("Full Call Parameters:", JSON.stringify(activeConnection.parameters, null, 2));
                if (activeConnection.parameters.CallSid) {
                    updateParentCallStatus(activeConnection.parameters.CallSid);
                }
            });

            // Handle call disconnection
            activeConnection.on('disconnect', () => {
                console.log('Call disconnected');
                updateCallStatus('Call ended');
                stopTimer(); // Stop timer immediately
                // console.log("Full Call Parameters:", JSON.stringify(activeConnection.parameters, null, 2));
                if (activeConnection.parameters.CallSid) {
                    updateParentCallStatus(activeConnection.parameters.CallSid);
                }
                activeConnection = null; // Reset the active connection
                jQuery('#newCallingStatusModal').modal('hide'); // Close the modal immediately

            });

        } catch (error) {
            console.error('Twilio Call Error:', error);
            updateCallStatus('Call failed: ' + error.message);
            stopTimer();
        }
    });

    async function refreshTwilioToken(agentId, conductedFrom) {
        console.log('Manually refreshing Twilio token...');
        const newToken = await fetchToken(agentId, conductedFrom);
        if (newToken && twilioDevice) {
            twilioDevice.updateToken(newToken);
            console.log('Token refreshed successfully');
        } else {
            console.error('Failed to refresh Twilio token');
        }
    }

    // Hang up the call
    document.getElementById('hangupButton').addEventListener('click', () => {
        if (activeConnection) {
            activeConnection.disconnect(); // Disconnect the call immediately
            activeConnection = null;
        }
        updateCallStatus('Call ended');
        stopTimer();
        jQuery('#newCallingStatusModal').modal('hide'); // Close the modal immediately
        jQuery('#newCallingIncomingCallModal').modal('hide');
    });

    // Incoming call modal handling
    function showIncomingCallModal(connection) {
        activeConnection = connection;  // Set active connection

        // Show the incoming call modal
        jQuery('#newCallingIncomingCallModal').modal('show');
        document.getElementById('incomingCallNumber').textContent = activeConnection.parameters.From;

        // Accept the call
        document.getElementById('acceptCallButton').onclick = () => {
            activeConnection.accept();
            updateCallStatus('Call in progress...');
            jQuery('#newCallingStatusModal').modal('show');
            jQuery('#newCallingIncomingCallModal').modal('hide');
            startTimer(); // Start timer only when the call is accepted
            // console.log("Full Call Parameters:", JSON.stringify(activeConnection.parameters, null, 2));
            if (activeConnection.parameters.CallSid) {
                updateParentCallStatus(activeConnection.parameters.CallSid, "incoming");
            }
        };

        // Reject the call
        document.getElementById('rejectCallButton').onclick = () => {
            activeConnection.reject();
            jQuery('#newCallingIncomingCallModal').modal('hide');
            updateCallStatus('Call rejected');
            jQuery('#newCallingStatusModal').modal('hide');
            // console.log("Full Call Parameters:", JSON.stringify(activeConnection.parameters, null, 2));
            if (activeConnection.parameters.CallSid) {
                updateParentCallStatus(activeConnection.parameters.CallSid, "incoming");
            }
            activeConnection = null; // Reset active connection after rejection
        };
        
        // Listen for disconnect on the active connection
        activeConnection.on('disconnect', () => {
            console.log('Call disconnected');
            updateCallStatus('Call ended');
            stopTimer();
            // console.log("Full Call Parameters:", JSON.stringify(activeConnection.parameters, null, 2));
            if (activeConnection.parameters.CallSid) {
                updateParentCallStatus(activeConnection.parameters.CallSid, "incoming");
            }
            activeConnection = null; // Reset active connection
            jQuery('#newCallingStatusModal').modal('hide'); // Close call status modal
            jQuery('#newCallingIncomingCallModal').modal('hide'); // Close incoming call modal
        });
    }


    // Update call status in the modal
    function updateCallStatus(status) {
        document.getElementById('callStatus').textContent = status;
    }

    // Start the call timer
    function startTimer() {
        callStartTime = Date.now();
        timerInterval = setInterval(updateTimer, 1000);
    }

    // Stop the call timer
    function stopTimer() {
        clearInterval(timerInterval);
        document.getElementById('callTimer').textContent = '00:00';
    }

    // Update the timer display
    function updateTimer() {
        const elapsedTime = Math.floor((Date.now() - callStartTime) / 1000);
        const minutes = Math.floor(elapsedTime / 60).toString().padStart(2, '0');
        const seconds = (elapsedTime % 60).toString().padStart(2, '0');
        document.getElementById('callTimer').textContent = `${minutes}:${seconds}`;
    }

Following is outgoing call handler which is handled by twiml

// Caller input details from twilio
    $toph = $_REQUEST['To']; // 917797809829
    $fromph = $_REQUEST['From']; // client:2x13612
    $callsid = $_REQUEST['CallSid']; // CAfef253c1b7c1d15bcb1cd7240ce6556c
    $callstat = $_REQUEST['CallStatus']; // ringing
    $twilioMasterNo = getTwilioMasterInfo($con);

    // Twilio call status callback url handling hook file
    $statusCallbackUrl = $site_URL . "twilio-current-call-status-callback.php";

    $qp = $_REQUEST;
    $calldesc = "";
    foreach($qp as $key => $val){
        $out = $key.' == '.$val;
        if ($calldesc == "") {
            $calldesc = $out;
        } else {
            $calldesc = $calldesc . ' && ' . $out;
        }
    }
if ($twilioMasterNo != "" && $leadid != "") {
       
        ?>
        <Response>
            <Dial callerId="<?= htmlspecialchars($twilioMasterNo) ?>" record="true">
                <Number statusCallback="<?=htmlspecialchars($statusCallbackUrl)?>" statusCallbackEvent="initiated ringing answered completed"><?= htmlspecialchars($toph) ?></Number>
            </Dial>
        </Response>
        <?php
    }

Following is my incoming file ml handler

// Caller input details from twilio
    $toph = $_REQUEST['To'];
    $fromph = $_REQUEST['From'];
    $callsid = $_REQUEST['CallSid'];
    $callstat = $_REQUEST['CallStatus'];

    // Twilio call status callback url handling hook file
    $statusCallbackUrl = $site_URL . "twilio-current-call-status-callback.php";
    
    $qp = $_REQUEST;
    $calldesc = "";
    foreach($qp as $key => $val){
        $out = $key.' == '.$val;
        if ($calldesc == "") {
            $calldesc = $out;
        } else {
            $calldesc = $calldesc . ' && ' . $out;
        }
    }
if ($selectedAgent != "" && $leadid!= "") {
        // Create an inbound call record
        $op = create_vtigercallrecord($vclient, $leadid, $agentid, $userid, $leadph, $agentph, $callsid, $calldesc, $callstat, $conduct, $con);
        ?>
        <Response>
            <Dial record="true">  <!-- Enable call recording -->
                <Client statusCallback="<?=htmlspecialchars($statusCallbackUrl)?>" statusCallbackEvent="initiated ringing answered completed"><?=htmlspecialchars($selectedAgent)?></Client> <!-- Replace with your identity -->
            </Dial>
        </Response>
        <?php
    }
发布评论

评论列表(0)

  1. 暂无评论