Add automatic token refresh for browser phone to prevent timeouts
- Implement proactive token refresh 5 minutes before expiry (1-hour tokens) - Add call-safe refresh logic that postpones refresh during active calls - Replace fixed-interval refresh with smart scheduling based on token expiry - Add proper cleanup on page unload to prevent memory leaks - Enhance error handling with retry mechanisms for network failures - Apply to both admin browser phone page and frontend shortcode 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
		@@ -5691,6 +5691,8 @@ class TWP_Admin {
 | 
			
		||||
                var currentCall = null;
 | 
			
		||||
                var callTimer = null;
 | 
			
		||||
                var callStartTime = null;
 | 
			
		||||
                var tokenRefreshTimer = null;
 | 
			
		||||
                var tokenExpiry = null;
 | 
			
		||||
                
 | 
			
		||||
                // Wait for SDK to load
 | 
			
		||||
                function waitForTwilioSDK(callback) {
 | 
			
		||||
@@ -5718,6 +5720,9 @@ class TWP_Admin {
 | 
			
		||||
                            if (response.success) {
 | 
			
		||||
                                $('#browser-phone-error').hide();
 | 
			
		||||
                                setupTwilioDevice(response.data.token);
 | 
			
		||||
                                // Set token expiry and schedule refresh
 | 
			
		||||
                                tokenExpiry = Date.now() + (response.data.expires_in || 3600) * 1000;
 | 
			
		||||
                                scheduleTokenRefresh();
 | 
			
		||||
                            } else {
 | 
			
		||||
                                // WordPress wp_send_json_error sends the error message as response.data
 | 
			
		||||
                                var errorMsg = response.data || response.error || 'Unknown error';
 | 
			
		||||
@@ -5852,18 +5857,65 @@ class TWP_Admin {
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                function refreshToken() {
 | 
			
		||||
                    console.log('Refreshing capability token...');
 | 
			
		||||
                    
 | 
			
		||||
                    // Don't refresh if currently in a call
 | 
			
		||||
                    if (currentCall) {
 | 
			
		||||
                        console.log('Currently in call, postponing token refresh');
 | 
			
		||||
                        setTimeout(refreshToken, 60000); // Retry in 1 minute
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    $.post(ajaxurl, {
 | 
			
		||||
                        action: 'twp_generate_capability_token',
 | 
			
		||||
                        nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
 | 
			
		||||
                    }, function(response) {
 | 
			
		||||
                        if (response.success && device) {
 | 
			
		||||
                            console.log('Token refreshed successfully');
 | 
			
		||||
                            device.updateToken(response.data.token);
 | 
			
		||||
                            // Update token expiry and schedule next refresh
 | 
			
		||||
                            tokenExpiry = Date.now() + (response.data.expires_in || 3600) * 1000;
 | 
			
		||||
                            scheduleTokenRefresh();
 | 
			
		||||
                        } else {
 | 
			
		||||
                            console.error('Failed to refresh token:', response.data);
 | 
			
		||||
                            showError('Failed to refresh connection. Please refresh the page.');
 | 
			
		||||
                        }
 | 
			
		||||
                    }).fail(function() {
 | 
			
		||||
                        console.error('Failed to refresh token');
 | 
			
		||||
                        console.error('Failed to refresh token - network error');
 | 
			
		||||
                        // Retry in 30 seconds
 | 
			
		||||
                        setTimeout(refreshToken, 30000);
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                /**
 | 
			
		||||
                 * Schedule token refresh
 | 
			
		||||
                 * Refreshes token 5 minutes before expiry
 | 
			
		||||
                 */
 | 
			
		||||
                function scheduleTokenRefresh() {
 | 
			
		||||
                    // Clear any existing timer
 | 
			
		||||
                    if (tokenRefreshTimer) {
 | 
			
		||||
                        clearTimeout(tokenRefreshTimer);
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    if (!tokenExpiry) {
 | 
			
		||||
                        console.error('Token expiry time not set');
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    
 | 
			
		||||
                    // Calculate time until refresh (5 minutes before expiry)
 | 
			
		||||
                    var refreshBuffer = 5 * 60 * 1000; // 5 minutes in milliseconds
 | 
			
		||||
                    var timeUntilRefresh = tokenExpiry - Date.now() - refreshBuffer;
 | 
			
		||||
                    
 | 
			
		||||
                    if (timeUntilRefresh <= 0) {
 | 
			
		||||
                        // Token needs refresh immediately
 | 
			
		||||
                        refreshToken();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        // Schedule refresh
 | 
			
		||||
                        console.log('Scheduling token refresh in', Math.round(timeUntilRefresh / 1000), 'seconds');
 | 
			
		||||
                        tokenRefreshTimer = setTimeout(refreshToken, timeUntilRefresh);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                
 | 
			
		||||
                function showError(message) {
 | 
			
		||||
                    $('#browser-phone-error').html('<p><strong>Error:</strong> ' + message + '</p>').show();
 | 
			
		||||
                    $('#phone-status').text('Error').css('color', '#f44336');
 | 
			
		||||
@@ -6005,8 +6057,15 @@ class TWP_Admin {
 | 
			
		||||
                    }, 1000);
 | 
			
		||||
                });
 | 
			
		||||
                
 | 
			
		||||
                // Refresh token every 50 minutes (tokens expire in 1 hour)
 | 
			
		||||
                setInterval(initializeBrowserPhone, 50 * 60 * 1000);
 | 
			
		||||
                // Clean up on page unload
 | 
			
		||||
                $(window).on('beforeunload', function() {
 | 
			
		||||
                    if (tokenRefreshTimer) {
 | 
			
		||||
                        clearTimeout(tokenRefreshTimer);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (device) {
 | 
			
		||||
                        device.destroy();
 | 
			
		||||
                    }
 | 
			
		||||
                });
 | 
			
		||||
                
 | 
			
		||||
                // Mode switching functionality
 | 
			
		||||
                $('input[name="call_mode"]').on('change', function() {
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,8 @@
 | 
			
		||||
    let availableNumbers = [];
 | 
			
		||||
    let userQueues = [];
 | 
			
		||||
    let selectedQueue = null;
 | 
			
		||||
    let tokenRefreshTimer = null;
 | 
			
		||||
    let tokenExpiry = null;
 | 
			
		||||
    
 | 
			
		||||
    // Initialize when document is ready
 | 
			
		||||
    $(document).ready(function() {
 | 
			
		||||
@@ -86,6 +88,9 @@
 | 
			
		||||
                success: function(response) {
 | 
			
		||||
                    if (response.success) {
 | 
			
		||||
                        setupTwilioDevice(response.data.token);
 | 
			
		||||
                        // Set token expiry time (expires in 1 hour)
 | 
			
		||||
                        tokenExpiry = Date.now() + (response.data.expires_in || 3600) * 1000;
 | 
			
		||||
                        scheduleTokenRefresh();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        updateStatus('offline', 'Failed to initialize');
 | 
			
		||||
                        showMessage('Failed to initialize browser phone: ' + (response.data || 'Unknown error'), 'error');
 | 
			
		||||
@@ -112,6 +117,12 @@
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        try {
 | 
			
		||||
            // If device already exists, destroy it first to prevent multiple connections
 | 
			
		||||
            if (twilioDevice) {
 | 
			
		||||
                twilioDevice.destroy();
 | 
			
		||||
                twilioDevice = null;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            twilioDevice = new Twilio.Device(token, {
 | 
			
		||||
                logLevel: 1,
 | 
			
		||||
                answerOnBridge: true
 | 
			
		||||
@@ -120,13 +131,21 @@
 | 
			
		||||
            twilioDevice.on('registered', function() {
 | 
			
		||||
                updateStatus('online', 'Ready');
 | 
			
		||||
                isConnected = true;
 | 
			
		||||
                showMessage('Browser phone ready!', 'success');
 | 
			
		||||
                // Only show success message on initial connection
 | 
			
		||||
                if (!tokenRefreshTimer) {
 | 
			
		||||
                    showMessage('Browser phone ready!', 'success');
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            twilioDevice.on('error', function(error) {
 | 
			
		||||
                console.error('Twilio Device Error:', error);
 | 
			
		||||
                updateStatus('offline', 'Error: ' + error.message);
 | 
			
		||||
                showMessage('Device error: ' + error.message, 'error');
 | 
			
		||||
                
 | 
			
		||||
                // If token expired error, refresh immediately
 | 
			
		||||
                if (error.message && error.message.toLowerCase().includes('token')) {
 | 
			
		||||
                    refreshToken();
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
            
 | 
			
		||||
            twilioDevice.on('incoming', function(call) {
 | 
			
		||||
@@ -641,5 +660,88 @@
 | 
			
		||||
        }
 | 
			
		||||
    }, 30000); // Every 30 seconds
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Schedule token refresh
 | 
			
		||||
     * Refreshes token 5 minutes before expiry
 | 
			
		||||
     */
 | 
			
		||||
    function scheduleTokenRefresh() {
 | 
			
		||||
        // Clear any existing timer
 | 
			
		||||
        if (tokenRefreshTimer) {
 | 
			
		||||
            clearTimeout(tokenRefreshTimer);
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        if (!tokenExpiry) {
 | 
			
		||||
            console.error('Token expiry time not set');
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Calculate time until refresh (5 minutes before expiry)
 | 
			
		||||
        const refreshBuffer = 5 * 60 * 1000; // 5 minutes in milliseconds
 | 
			
		||||
        const timeUntilRefresh = tokenExpiry - Date.now() - refreshBuffer;
 | 
			
		||||
        
 | 
			
		||||
        if (timeUntilRefresh <= 0) {
 | 
			
		||||
            // Token needs refresh immediately
 | 
			
		||||
            refreshToken();
 | 
			
		||||
        } else {
 | 
			
		||||
            // Schedule refresh
 | 
			
		||||
            console.log('Scheduling token refresh in', Math.round(timeUntilRefresh / 1000), 'seconds');
 | 
			
		||||
            tokenRefreshTimer = setTimeout(refreshToken, timeUntilRefresh);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    /**
 | 
			
		||||
     * Refresh the capability token
 | 
			
		||||
     */
 | 
			
		||||
    function refreshToken() {
 | 
			
		||||
        console.log('Refreshing capability token...');
 | 
			
		||||
        
 | 
			
		||||
        // Don't refresh if currently in a call
 | 
			
		||||
        if (currentCall) {
 | 
			
		||||
            console.log('Currently in call, postponing token refresh');
 | 
			
		||||
            // Retry in 1 minute
 | 
			
		||||
            setTimeout(refreshToken, 60000);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        $.ajax({
 | 
			
		||||
            url: twp_frontend_ajax.ajax_url,
 | 
			
		||||
            method: 'POST',
 | 
			
		||||
            data: {
 | 
			
		||||
                action: 'twp_generate_capability_token',
 | 
			
		||||
                nonce: twp_frontend_ajax.nonce
 | 
			
		||||
            },
 | 
			
		||||
            success: function(response) {
 | 
			
		||||
                if (response.success) {
 | 
			
		||||
                    console.log('Token refreshed successfully');
 | 
			
		||||
                    // Update token expiry
 | 
			
		||||
                    tokenExpiry = Date.now() + (response.data.expires_in || 3600) * 1000;
 | 
			
		||||
                    // Update device with new token
 | 
			
		||||
                    setupTwilioDevice(response.data.token);
 | 
			
		||||
                    // Schedule next refresh
 | 
			
		||||
                    scheduleTokenRefresh();
 | 
			
		||||
                } else {
 | 
			
		||||
                    console.error('Failed to refresh token:', response.data);
 | 
			
		||||
                    updateStatus('offline', 'Token refresh failed');
 | 
			
		||||
                    showMessage('Failed to refresh connection. Please refresh the page.', 'error');
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            error: function() {
 | 
			
		||||
                console.error('Failed to refresh token - network error');
 | 
			
		||||
                updateStatus('offline', 'Connection lost');
 | 
			
		||||
                // Retry in 30 seconds
 | 
			
		||||
                setTimeout(refreshToken, 30000);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    // Clean up on page unload
 | 
			
		||||
    $(window).on('beforeunload', function() {
 | 
			
		||||
        if (tokenRefreshTimer) {
 | 
			
		||||
            clearTimeout(tokenRefreshTimer);
 | 
			
		||||
        }
 | 
			
		||||
        if (twilioDevice) {
 | 
			
		||||
            twilioDevice.destroy();
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
})(jQuery);
 | 
			
		||||
		Reference in New Issue
	
	Block a user