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