From d63eec129a3af2ea703aa5db0716b169d6aa342c Mon Sep 17 00:00:00 2001 From: Josh Knapp Date: Fri, 15 Aug 2025 09:14:51 -0700 Subject: [PATCH] Enhance browser phone with real-time updates and audible alerts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix token expiration: Extend refresh buffer to 10 minutes for reliability - Add real-time queue updates: Reduce polling to 5 seconds for instant feedback - Implement audible alert system: 30-second repeating notifications with user toggle - Optimize Discord/Slack notifications: Non-blocking requests for immediate delivery - Add persistent alert preferences: Toggle button with localStorage integration - Clean up debug file: Remove unused debug-phone-numbers.php 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- assets/css/browser-phone-frontend.css | 23 +++ assets/js/browser-phone-frontend.js | 202 ++++++++++++++++++++++++-- debug-phone-numbers.php | 83 ----------- includes/class-twp-notifications.php | 16 +- includes/class-twp-shortcodes.php | 3 + 5 files changed, 223 insertions(+), 104 deletions(-) delete mode 100644 debug-phone-numbers.php diff --git a/assets/css/browser-phone-frontend.css b/assets/css/browser-phone-frontend.css index 31a121a..474a3db 100644 --- a/assets/css/browser-phone-frontend.css +++ b/assets/css/browser-phone-frontend.css @@ -434,10 +434,33 @@ .queue-actions { display: flex; gap: 8px; + flex-wrap: wrap; } .queue-actions .twp-btn { flex: 1; + min-width: 80px; +} + +/* Alert Toggle Button */ +#twp-alert-toggle { + position: relative; +} + +#twp-alert-toggle.alert-on { + background-color: #28a745 !important; + border-color: #28a745; + color: white; +} + +#twp-alert-toggle.alert-off { + background-color: #6c757d !important; + border-color: #6c757d; + color: white; +} + +#twp-alert-toggle:hover { + opacity: 0.8; } .twp-btn-secondary { diff --git a/assets/js/browser-phone-frontend.js b/assets/js/browser-phone-frontend.js index 83e66d2..1fecf22 100644 --- a/assets/js/browser-phone-frontend.js +++ b/assets/js/browser-phone-frontend.js @@ -15,6 +15,11 @@ let selectedQueue = null; let tokenRefreshTimer = null; let tokenExpiry = null; + let queuePollingTimer = null; + let lastQueueUpdate = {}; + let alertSound = null; + let alertInterval = null; + let alertEnabled = false; // Initialize when document is ready $(document).ready(function() { @@ -263,6 +268,7 @@ // Accept queue call button $('#twp-accept-queue-call').on('click', function() { acceptQueueCall(); + stopAlert(); // Stop alert when accepting call }); // Refresh queues button @@ -270,6 +276,11 @@ loadUserQueues(); }); + // Alert toggle button + $(document).on('click', '#twp-alert-toggle', function() { + toggleAlert(); + }); + // Queue item selection $(document).on('click', '.queue-item', function() { const queueId = $(this).data('queue-id'); @@ -313,6 +324,9 @@ return; } + // Stop alerts when making a call + stopAlert(); + updateCallState('connecting'); showCallInfo('Connecting...'); @@ -420,12 +434,20 @@ updateCallState('idle'); hideCallInfo(); $('.twp-browser-phone-container').removeClass('incoming-call'); + + // Restart alerts if enabled and there are waiting calls + if (alertEnabled) { + const hasWaitingCalls = userQueues.some(q => parseInt(q.current_waiting) > 0); + if (hasWaitingCalls) { + setTimeout(startAlert, 1000); // Small delay to avoid immediate alert + } + } } /** * Load user's assigned queues */ - function loadUserQueues() { + function loadUserQueues(silent = false) { $.ajax({ url: twp_frontend_ajax.ajax_url, method: 'POST', @@ -435,18 +457,44 @@ }, success: function(response) { if (response.success) { + // Check for new calls in queues + checkForNewCalls(response.data); userQueues = response.data; displayQueues(); - } else { + } else if (!silent) { showMessage('Failed to load queues: ' + (response.data || 'Unknown error'), 'error'); } }, error: function() { - showMessage('Failed to load queues', 'error'); + if (!silent) { + showMessage('Failed to load queues', 'error'); + } } }); } + /** + * Check for new calls in queues and trigger alerts + */ + function checkForNewCalls(newQueues) { + newQueues.forEach(function(queue) { + const queueId = queue.id; + const currentWaiting = parseInt(queue.current_waiting) || 0; + const previousWaiting = lastQueueUpdate[queueId] || 0; + + // If waiting count increased, we have new calls + if (currentWaiting > previousWaiting) { + console.log('New call detected in queue:', queue.queue_name); + // Trigger alert if enabled + if (alertEnabled && !currentCall) { + startAlert(); + } + } + + lastQueueUpdate[queueId] = currentWaiting; + }); + } + /** * Display queues in the UI */ @@ -653,16 +701,29 @@ } } - // Periodic status updates - setInterval(function() { - if (isConnected) { - loadUserQueues(); // This will refresh all queue data including waiting counts + // Start queue polling with faster interval + startQueuePolling(); + + /** + * Start polling for queue updates + */ + function startQueuePolling() { + // Clear any existing timer + if (queuePollingTimer) { + clearInterval(queuePollingTimer); } - }, 30000); // Every 30 seconds + + // Poll every 5 seconds for real-time updates + queuePollingTimer = setInterval(function() { + if (isConnected) { + loadUserQueues(true); // Silent update + } + }, 5000); // Every 5 seconds + } /** * Schedule token refresh - * Refreshes token 5 minutes before expiry + * Refreshes token 10 minutes before expiry for safety */ function scheduleTokenRefresh() { // Clear any existing timer @@ -672,11 +733,17 @@ if (!tokenExpiry) { console.error('Token expiry time not set'); + // Retry in 30 seconds if token expiry not set + setTimeout(function() { + if (tokenExpiry) { + scheduleTokenRefresh(); + } + }, 30000); return; } - // Calculate time until refresh (5 minutes before expiry) - const refreshBuffer = 5 * 60 * 1000; // 5 minutes in milliseconds + // Calculate time until refresh (10 minutes before expiry for extra safety) + const refreshBuffer = 10 * 60 * 1000; // 10 minutes in milliseconds const timeUntilRefresh = tokenExpiry - Date.now() - refreshBuffer; if (timeUntilRefresh <= 0) { @@ -734,14 +801,127 @@ }); } + /** + * Initialize alert sound + */ + function initAlertSound() { + // Create audio element for alert sound + alertSound = new Audio(); + alertSound.src = 'data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQAAAAA='; // Simple beep sound + // Use Web Audio API for better sound + const audioContext = new (window.AudioContext || window.webkitAudioContext)(); + + // Create a simple beep sound + function playBeep() { + const oscillator = audioContext.createOscillator(); + const gainNode = audioContext.createGain(); + + oscillator.connect(gainNode); + gainNode.connect(audioContext.destination); + + oscillator.frequency.value = 800; // Frequency in Hz + gainNode.gain.setValueAtTime(0.3, audioContext.currentTime); + gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5); + + oscillator.start(audioContext.currentTime); + oscillator.stop(audioContext.currentTime + 0.5); + } + + return playBeep; + } + + const playAlertSound = initAlertSound(); + + /** + * Start alert for new calls + */ + function startAlert() { + if (!alertEnabled || alertInterval) return; + + // Play initial alert + playAlertSound(); + + // Repeat every 30 seconds + alertInterval = setInterval(function() { + if (alertEnabled && !currentCall) { + playAlertSound(); + } else { + stopAlert(); + } + }, 30000); + } + + /** + * Stop alert + */ + function stopAlert() { + if (alertInterval) { + clearInterval(alertInterval); + alertInterval = null; + } + } + + /** + * Toggle alert on/off + */ + function toggleAlert() { + alertEnabled = !alertEnabled; + localStorage.setItem('twp_alert_enabled', alertEnabled); + + // Update button state + updateAlertButton(); + + if (!alertEnabled) { + stopAlert(); + showMessage('Queue alerts disabled', 'info'); + } else { + showMessage('Queue alerts enabled', 'success'); + // Check if there are waiting calls + const hasWaitingCalls = userQueues.some(q => parseInt(q.current_waiting) > 0); + if (hasWaitingCalls && !currentCall) { + startAlert(); + } + } + } + + /** + * Update alert button UI + */ + function updateAlertButton() { + const $btn = $('#twp-alert-toggle'); + if (alertEnabled) { + $btn.removeClass('alert-off').addClass('alert-on').html('🔔 Alerts ON'); + } else { + $btn.removeClass('alert-on').addClass('alert-off').html('🔕 Alerts OFF'); + } + } + + /** + * Load alert preference from localStorage + */ + function loadAlertPreference() { + const saved = localStorage.getItem('twp_alert_enabled'); + alertEnabled = saved === null ? true : saved === 'true'; + updateAlertButton(); + } + // Clean up on page unload $(window).on('beforeunload', function() { if (tokenRefreshTimer) { clearTimeout(tokenRefreshTimer); } + if (queuePollingTimer) { + clearInterval(queuePollingTimer); + } + if (alertInterval) { + clearInterval(alertInterval); + } if (twilioDevice) { twilioDevice.destroy(); } }); + // Load alert preference on init + loadAlertPreference(); + })(jQuery); \ No newline at end of file diff --git a/debug-phone-numbers.php b/debug-phone-numbers.php deleted file mode 100644 index 77ad3dd..0000000 --- a/debug-phone-numbers.php +++ /dev/null @@ -1,83 +0,0 @@ -incomingPhoneNumbers->read([], 10); - - if (empty($numbers)) { - echo "No phone numbers found in your Twilio account.\n"; - exit(0); - } - - echo "Found " . count($numbers) . " phone number(s):\n\n"; - - foreach ($numbers as $i => $number) { - echo "=== Phone Number " . ($i + 1) . " ===\n"; - echo "SID: " . $number->sid . "\n"; - echo "Phone Number: " . $number->phoneNumber . "\n"; - echo "Friendly Name: " . ($number->friendlyName ?: '[Not set]') . "\n"; - echo "Voice URL: " . ($number->voiceUrl ?: '[Not set]') . "\n"; - echo "SMS URL: " . ($number->smsUrl ?: '[Not set]') . "\n"; - echo "Account SID: " . $number->accountSid . "\n"; - - // Debug capabilities object - echo "\nCapabilities (raw object):\n"; - var_dump($number->capabilities); - - echo "\nCapabilities (properties):\n"; - echo "- Voice: " . ($number->capabilities->voice ? 'YES' : 'NO') . "\n"; - echo "- SMS: " . ($number->capabilities->sms ? 'YES' : 'NO') . "\n"; - echo "- MMS: " . ($number->capabilities->mms ? 'YES' : 'NO') . "\n"; - echo "\n" . str_repeat('-', 40) . "\n\n"; - } - -} catch (Exception $e) { - echo "ERROR: " . $e->getMessage() . "\n"; - echo "Class: " . get_class($e) . "\n"; - exit(1); -} - -echo "Debug complete!\n"; \ No newline at end of file diff --git a/includes/class-twp-notifications.php b/includes/class-twp-notifications.php index 8558bd5..4218dfa 100644 --- a/includes/class-twp-notifications.php +++ b/includes/class-twp-notifications.php @@ -347,12 +347,14 @@ class TWP_Notifications { * Send webhook request */ private static function send_webhook_request($webhook_url, $payload, $service) { + // Send notification immediately without blocking $response = wp_remote_post($webhook_url, array( 'headers' => array( 'Content-Type' => 'application/json', ), 'body' => json_encode($payload), - 'timeout' => 30, + 'timeout' => 10, // Reduce timeout for faster processing + 'blocking' => false, // Non-blocking request for immediate processing )); if (is_wp_error($response)) { @@ -360,15 +362,9 @@ class TWP_Notifications { return false; } - $response_code = wp_remote_retrieve_response_code($response); - if ($response_code >= 200 && $response_code < 300) { - error_log("TWP {$service} notification sent successfully"); - return true; - } else { - error_log("TWP {$service} notification failed with response code: " . $response_code); - error_log("Response body: " . wp_remote_retrieve_body($response)); - return false; - } + // For non-blocking requests, we can't check response code immediately + error_log("TWP {$service} notification sent (non-blocking)"); + return true; } /** diff --git a/includes/class-twp-shortcodes.php b/includes/class-twp-shortcodes.php index feea462..b92f09a 100644 --- a/includes/class-twp-shortcodes.php +++ b/includes/class-twp-shortcodes.php @@ -193,6 +193,9 @@ class TWP_Shortcodes { +