1/** 2 * Complianz Record of Consent Extension - JavaScript 3 * Listens to Complianz consent events and stores records 4 */ 5 6(function() { 7 'use strict'; 8 9 // Track if consent came from content blocker 10 let contentBlockerConsent = false; 11 12 // Generate or retrieve unique user ID (14 chars, no prefix) 13 function getUserId() { 14 let uid = localStorage.getItem('rocext_uid'); 15 if (!uid || uid.indexOf('user_') === 0) { 16 // Generate new format: 14 case-sensitive alphanumeric characters 17 var chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; 18 uid = ''; 19 for (var i = 0; i < 14; i++) { 20 uid += chars.charAt(Math.floor(Math.random() * chars.length)); 21 } 22 localStorage.setItem('rocext_uid', uid); 23 } 24 return uid; 25 } 26 27 // Convert Complianz categories array to our format (only statistics and marketing) 28 function formatCategories(categoriesArray) { 29 const result = { 30 statistics: 'deny', 31 marketing: 'deny' 32 }; 33 34 if (categoriesArray && Array.isArray(categoriesArray)) { 35 categoriesArray.forEach(function(cat) { 36 // Only process statistics and marketing, ignore functional and preferences 37 if (cat === 'statistics' || cat === 'marketing') { 38 result[cat] = 'allow'; 39 } 40 }); 41 } 42 43 return result; 44 } 45 46 // Check if consent has actually changed 47 function hasConsentChanged(formattedCategories) { 48 const lastConsent = localStorage.getItem('rocext_last_consent'); 49 const currentConsent = JSON.stringify(formattedCategories); 50 51 if (!lastConsent) { 52 return true; 53 } 54 55 return lastConsent !== currentConsent; 56 } 57 58 // Determine action type - read only, no side effects 59 function getActionType() { 60 return localStorage.getItem('rocext_has_consent') ? 'update' : 'grant'; 61 } 62 63 // Update stored consent state after a confirmed successful save 64 function updateStoredConsent(formattedCategories) { 65 localStorage.setItem('rocext_last_consent', JSON.stringify(formattedCategories)); 66 localStorage.setItem('rocext_has_consent', 'true'); 67 } 68 69 // Send consent record to backend 70 function storeConsent(consentData) { 71 // Validate we have at least categories data 72 if (!consentData.categories || !Array.isArray(consentData.categories)) { 73 return; 74 } 75 76 const userId = getUserId(); 77 const formattedCategories = formatCategories(consentData.categories); 78 79 // Skip storage if user denied all cookies (GDPR does not require proof of refusal) 80 if (formattedCategories.statistics === 'deny' && formattedCategories.marketing === 'deny') { 81 return; 82 } 83 84 // Check if consent actually changed 85 if (!hasConsentChanged(formattedCategories)) { 86 return; 87 } 88 89 // Determine consent method: blocker if flagged, otherwise banner 90 const consentMethod = contentBlockerConsent ? 'blocker' : 'banner'; 91 const actionType = getActionType(); 92 93 const payload = { 94 action: 'rocext_custom_store', 95 nonce: rocextROC.nonce, 96 uid: userId, 97 region: consentData.region || '', 98 categories: JSON.stringify(formattedCategories), 99 policy_version: rocextROC.policyVersion || '1.0', 100 consent_method: consentMethod, 101 action_type: actionType 102 }; 103 104 fetch(rocextROC.ajax, { 105 method: 'POST', 106 headers: { 107 'Content-Type': 'application/x-www-form-urlencoded', 108 }, 109 body: new URLSearchParams(payload), 110 credentials: 'same-origin' 111 }) 112 .then(response => { 113 // Check if response is ok 114 if (!response.ok) { 115 throw new Error('HTTP error ' + response.status); 116 } 117 118 // Parse JSON 119 return response.json(); 120 }) 121 .then(data => { 122 if (data.success) { 123 console.log('Consent recorded. Your ID: ' + userId + ' (save this for data deletion requests)'); 124 // Update stored consent state after successful save 125 updateStoredConsent(formattedCategories); 126 // Reset content blocker flag after successful storage 127 contentBlockerConsent = false;
128 } else { 129 console.error('Failed to store consent record:', data); 130 } 131 }) 132 .catch(error => { 133 console.warn('Consent storage attempt cancelled (likely superseded by newer request):', error.message); 134 }); 135 } 136 137 // Debounce function to prevent multiple rapid saves 138 let saveTimeout = null; 139 let pendingCategories = null; 140 141 // Listen for Complianz consent events 142 document.addEventListener('cmplz_fire_categories', function(e) { 143 pendingCategories = { 144 region: e.detail.region || '', 145 categories: e.detail.categories || [], 146 category: e.detail.category || null 147 }; 148 149 if (saveTimeout) { 150 clearTimeout(saveTimeout); 151 } 152 153 saveTimeout = setTimeout(function() { 154 if (pendingCategories) { 155 storeConsent(pendingCategories); 156 pendingCategories = null; 157 } 158 }, 500); 159 }); 160 161 // Listen for content blocker consent (like Google Maps iframe) 162 document.addEventListener('cmplz_enable_category', function(e) { 163 // Set flag that next consent grant is from content blocker 164 contentBlockerConsent = true; 165 166 if (typeof cmplz_categories !== 'undefined' && cmplz_categories.categories) { 167 const consentData = { 168 region: cmplz_categories.region || '', 169 categories: cmplz_categories.categories || [] 170 }; 171 172 storeConsent(consentData); 173 } 174 }); 175 176})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.