upskill-event-manager/assets/js/safari-ajax-handler.js
Ben 89872ec998 fix: resolve registration form display and event edit issues
- 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>
2025-08-24 08:27:17 -03:00

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;
});