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