Enhance browser phone with real-time updates and audible alerts

- 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 <noreply@anthropic.com>
This commit is contained in:
2025-08-15 09:14:51 -07:00
parent 2cb9b9472d
commit d63eec129a
5 changed files with 223 additions and 104 deletions

View File

@@ -434,10 +434,33 @@
.queue-actions { .queue-actions {
display: flex; display: flex;
gap: 8px; gap: 8px;
flex-wrap: wrap;
} }
.queue-actions .twp-btn { .queue-actions .twp-btn {
flex: 1; 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 { .twp-btn-secondary {

View File

@@ -15,6 +15,11 @@
let selectedQueue = null; let selectedQueue = null;
let tokenRefreshTimer = null; let tokenRefreshTimer = null;
let tokenExpiry = null; let tokenExpiry = null;
let queuePollingTimer = null;
let lastQueueUpdate = {};
let alertSound = null;
let alertInterval = null;
let alertEnabled = false;
// Initialize when document is ready // Initialize when document is ready
$(document).ready(function() { $(document).ready(function() {
@@ -263,6 +268,7 @@
// Accept queue call button // Accept queue call button
$('#twp-accept-queue-call').on('click', function() { $('#twp-accept-queue-call').on('click', function() {
acceptQueueCall(); acceptQueueCall();
stopAlert(); // Stop alert when accepting call
}); });
// Refresh queues button // Refresh queues button
@@ -270,6 +276,11 @@
loadUserQueues(); loadUserQueues();
}); });
// Alert toggle button
$(document).on('click', '#twp-alert-toggle', function() {
toggleAlert();
});
// Queue item selection // Queue item selection
$(document).on('click', '.queue-item', function() { $(document).on('click', '.queue-item', function() {
const queueId = $(this).data('queue-id'); const queueId = $(this).data('queue-id');
@@ -313,6 +324,9 @@
return; return;
} }
// Stop alerts when making a call
stopAlert();
updateCallState('connecting'); updateCallState('connecting');
showCallInfo('Connecting...'); showCallInfo('Connecting...');
@@ -420,12 +434,20 @@
updateCallState('idle'); updateCallState('idle');
hideCallInfo(); hideCallInfo();
$('.twp-browser-phone-container').removeClass('incoming-call'); $('.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 * Load user's assigned queues
*/ */
function loadUserQueues() { function loadUserQueues(silent = false) {
$.ajax({ $.ajax({
url: twp_frontend_ajax.ajax_url, url: twp_frontend_ajax.ajax_url,
method: 'POST', method: 'POST',
@@ -435,15 +457,41 @@
}, },
success: function(response) { success: function(response) {
if (response.success) { if (response.success) {
// Check for new calls in queues
checkForNewCalls(response.data);
userQueues = response.data; userQueues = response.data;
displayQueues(); displayQueues();
} else { } else if (!silent) {
showMessage('Failed to load queues: ' + (response.data || 'Unknown error'), 'error'); showMessage('Failed to load queues: ' + (response.data || 'Unknown error'), 'error');
} }
}, },
error: function() { error: function() {
if (!silent) {
showMessage('Failed to load queues', 'error'); 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;
}); });
} }
@@ -653,16 +701,29 @@
} }
} }
// Periodic status updates // Start queue polling with faster interval
setInterval(function() { startQueuePolling();
if (isConnected) {
loadUserQueues(); // This will refresh all queue data including waiting counts /**
* Start polling for queue updates
*/
function startQueuePolling() {
// Clear any existing timer
if (queuePollingTimer) {
clearInterval(queuePollingTimer);
}
// Poll every 5 seconds for real-time updates
queuePollingTimer = setInterval(function() {
if (isConnected) {
loadUserQueues(true); // Silent update
}
}, 5000); // Every 5 seconds
} }
}, 30000); // Every 30 seconds
/** /**
* Schedule token refresh * Schedule token refresh
* Refreshes token 5 minutes before expiry * Refreshes token 10 minutes before expiry for safety
*/ */
function scheduleTokenRefresh() { function scheduleTokenRefresh() {
// Clear any existing timer // Clear any existing timer
@@ -672,11 +733,17 @@
if (!tokenExpiry) { if (!tokenExpiry) {
console.error('Token expiry time not set'); console.error('Token expiry time not set');
// Retry in 30 seconds if token expiry not set
setTimeout(function() {
if (tokenExpiry) {
scheduleTokenRefresh();
}
}, 30000);
return; return;
} }
// Calculate time until refresh (5 minutes before expiry) // Calculate time until refresh (10 minutes before expiry for extra safety)
const refreshBuffer = 5 * 60 * 1000; // 5 minutes in milliseconds const refreshBuffer = 10 * 60 * 1000; // 10 minutes in milliseconds
const timeUntilRefresh = tokenExpiry - Date.now() - refreshBuffer; const timeUntilRefresh = tokenExpiry - Date.now() - refreshBuffer;
if (timeUntilRefresh <= 0) { 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 // Clean up on page unload
$(window).on('beforeunload', function() { $(window).on('beforeunload', function() {
if (tokenRefreshTimer) { if (tokenRefreshTimer) {
clearTimeout(tokenRefreshTimer); clearTimeout(tokenRefreshTimer);
} }
if (queuePollingTimer) {
clearInterval(queuePollingTimer);
}
if (alertInterval) {
clearInterval(alertInterval);
}
if (twilioDevice) { if (twilioDevice) {
twilioDevice.destroy(); twilioDevice.destroy();
} }
}); });
// Load alert preference on init
loadAlertPreference();
})(jQuery); })(jQuery);

View File

@@ -1,83 +0,0 @@
<?php
/**
* Debug script to see what Twilio SDK actually returns
* Run this to debug phone number issues
*/
// Load WordPress (adjust path as needed)
$wp_load_path = dirname(dirname(dirname(dirname(__FILE__)))) . '/wp-load.php';
if (!file_exists($wp_load_path)) {
echo "WordPress not found. Please adjust the path in this script.\n";
echo "Looking for: $wp_load_path\n";
exit(1);
}
require_once $wp_load_path;
echo "Debug: Twilio Phone Numbers\n";
echo "===========================\n\n";
// Load Twilio SDK
$autoloader = __DIR__ . '/vendor/autoload.php';
if (!file_exists($autoloader)) {
echo "ERROR: SDK not found. Run ./install-twilio-sdk.sh first.\n";
exit(1);
}
require_once $autoloader;
// Get Twilio credentials from WordPress
$account_sid = get_option('twp_twilio_account_sid');
$auth_token = get_option('twp_twilio_auth_token');
if (empty($account_sid) || empty($auth_token)) {
echo "ERROR: Twilio credentials not configured in WordPress.\n";
exit(1);
}
echo "Account SID: " . substr($account_sid, 0, 10) . "...\n";
echo "Auth Token: " . substr($auth_token, 0, 10) . "...\n\n";
try {
// Create Twilio client
$client = new \Twilio\Rest\Client($account_sid, $auth_token);
echo "✅ Twilio client created successfully\n\n";
// Get phone numbers
echo "Fetching phone numbers...\n";
$numbers = $client->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";

View File

@@ -347,12 +347,14 @@ class TWP_Notifications {
* Send webhook request * Send webhook request
*/ */
private static function send_webhook_request($webhook_url, $payload, $service) { private static function send_webhook_request($webhook_url, $payload, $service) {
// Send notification immediately without blocking
$response = wp_remote_post($webhook_url, array( $response = wp_remote_post($webhook_url, array(
'headers' => array( 'headers' => array(
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
), ),
'body' => json_encode($payload), '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)) { if (is_wp_error($response)) {
@@ -360,15 +362,9 @@ class TWP_Notifications {
return false; return false;
} }
$response_code = wp_remote_retrieve_response_code($response); // For non-blocking requests, we can't check response code immediately
if ($response_code >= 200 && $response_code < 300) { error_log("TWP {$service} notification sent (non-blocking)");
error_log("TWP {$service} notification sent successfully");
return true; 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;
}
} }
/** /**

View File

@@ -193,6 +193,9 @@ class TWP_Shortcodes {
<button id="twp-refresh-queues" class="twp-btn twp-btn-secondary"> <button id="twp-refresh-queues" class="twp-btn twp-btn-secondary">
Refresh Refresh
</button> </button>
<button id="twp-alert-toggle" class="twp-btn twp-btn-secondary alert-off">
🔕 Alerts OFF
</button>
</div> </div>
</div> </div>
</div> </div>