/** * Feature Detection System * Detects browser capabilities instead of relying on user agent strings * * @package HVAC_Community_Events * @since 2.0.0 */ var HVACFeatureDetection = (function() { 'use strict'; var features = {}; var _hasRunDetection = false; /** * Detect all features */ function detectAll() { if (_hasRunDetection) { return features; } console.log('[Feature Detection] Running capability tests...'); features = { // Storage capabilities localStorage: testLocalStorage(), sessionStorage: testSessionStorage(), cookies: navigator.cookieEnabled || false, indexedDB: testIndexedDB(), // JavaScript features es6: testES6Support(), promises: typeof Promise !== 'undefined', fetch: typeof fetch !== 'undefined', async: testAsyncSupport(), // DOM features mutationObserver: 'MutationObserver' in window, intersectionObserver: 'IntersectionObserver' in window, resizeObserver: 'ResizeObserver' in window, // CSS features cssGrid: testCSSSupport('display', 'grid'), cssFlexbox: testCSSSupport('display', 'flex'), cssVariables: testCSSVariables(), cssTransforms: testCSSSupport('transform', 'translateX(1px)'), cssTransitions: testCSSSupport('transition', 'all 0.3s'), cssFilters: testCSSSupport('filter', 'blur(1px)'), // Media features webGL: testWebGL(), canvas: testCanvas(), svg: testSVG(), webAudio: 'AudioContext' in window || 'webkitAudioContext' in window, // Network features serviceWorker: 'serviceWorker' in navigator, webSockets: 'WebSocket' in window, webRTC: testWebRTC(), // Input features touch: testTouch(), pointer: 'PointerEvent' in window, // Performance features performanceAPI: 'performance' in window, navigationTiming: !!(window.performance && window.performance.timing), // Safari-specific issues safariPrivateBrowsing: testSafariPrivateBrowsing(), safariITP: testSafariITP() }; _hasRunDetection = true; console.log('[Feature Detection] Capabilities detected:', features); return features; } /** * Test localStorage availability */ function testLocalStorage() { try { var test = '__test__'; localStorage.setItem(test, test); localStorage.removeItem(test); return true; } catch(e) { return false; } } /** * Test sessionStorage availability */ function testSessionStorage() { try { var test = '__test__'; sessionStorage.setItem(test, test); sessionStorage.removeItem(test); return true; } catch(e) { return false; } } /** * Test IndexedDB availability */ function testIndexedDB() { try { return !!(window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB); } catch(e) { return false; } } /** * Test ES6 support */ function testES6Support() { try { // Test arrow functions eval('(() => {})'); // Test template literals eval('`test`'); // Test let/const eval('let a = 1; const b = 2;'); // Test destructuring eval('const {a, b} = {a: 1, b: 2}'); return true; } catch(e) { return false; } } /** * Test async/await support */ function testAsyncSupport() { try { eval('(async function() {})'); return true; } catch(e) { return false; } } /** * Test CSS support */ function testCSSSupport(property, value) { // Use CSS.supports if available if (window.CSS && window.CSS.supports) { return CSS.supports(property, value); } // Fallback method var el = document.createElement('div'); el.style.cssText = property + ':' + value; return el.style[property] !== ''; } /** * Test CSS variables support */ function testCSSVariables() { return testCSSSupport('--test', '1px'); } /** * Test WebGL support */ function testWebGL() { try { var canvas = document.createElement('canvas'); return !!(window.WebGLRenderingContext && (canvas.getContext('webgl') || canvas.getContext('experimental-webgl'))); } catch(e) { return false; } } /** * Test Canvas support */ function testCanvas() { var elem = document.createElement('canvas'); return !!(elem.getContext && elem.getContext('2d')); } /** * Test SVG support */ function testSVG() { return !!(document.createElementNS && document.createElementNS('http://www.w3.org/2000/svg', 'svg').createSVGRect); } /** * Test WebRTC support */ function testWebRTC() { return !!(window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection); } /** * Test touch support */ function testTouch() { return ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0); } /** * Test Safari private browsing mode */ function testSafariPrivateBrowsing() { try { // Safari private mode throws quota exceeded immediately localStorage.setItem('__private_test__', '1'); localStorage.removeItem('__private_test__'); return false; } catch(e) { // Check if it's quota exceeded error if (e.code === 22 || e.code === 1014 || e.name === 'QuotaExceededError') { return true; } return false; } } /** * Test Safari ITP restrictions */ function testSafariITP() { // Check if this looks like Safari var isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); if (!isSafari) { return false; } // Safari with ITP has specific storage restrictions // This is a heuristic check try { // Check if third-party cookies are blocked var testCookie = '__itp_test__=1;SameSite=None;Secure'; document.cookie = testCookie; var hasITP = document.cookie.indexOf('__itp_test__') === -1; // Clean up if (!hasITP) { document.cookie = '__itp_test__=;expires=Thu, 01 Jan 1970 00:00:00 UTC;'; } return hasITP; } catch(e) { return false; } } /** * Get feature support level */ function getSupportLevel() { if (!_hasRunDetection) { detectAll(); } var critical = [ 'localStorage', 'sessionStorage', 'cookies', 'es6', 'promises', 'mutationObserver', 'cssFlexbox' ]; var enhanced = [ 'fetch', 'async', 'intersectionObserver', 'cssGrid', 'cssVariables' ]; var allCritical = critical.every(function(feature) { return features[feature]; }); var allEnhanced = enhanced.every(function(feature) { return features[feature]; }); if (!allCritical) { return 'minimal'; } else if (!allEnhanced) { return 'basic'; } else { return 'full'; } } /** * Check if feature is supported */ function isSupported(feature) { if (!_hasRunDetection) { detectAll(); } return !!features[feature]; } /** * Get recommended polyfills */ function getRecommendedPolyfills() { if (!_hasRunDetection) { detectAll(); } var polyfills = []; if (!features.promises) { polyfills.push('promise-polyfill'); } if (!features.fetch) { polyfills.push('whatwg-fetch'); } if (!features.intersectionObserver) { polyfills.push('intersection-observer'); } if (!features.cssVariables) { polyfills.push('css-vars-ponyfill'); } return polyfills; } /** * Initialize and run detection */ function init() { detectAll(); // Add data attributes to body for CSS targeting var body = document.body; if (body) { body.setAttribute('data-feature-level', getSupportLevel()); // Add specific feature flags if (features.safariPrivateBrowsing) { body.setAttribute('data-safari-private', 'true'); } if (features.safariITP) { body.setAttribute('data-safari-itp', 'true'); } if (!features.es6) { body.setAttribute('data-legacy-js', 'true'); } } return features; } // Public API return { init: init, detectAll: detectAll, isSupported: isSupported, getSupportLevel: getSupportLevel, getRecommendedPolyfills: getRecommendedPolyfills, features: function() { return features; } }; })(); // Initialize on DOM ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function() { HVACFeatureDetection.init(); }); } else { HVACFeatureDetection.init(); } // Make globally available window.HVACFeatureDetection = HVACFeatureDetection;