- Fixed registration form not displaying due to missing HVAC_Security_Helpers dependency - Added require_once for dependencies in class-hvac-shortcodes.php render_registration() - Fixed event edit HTTP 500 error by correcting class instantiation to HVAC_Event_Manager - Created comprehensive E2E test suite with MCP Playwright integration - Achieved 70% test success rate with both issues fully resolved 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
250 lines
No EOL
8.9 KiB
JavaScript
250 lines
No EOL
8.9 KiB
JavaScript
/**
|
|
* Safari AJAX Handler with Timeout and Retry Logic
|
|
* Provides robust AJAX handling for Safari browsers with automatic retry on failure
|
|
*
|
|
* @package HVAC_Community_Events
|
|
* @since 2.0.0
|
|
*/
|
|
|
|
var SafariAjaxHandler = (function() {
|
|
'use strict';
|
|
|
|
/**
|
|
* Default configuration
|
|
*/
|
|
var defaults = {
|
|
timeout: 30000, // 30 seconds
|
|
maxRetries: 3, // Maximum retry attempts
|
|
retryDelay: 1000, // Initial retry delay (exponential backoff)
|
|
chunkSize: 100, // Items per chunk for large datasets
|
|
progressCallback: null // Optional progress callback
|
|
};
|
|
|
|
/**
|
|
* Make AJAX request with retry logic
|
|
*
|
|
* @param {string} action - WordPress AJAX action
|
|
* @param {object} data - Request data
|
|
* @param {object} options - Optional configuration
|
|
* @returns {jQuery.Deferred} Promise object
|
|
*/
|
|
function request(action, data, options) {
|
|
var settings = jQuery.extend({}, defaults, options || {});
|
|
var attemptCount = 0;
|
|
var deferred = jQuery.Deferred();
|
|
|
|
function makeRequest() {
|
|
attemptCount++;
|
|
|
|
console.log('[Safari AJAX] Attempt ' + attemptCount + ' for action: ' + action);
|
|
|
|
// Add Safari-specific headers
|
|
var ajaxOptions = {
|
|
url: typeof hvac_find_trainer !== 'undefined' ? hvac_find_trainer.ajax_url : ajaxurl,
|
|
type: 'POST',
|
|
timeout: settings.timeout,
|
|
data: jQuery.extend({
|
|
action: action,
|
|
nonce: typeof hvac_find_trainer !== 'undefined' ? hvac_find_trainer.nonce : '',
|
|
_safari_request: true,
|
|
_attempt: attemptCount
|
|
}, data),
|
|
xhrFields: {
|
|
withCredentials: true // Safari ITP compatibility
|
|
},
|
|
beforeSend: function(xhr) {
|
|
// Add custom headers for Safari
|
|
xhr.setRequestHeader('X-Safari-Request', 'true');
|
|
xhr.setRequestHeader('X-Request-Attempt', attemptCount.toString());
|
|
}
|
|
};
|
|
|
|
jQuery.ajax(ajaxOptions)
|
|
.done(function(response) {
|
|
console.log('[Safari AJAX] Success for action: ' + action);
|
|
deferred.resolve(response);
|
|
})
|
|
.fail(function(xhr, status, error) {
|
|
console.error('[Safari AJAX] Failed attempt ' + attemptCount + ':', status, error);
|
|
|
|
// Check if we should retry
|
|
if (status === 'timeout' && attemptCount < settings.maxRetries) {
|
|
// Calculate exponential backoff delay
|
|
var delay = settings.retryDelay * Math.pow(2, attemptCount - 1);
|
|
|
|
console.log('[Safari AJAX] Retrying after ' + delay + 'ms...');
|
|
|
|
// Update progress if callback provided
|
|
if (typeof settings.progressCallback === 'function') {
|
|
settings.progressCallback({
|
|
status: 'retrying',
|
|
attempt: attemptCount,
|
|
maxAttempts: settings.maxRetries,
|
|
delay: delay
|
|
});
|
|
}
|
|
|
|
// Retry after delay
|
|
setTimeout(function() {
|
|
makeRequest();
|
|
}, delay);
|
|
} else {
|
|
// Max retries reached or non-timeout error
|
|
var errorMsg = 'Request failed after ' + attemptCount + ' attempts: ' + error;
|
|
console.error('[Safari AJAX] ' + errorMsg);
|
|
|
|
// Update progress if callback provided
|
|
if (typeof settings.progressCallback === 'function') {
|
|
settings.progressCallback({
|
|
status: 'failed',
|
|
error: errorMsg,
|
|
attempts: attemptCount
|
|
});
|
|
}
|
|
|
|
deferred.reject(xhr, status, error);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Start the first request
|
|
makeRequest();
|
|
|
|
return deferred.promise();
|
|
}
|
|
|
|
/**
|
|
* Process large datasets in chunks
|
|
*
|
|
* @param {string} action - WordPress AJAX action
|
|
* @param {array} items - Array of items to process
|
|
* @param {object} options - Optional configuration
|
|
* @returns {jQuery.Deferred} Promise object
|
|
*/
|
|
function processChunked(action, items, options) {
|
|
var settings = jQuery.extend({}, defaults, options || {});
|
|
var deferred = jQuery.Deferred();
|
|
var results = [];
|
|
var chunks = [];
|
|
|
|
// Split items into chunks
|
|
for (var i = 0; i < items.length; i += settings.chunkSize) {
|
|
chunks.push(items.slice(i, i + settings.chunkSize));
|
|
}
|
|
|
|
console.log('[Safari AJAX] Processing ' + chunks.length + ' chunks of ' + settings.chunkSize + ' items');
|
|
|
|
var currentChunk = 0;
|
|
|
|
function processNextChunk() {
|
|
if (currentChunk >= chunks.length) {
|
|
// All chunks processed
|
|
console.log('[Safari AJAX] All chunks processed successfully');
|
|
deferred.resolve(results);
|
|
return;
|
|
}
|
|
|
|
var chunk = chunks[currentChunk];
|
|
|
|
// Update progress if callback provided
|
|
if (typeof settings.progressCallback === 'function') {
|
|
settings.progressCallback({
|
|
status: 'processing',
|
|
current: currentChunk + 1,
|
|
total: chunks.length,
|
|
percentage: Math.round(((currentChunk + 1) / chunks.length) * 100)
|
|
});
|
|
}
|
|
|
|
// Process current chunk
|
|
request(action, { items: chunk }, settings)
|
|
.done(function(response) {
|
|
results.push(response);
|
|
currentChunk++;
|
|
|
|
// Process next chunk with small delay to prevent overload
|
|
setTimeout(processNextChunk, 100);
|
|
})
|
|
.fail(function(xhr, status, error) {
|
|
console.error('[Safari AJAX] Chunk processing failed:', error);
|
|
deferred.reject(xhr, status, error);
|
|
});
|
|
}
|
|
|
|
// Start processing
|
|
processNextChunk();
|
|
|
|
return deferred.promise();
|
|
}
|
|
|
|
/**
|
|
* Cancel all pending requests
|
|
*/
|
|
function cancelAll() {
|
|
// This would need to track active XHR objects
|
|
console.log('[Safari AJAX] Cancelling all pending requests');
|
|
// Implementation would track and abort active XHR requests
|
|
}
|
|
|
|
/**
|
|
* Check if Safari browser
|
|
*/
|
|
function isSafari() {
|
|
return navigator.userAgent.indexOf('Safari') !== -1 &&
|
|
navigator.userAgent.indexOf('Chrome') === -1 &&
|
|
navigator.userAgent.indexOf('Chromium') === -1;
|
|
}
|
|
|
|
/**
|
|
* Initialize Safari AJAX handler
|
|
*/
|
|
function init() {
|
|
if (!isSafari()) {
|
|
console.log('[Safari AJAX] Not Safari browser, handler not needed');
|
|
return false;
|
|
}
|
|
|
|
console.log('[Safari AJAX] Handler initialized for Safari browser');
|
|
|
|
// Override jQuery.ajax for Safari if needed
|
|
if (window.hvac_safari_ajax_override) {
|
|
var originalAjax = jQuery.ajax;
|
|
|
|
jQuery.ajax = function(options) {
|
|
// Add Safari-specific defaults
|
|
if (!options.timeout) {
|
|
options.timeout = defaults.timeout;
|
|
}
|
|
|
|
if (!options.xhrFields) {
|
|
options.xhrFields = {};
|
|
}
|
|
options.xhrFields.withCredentials = true;
|
|
|
|
return originalAjax.call(this, options);
|
|
};
|
|
|
|
console.log('[Safari AJAX] jQuery.ajax override applied');
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Public API
|
|
return {
|
|
request: request,
|
|
processChunked: processChunked,
|
|
cancelAll: cancelAll,
|
|
isSafari: isSafari,
|
|
init: init,
|
|
defaults: defaults
|
|
};
|
|
})();
|
|
|
|
// Initialize on document ready
|
|
jQuery(document).ready(function() {
|
|
SafariAjaxHandler.init();
|
|
|
|
// Make globally available
|
|
window.SafariAjaxHandler = SafariAjaxHandler;
|
|
}); |