/** * Safari ITP-Compatible Storage Strategy * Handles Safari's Intelligent Tracking Prevention restrictions * * Safari ITP limits: * - Cookies expire after 7 days * - localStorage may be cleared after 7 days of non-interaction * - sessionStorage is preserved for session only * * @package HVAC_Community_Events * @since 2.0.0 */ var SafariStorage = (function() { 'use strict'; var features = { localStorage: false, sessionStorage: false, cookies: false }; /** * Test localStorage availability */ function testLocalStorage() { try { var test = '__safari_storage_test__'; localStorage.setItem(test, test); localStorage.removeItem(test); return true; } catch(e) { console.warn('[Safari Storage] localStorage not available:', e); return false; } } /** * Test sessionStorage availability */ function testSessionStorage() { try { var test = '__safari_storage_test__'; sessionStorage.setItem(test, test); sessionStorage.removeItem(test); return true; } catch(e) { console.warn('[Safari Storage] sessionStorage not available:', e); return false; } } /** * Test cookie availability */ function testCookies() { try { // Test if cookies are enabled if (!navigator.cookieEnabled) { return false; } // Try to set a test cookie document.cookie = '__safari_test=1;SameSite=Lax;Secure'; var cookieEnabled = document.cookie.indexOf('__safari_test') !== -1; // Clean up test cookie if (cookieEnabled) { document.cookie = '__safari_test=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;'; } return cookieEnabled; } catch(e) { console.warn('[Safari Storage] Cookies not available:', e); return false; } } /** * Initialize feature detection */ function init() { features.localStorage = testLocalStorage(); features.sessionStorage = testSessionStorage(); features.cookies = testCookies(); console.log('[Safari Storage] Features detected:', features); // Warn if no storage is available if (!features.localStorage && !features.sessionStorage && !features.cookies) { console.error('[Safari Storage] WARNING: No storage methods available!'); } return features; } /** * Set a value with automatic fallback * * @param {string} key - Storage key * @param {*} value - Value to store * @param {number} days - Days until expiration (default 7 for Safari ITP) * @returns {boolean} Success status */ function set(key, value, days) { days = days || 7; // Default to Safari ITP limit var data = { value: value, timestamp: Date.now(), expires: Date.now() + (days * 24 * 60 * 60 * 1000) }; var stringData = JSON.stringify(data); // Try localStorage first (most persistent) if (features.localStorage) { try { localStorage.setItem('hvac_' + key, stringData); console.log('[Safari Storage] Saved to localStorage:', key); return true; } catch(e) { console.warn('[Safari Storage] localStorage failed, trying fallback:', e); } } // Fallback to sessionStorage (session-only but reliable) if (features.sessionStorage) { try { sessionStorage.setItem('hvac_' + key, stringData); console.log('[Safari Storage] Saved to sessionStorage:', key); // Also try to set a cookie for cross-page persistence if (features.cookies) { setCookie('hvac_' + key, stringData, days); } return true; } catch(e) { console.warn('[Safari Storage] sessionStorage failed, trying cookies:', e); } } // Last resort: cookies only if (features.cookies) { setCookie('hvac_' + key, stringData, days); console.log('[Safari Storage] Saved to cookies:', key); return true; } console.error('[Safari Storage] Unable to save data:', key); return false; } /** * Get a value with automatic fallback * * @param {string} key - Storage key * @returns {*} Stored value or null */ function get(key) { var prefixedKey = 'hvac_' + key; var data = null; // Try localStorage first if (features.localStorage) { try { var localData = localStorage.getItem(prefixedKey); if (localData) { data = JSON.parse(localData); // Check if expired (Safari ITP) if (data.expires && Date.now() > data.expires) { console.log('[Safari Storage] Data expired in localStorage:', key); localStorage.removeItem(prefixedKey); data = null; } else { console.log('[Safari Storage] Retrieved from localStorage:', key); return data.value; } } } catch(e) { console.warn('[Safari Storage] localStorage read failed:', e); } } // Try sessionStorage if (features.sessionStorage) { try { var sessionData = sessionStorage.getItem(prefixedKey); if (sessionData) { data = JSON.parse(sessionData); console.log('[Safari Storage] Retrieved from sessionStorage:', key); return data.value; } } catch(e) { console.warn('[Safari Storage] sessionStorage read failed:', e); } } // Try cookies if (features.cookies) { var cookieData = getCookie(prefixedKey); if (cookieData) { try { data = JSON.parse(cookieData); // Check if expired if (data.expires && Date.now() > data.expires) { console.log('[Safari Storage] Data expired in cookie:', key); removeCookie(prefixedKey); return null; } console.log('[Safari Storage] Retrieved from cookies:', key); return data.value; } catch(e) { // Might be a plain string cookie return cookieData; } } } return null; } /** * Remove a value from all storage types * * @param {string} key - Storage key */ function remove(key) { var prefixedKey = 'hvac_' + key; if (features.localStorage) { try { localStorage.removeItem(prefixedKey); } catch(e) { // Silent fail } } if (features.sessionStorage) { try { sessionStorage.removeItem(prefixedKey); } catch(e) { // Silent fail } } if (features.cookies) { removeCookie(prefixedKey); } console.log('[Safari Storage] Removed:', key); } /** * Set a cookie with Safari ITP compatibility * * @param {string} name - Cookie name * @param {string} value - Cookie value * @param {number} days - Days until expiration */ function setCookie(name, value, days) { var expires = ''; if (days) { var date = new Date(); date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); expires = ';expires=' + date.toUTCString(); } // Safari ITP compatible settings // - SameSite=Lax for cross-site GET requests // - Secure for HTTPS only // - Path=/ for site-wide access var isSecure = window.location.protocol === 'https:'; var cookieString = name + '=' + encodeURIComponent(value) + expires + ';path=/;SameSite=Lax' + (isSecure ? ';Secure' : ''); document.cookie = cookieString; } /** * Get a cookie value * * @param {string} name - Cookie name * @returns {string|null} Cookie value or null */ function getCookie(name) { var nameEQ = name + '='; var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = cookies[i]; while (cookie.charAt(0) === ' ') { cookie = cookie.substring(1, cookie.length); } if (cookie.indexOf(nameEQ) === 0) { return decodeURIComponent(cookie.substring(nameEQ.length, cookie.length)); } } return null; } /** * Remove a cookie * * @param {string} name - Cookie name */ function removeCookie(name) { document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;'; } /** * Clear all HVAC storage */ function clearAll() { // Clear localStorage if (features.localStorage) { try { var keys = []; for (var i = 0; i < localStorage.length; i++) { var key = localStorage.key(i); if (key && key.indexOf('hvac_') === 0) { keys.push(key); } } keys.forEach(function(key) { localStorage.removeItem(key); }); } catch(e) { // Silent fail } } // Clear sessionStorage if (features.sessionStorage) { try { var sessionKeys = []; for (var j = 0; j < sessionStorage.length; j++) { var sessionKey = sessionStorage.key(j); if (sessionKey && sessionKey.indexOf('hvac_') === 0) { sessionKeys.push(sessionKey); } } sessionKeys.forEach(function(key) { sessionStorage.removeItem(key); }); } catch(e) { // Silent fail } } // Clear cookies if (features.cookies) { var cookies = document.cookie.split(';'); cookies.forEach(function(cookie) { var eqPos = cookie.indexOf('='); var name = eqPos > -1 ? cookie.substr(0, eqPos).trim() : cookie.trim(); if (name.indexOf('hvac_') === 0) { removeCookie(name); } }); } console.log('[Safari Storage] All storage cleared'); } // Initialize on load init(); // Public API return { init: init, set: set, get: get, remove: remove, clearAll: clearAll, features: features }; })(); // Make globally available window.SafariStorage = SafariStorage;