1'use strict'; 2{ 3 let trackingEnabled = false; 4 let trackingInitialized = false; 5 const trackedTeasers = new Set(); 6 7 const getTeaserData = ( teaser ) => { 8 try { 9 const teaserData = JSON.parse( teaser.dataset.drive ?? '' ); 10 if ( 11 typeof teaserData?.article_id !== 'string' || 12 teaserData.article_id === '' || 13 typeof teaserData.correlation_id !== 'string' || 14 teaserData.correlation_id === '' 15 ) { 16 return null; 17 } 18 19 return teaserData; 20 } catch { 21 return null; 22 } 23 }; 24 25 const sendExposureEvent = ( teaser ) => { 26 if ( ! trackingEnabled || teaser.dataset.exposed !== 'false' ) { 27 return; 28 } 29 30 const teaserData = getTeaserData( teaser ); 31 if ( teaserData && typeof window.snowplow === 'function' ) { 32 try { 33 window.snowplow( 'trackSelfDescribingEvent', { 34 schema: 'iglu:de.drive/recommendation_exposure/jsonschema/1-0-0', 35 data: teaserData, 36 } ); 37 teaser.dataset.exposed = 'true'; 38 intersectionObserver?.unobserve( teaser ); 39 } catch { 40 // Keep the teaser eligible for a later tracking attempt. 41 } 42 } 43 }; 44 45 const onIntersection = ( entries ) => { 46 for ( const entry of entries ) { 47 if ( entry.isIntersecting ) { 48 sendExposureEvent( entry.target ); 49 } 50 } 51 }; 52 53 const intersectionObserver = 54 typeof window.IntersectionObserver === 'function' 55 ? new window.IntersectionObserver( onIntersection, { 56 threshold: 0.9, 57 } ) 58 : null; 59 60 const sendClickEvent = ( teaser ) => { 61 if ( ! trackingEnabled ) { 62 return; 63 } 64 65 const teaserData = getTeaserData( teaser ); 66 if ( teaserData && typeof window.snowplow === 'function' ) { 67 try { 68 window.snowplow( 'trackSelfDescribingEvent', { 69 schema: 'iglu:de.drive/recommendation_click/jsonschema/1-0-0', 70 data: { 71 article_id: teaserData.article_id, 72 correlation_id: teaserData.correlation_id, 73 }, 74 } ); 75 } catch { 76 // Do not interfere with the visitor following the link. 77 } 78 } 79 }; 80 81 const normaliseUrl = ( url ) => { 82 if ( typeof url !== 'string' || url === '' ) { 83 return null; 84 } 85 86 try { 87 const parsedUrl = new URL( url, window.location.href ); 88 return `${ parsedUrl.origin }${ parsedUrl.pathname }${ parsedUrl.search }`; 89 } catch { 90 return null; 91 } 92 }; 93 94 const createTeaserData = ( items, correlationId, teaser ) => { 95 const teaserLink = teaser.querySelector( '.teaser__title-link' ); 96 if ( ! teaserLink ) { 97 return null; 98 } 99 100 const teaserUrl = normaliseUrl( teaserLink.href ); 101 const matchingItem = items.find( 102 ( item ) => 103 item && 104 typeof item === 'object' && 105 normaliseUrl( item.url ) === teaserUrl && 106 typeof item.articleId === 'string' && 107 item.articleId !== '' 108 ); 109 110 if ( ! matchingItem ) { 111 return null; 112 } 113 114 return { 115 correlation_id: correlationId, 116 article_id: matchingItem.articleId, 117 is_plus: 118 matchingItem.isPremium === true || 119 matchingItem.isPremium === 'true', 120 plus_sign_visible: false, 121 }; 122 }; 123 124 const initTracking = () => { 125 if ( trackingInitialized ) { 126 return; 127 } 128 129 trackingInitialized = true; 130 window.addEventListener( 'teasers-appended', ( event ) => { 131 const container = event.target; 132 const items = event.detail?.items; 133 const correlationId = event.detail?.correlationId; 134 135 if ( 136 ! container?.classList?.contains( 'drive-experiment-block' ) || 137 ! Array.isArray( items ) || 138 items.length === 0 || 139 typeof correlationId !== 'string' || 140 correlationId === '' 141 ) { 142 return; 143 } 144 145 container.querySelectorAll( '.teaser' ).forEach( ( teaser ) => { 146 if ( teaser.dataset.driveTrackingInitialized === 'true' ) { 147 return; 148 } 149 150 const teaserData = createTeaserData( 151 items, 152 correlationId, 153 teaser 154 ); 155 if ( ! teaserData ) { 156 return; 157 } 158 159 teaser.dataset.driveTrackingInitialized = 'true'; 160 teaser.dataset.drive = JSON.stringify( teaserData ); 161 teaser.dataset.exposed = 'false'; 162 trackedTeasers.add( teaser ); 163 if ( trackingEnabled ) { 164 intersectionObserver?.observe( teaser ); 165 } 166 167 teaser.querySelectorAll( 'a' ).forEach( ( link ) => 168 link.addEventListener( 'click', () => { 169 sendClickEvent( teaser ); 170 } ) 171 ); 172 } ); 173 } ); 174 }; 175 176 const checkIntegrationAllowed = () => { 177 if ( typeof window.__cmp !== 'function' ) { 178 return; 179 } 180 181 const cmpData = window.__cmp( 'getCMPData' ); 182 const gtmConsent = cmpData?.vendorConsents?.s905 === true; 183 trackingEnabled = gtmConsent; 184 185 if ( gtmConsent ) {
186 trackedTeasers.forEach( ( teaser ) => { 187 if ( teaser.dataset.exposed === 'false' ) { 188 intersectionObserver?.observe( teaser ); 189 } 190 } ); 191 } else { 192 intersectionObserver?.disconnect(); 193 } 194 }; 195 196 const registerConsentListener = () => { 197 if ( typeof window.__cmp !== 'function' ) { 198 return; 199 } 200 201 window.__cmp( 202 'addEventListener', 203 [ 'consent', checkIntegrationAllowed, false ], 204 null 205 ); 206 checkIntegrationAllowed(); 207 }; 208 209 initTracking(); 210 if ( typeof window.__cmp === 'undefined' ) { 211 window.addEventListener( 'cmpEvent_init', registerConsentListener ); 212 } else { 213 registerConsentListener(); 214 } 215}
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.