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 currentCall = null;
|
||||||
var callTimer = null;
|
var callTimer = null;
|
||||||
var callStartTime = null;
|
var callStartTime = null;
|
||||||
|
var tokenRefreshTimer = null;
|
||||||
|
var tokenExpiry = null;
|
||||||
|
|
||||||
// Wait for SDK to load
|
// Wait for SDK to load
|
||||||
function waitForTwilioSDK(callback) {
|
function waitForTwilioSDK(callback) {
|
||||||
@@ -5718,6 +5720,9 @@ class TWP_Admin {
|
|||||||
if (response.success) {
|
if (response.success) {
|
||||||
$('#browser-phone-error').hide();
|
$('#browser-phone-error').hide();
|
||||||
setupTwilioDevice(response.data.token);
|
setupTwilioDevice(response.data.token);
|
||||||
|
// Set token expiry and schedule refresh
|
||||||
|
tokenExpiry = Date.now() + (response.data.expires_in || 3600) * 1000;
|
||||||
|
scheduleTokenRefresh();
|
||||||
} else {
|
} else {
|
||||||
// WordPress wp_send_json_error sends the error message as response.data
|
// WordPress wp_send_json_error sends the error message as response.data
|
||||||
var errorMsg = response.data || response.error || 'Unknown error';
|
var errorMsg = response.data || response.error || 'Unknown error';
|
||||||
@@ -5852,18 +5857,65 @@ class TWP_Admin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refreshToken() {
|
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, {
|
$.post(ajaxurl, {
|
||||||
action: 'twp_generate_capability_token',
|
action: 'twp_generate_capability_token',
|
||||||
nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
|
nonce: '<?php echo wp_create_nonce('twp_ajax_nonce'); ?>'
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
if (response.success && device) {
|
if (response.success && device) {
|
||||||
|
console.log('Token refreshed successfully');
|
||||||
device.updateToken(response.data.token);
|
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() {
|
}).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) {
|
function showError(message) {
|
||||||
$('#browser-phone-error').html('<p><strong>Error:</strong> ' + message + '</p>').show();
|
$('#browser-phone-error').html('<p><strong>Error:</strong> ' + message + '</p>').show();
|
||||||
$('#phone-status').text('Error').css('color', '#f44336');
|
$('#phone-status').text('Error').css('color', '#f44336');
|
||||||
@@ -6005,8 +6057,15 @@ class TWP_Admin {
|
|||||||
}, 1000);
|
}, 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Refresh token every 50 minutes (tokens expire in 1 hour)
|
// Clean up on page unload
|
||||||
setInterval(initializeBrowserPhone, 50 * 60 * 1000);
|
$(window).on('beforeunload', function() {
|
||||||
|
if (tokenRefreshTimer) {
|
||||||
|
clearTimeout(tokenRefreshTimer);
|
||||||
|
}
|
||||||
|
if (device) {
|
||||||
|
device.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Mode switching functionality
|
// Mode switching functionality
|
||||||
$('input[name="call_mode"]').on('change', function() {
|
$('input[name="call_mode"]').on('change', function() {
|
||||||
|
@@ -13,6 +13,8 @@
|
|||||||
let availableNumbers = [];
|
let availableNumbers = [];
|
||||||
let userQueues = [];
|
let userQueues = [];
|
||||||
let selectedQueue = null;
|
let selectedQueue = null;
|
||||||
|
let tokenRefreshTimer = null;
|
||||||
|
let tokenExpiry = null;
|
||||||
|
|
||||||
// Initialize when document is ready
|
// Initialize when document is ready
|
||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
@@ -86,6 +88,9 @@
|
|||||||
success: function(response) {
|
success: function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
setupTwilioDevice(response.data.token);
|
setupTwilioDevice(response.data.token);
|
||||||
|
// Set token expiry time (expires in 1 hour)
|
||||||
|
tokenExpiry = Date.now() + (response.data.expires_in || 3600) * 1000;
|
||||||
|
scheduleTokenRefresh();
|
||||||
} else {
|
} else {
|
||||||
updateStatus('offline', 'Failed to initialize');
|
updateStatus('offline', 'Failed to initialize');
|
||||||
showMessage('Failed to initialize browser phone: ' + (response.data || 'Unknown error'), 'error');
|
showMessage('Failed to initialize browser phone: ' + (response.data || 'Unknown error'), 'error');
|
||||||
@@ -112,6 +117,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// If device already exists, destroy it first to prevent multiple connections
|
||||||
|
if (twilioDevice) {
|
||||||
|
twilioDevice.destroy();
|
||||||
|
twilioDevice = null;
|
||||||
|
}
|
||||||
|
|
||||||
twilioDevice = new Twilio.Device(token, {
|
twilioDevice = new Twilio.Device(token, {
|
||||||
logLevel: 1,
|
logLevel: 1,
|
||||||
answerOnBridge: true
|
answerOnBridge: true
|
||||||
@@ -120,13 +131,21 @@
|
|||||||
twilioDevice.on('registered', function() {
|
twilioDevice.on('registered', function() {
|
||||||
updateStatus('online', 'Ready');
|
updateStatus('online', 'Ready');
|
||||||
isConnected = true;
|
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) {
|
twilioDevice.on('error', function(error) {
|
||||||
console.error('Twilio Device Error:', error);
|
console.error('Twilio Device Error:', error);
|
||||||
updateStatus('offline', 'Error: ' + error.message);
|
updateStatus('offline', 'Error: ' + error.message);
|
||||||
showMessage('Device error: ' + error.message, 'error');
|
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) {
|
twilioDevice.on('incoming', function(call) {
|
||||||
@@ -641,5 +660,88 @@
|
|||||||
}
|
}
|
||||||
}, 30000); // Every 30 seconds
|
}, 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);
|
})(jQuery);
|
Reference in New Issue
Block a user