const CC_MODAL_ID = 'cc-modal-cookies-banner'; const CC_PENDING_COOKIES_KEY = 'captainConsentPending'; const CC_COOKIE_MODAL = 'captain_cookie_consent'; const CC_SERVER_URL = 'https://cc-platform-api-prod.fly.dev'; const CC_STANDARD_MODE = 'BANNER'; const CC_STANDARD_MODE_MIDDLE = 'MIDDLE'; const CC_STANDARD_MODE_MINIMAL = 'MINIMAL'; const CC_STANDARD_MODE_SIMPLE_MIDDLE = 'SIMPLE_MIDDLE'; const CC_STANDARD_MODE_BANNER_LINEAL = 'BANNER_LINEAL'; const CC_STANDARD_MODE_ONLY_SETTINGS = 'ONLY_SETTINGS'; const CC_MODES_ALLOWED = ['STRICTLY_NECESSARY_COOKIES', 'UNCLASSIFIED_COOKIES']; const CC_MODAL_ID_SETTINGS = 'cc-modal-cookies-banner-settings'; function toggleActions() { const privacyLabels = document.querySelectorAll('.privacy-label'); // Accordion const toggleAccordion = (e) => { // Toggle chevron e.currentTarget.classList.toggle('active'); // Toggle privacy-description opacity e.currentTarget.parentElement.nextElementSibling .querySelector('p') .classList.toggle('active'); // Toggle collapsible if (e.currentTarget.parentElement.nextElementSibling.style.maxHeight) { e.currentTarget.parentElement.nextElementSibling.style.maxHeight = null; } else { e.currentTarget.parentElement.nextElementSibling.style.maxHeight = e.currentTarget.parentElement.nextElementSibling.scrollHeight + 'px'; } }; privacyLabels.forEach((label) => { label.addEventListener('click', toggleAccordion); }); } function fullHex(hex) { let r = hex.slice(1, 2); let g = hex.slice(2, 3); let b = hex.slice(3, 4); r = parseInt(r + r, 16); g = parseInt(g + g, 16); b = parseInt(b + b, 16); return { r, g, b }; } function hex2rgb(hex) { if (hex.length === 4) { return fullHex(hex); } const r = parseInt(hex.slice(1, 3), 16); const g = parseInt(hex.slice(3, 5), 16); const b = parseInt(hex.slice(5, 7), 16); return { r, g, b }; } function generateSwitchColor(hexColor) { const { r, g, b } = hex2rgb(hexColor); if (r === g && g === b) { const newColor = { r, g, b }; newColor.r = Math.floor(r * 0.5); newColor.g = Math.floor(g * 0.5); newColor.b = Math.floor(b * 0.5); return `${newColor.r}, ${newColor.g}, ${newColor.b}`; } return `${r}, ${g}, ${b}`; } function checkIfAllSwitchesAreOff(bannerDisplays) { if (!bannerDisplays?.length) return false; return (bannerDisplays || []).every(({ display }) => !display); } function displayButtonsByMode(mode, selectionButton) { switch (mode) { case CC_STANDARD_MODE_MINIMAL: return ` ${selectionButton} `; case CC_STANDARD_MODE_MIDDLE: return ` `; case CC_STANDARD_MODE_SIMPLE_MIDDLE: return ` `; case CC_STANDARD_MODE_BANNER_LINEAL: return ` `; default: return ` `; } } function generateBannerGridTemplates(mode) { switch (mode) { case CC_STANDARD_MODE_BANNER_LINEAL: return 'grid-template-columns: 1fr;'; default: return 'grid-template-columns: 1fr;'; } } function generateBannerHeaderGridTemplates1024(mode) { switch (mode) { case CC_STANDARD_MODE_BANNER_LINEAL: return 'grid-template-columns: 80px 1fr 1fr 0.3fr;'; default: return 'grid-template-columns: 80px 1fr 1fr 0.6fr;'; } } function generateBannerHeaderGridTemplates1280(mode) { switch (mode) { case CC_STANDARD_MODE_BANNER_LINEAL: return 'grid-template-columns: 80px 1fr 1fr 1fr 0.3fr'; default: return 'grid-template-columns: 80px 1fr 1fr 1fr 0.6fr'; } } const bannerMode = (colSpan, bannerType) => { const gridTemplates = generateBannerGridTemplates(bannerType); const gridHeaderTemplates1024 = generateBannerHeaderGridTemplates1024(bannerType); const gridHeaderTemplates1280 = generateBannerHeaderGridTemplates1280(bannerType); return ` @media (min-width: 640px) { div.cc-cookie-simple-container_data { padding: var(--tw-size-1); padding-top: var(--tw-size-1); padding-bottom: var(--tw-size-1); } } @media (min-width: 1024px) { div.cc-cookie-simple-container_data_header { align-items: center; ${gridHeaderTemplates1024} gap: 1em; } } @media (min-width: 1280px) { div.cc-cookie-simple-container_data_header { ${gridHeaderTemplates1280} } } @media (min-width: 768px) { div.cc-cookie-simple-container_data_header > div > a > img { width: var(--tw-size-16); } } @media (min-width: 1024px) { div.cc-cookie-simple-content { height: 100%; grid-column: span ${colSpan} / span ${colSpan}; } } @media (min-width: 1280px) { .cc-cookie-footer { grid-column-start: 5; grid-column: span 1 / span 1; } } @media (min-width: 640px) { div.cc-cookie-button-list { grid-template-columns: 1fr; gap: 3px; } .cc-cookie-logo { display: flex; } } @media (min-width: 1280px) { div.cc-cookie-button-list { ${gridTemplates} gap: 3px; } } @media (min-width: 640px) { .cc-cookie-button-list { row-gap: var(--tw-size-0); -moz-column-gap: var(--tw-size-5); column-gap: var(--tw-size-5); } } @media (min-width: 1280px) { .cc-cookie-button-list { row-gap: var(--tw-size-1); -moz-column-gap: var(--tw-size-0); column-gap: var(--tw-size-1); } } `; }; const modalMode = (y, x) => ` @media (max-width: 640px) { div.cc-cookie-simple { width: 100%; ${y}: 0; ${x}: 0; } } `; function generateGridTemplates(mode) { switch (mode) { case CC_STANDARD_MODE_MIDDLE: return 'grid-template-columns: 1fr;'; case CC_STANDARD_MODE_MINIMAL: return 'grid-template-columns: 1fr;'; case CC_STANDARD_MODE_SIMPLE_MIDDLE: return 'grid-template-columns: 1fr 0.8fr 0.8fr;'; default: return 'grid-template-columns: 0.8fr 0.8fr 1fr;'; } } function generateModalWidth(mode) { switch (mode) { case CC_STANDARD_MODE_BANNER_LINEAL: return '100%'; case CC_STANDARD_MODE: return '80%'; default: return '350px'; } } function generateModalPositionY(mode, position, size) { switch (mode) { case CC_STANDARD_MODE_MIDDLE: return `top: 50%; transform: translate(-50%, -50%);`; case CC_STANDARD_MODE_SIMPLE_MIDDLE: return `top: 50%; transform: translate(-50%, -50%);`; default: return `${position}: var(${size});`; } } function generateModalPositionX(mode, position, size) { switch (mode) { case CC_STANDARD_MODE_MIDDLE: return `left: 50%;`; case CC_STANDARD_MODE_SIMPLE_MIDDLE: return `left: 50%;`; default: return `${position}: var(${size});`; } } function addStyles(bannerConfiguration, banner) { const { mode, styles, displayLogo } = bannerConfiguration; const isBannerMode = [ CC_STANDARD_MODE, CC_STANDARD_MODE_BANNER_LINEAL, ].includes(mode); const { r, g, b } = hex2rgb(styles.theme); const stringRGB = `${r} ${g} ${b}`; const switchColor = generateSwitchColor(styles.theme); const width = generateModalWidth(mode); const responsive = isBannerMode ? bannerMode(displayLogo ? '3' : '4', mode) : modalMode(styles.positionY, styles.positionX); const switchCircleSize = '--tw-size-4'; const positionSize = mode === CC_STANDARD_MODE_BANNER_LINEAL ? '--tw-size-0' : '--tw-size-4'; const positionButtonSize = '--tw-size-4'; const positionY = generateModalPositionY( mode, styles.positionY, positionSize, ); const positionX = generateModalPositionX( mode, styles.positionX, positionSize, ); const positionButtonY = `${styles.positionY}: var(${positionButtonSize});`; const positionButtonX = `${styles.positionX}: var(${positionButtonSize});`; const forceDisplayCookieIcon = mode === CC_STANDARD_MODE_ONLY_SETTINGS; const shouldHidden = styles.defaultHidden || forceDisplayCookieIcon || !shouldDisplayBanner(bannerConfiguration, banner); const defaultHidden = shouldHidden ? 'hidden' : 'visible'; const displayCookie = forceDisplayCookieIcon ? 'visible' : 'hidden'; const gridTemplate = generateGridTemplates(mode); const someStyle = ` `; document.head.insertAdjacentHTML('beforeend', someStyle); } function getItemConfiguration(bannerDisplays, id) { const foundItem = (bannerDisplays || []).find( ({ cookieTypeId }) => cookieTypeId === id, ); return ( foundItem || { display: true, checked: false, } ); } function createSwitchList(switchData, bannerDisplays) { return switchData.reduce((accumulator, { key, name, id }) => { const foundItem = getItemConfiguration(bannerDisplays, id); if (key === 'UNCLASSIFIED_COOKIES' || !foundItem?.display) { return accumulator; } const strictlyNecessary = 'STRICTLY_NECESSARY_COOKIES' === key; const defaultValues = foundItem ? `${foundItem.checked ? 'checked' : ''} ${ strictlyNecessary ? 'disabled checked' : '' }` : ''; return ` ${accumulator}

${name.replace('cookies', '')}

`; }, ''); } function addModal(bannerConfiguration, switchData, bannerDisplays, report) { const displayModal = bannerConfiguration.displayLogo; const cookieSettingsStyle = bannerConfiguration.mode === CC_STANDARD_MODE_MINIMAL ? 'full-outlined-button' : ''; const hasImage = !!bannerConfiguration.image; const shouldNotDisplayPartialButton = checkIfAllSwitchesAreOff(bannerDisplays); const selectionButton = shouldNotDisplayPartialButton ? '' : `
Cookie Settings
`; const poweredBy = ` `; const buttonList = displayButtonsByMode( bannerConfiguration.mode, selectionButton, ); const switchList = createSwitchList(switchData, bannerDisplays); const modal = document.createElement('div'); modal.classList.add('captain-compliance-modal-container'); modal.innerHTML += ` `; document.body.appendChild(modal); } async function loadBannerData() { const paramToken = null; const accessToken = paramToken || document.currentScript.getAttribute('access-token'); const response = await fetch( `${CC_SERVER_URL}/banner/banner-token?access-token=${accessToken}`, ); return await response.json(); } async function loadData(scannerId) { const response = await fetch( `${CC_SERVER_URL}/report/find-last-report?scannerId=${scannerId}`, ); return response ? await response.json() : null; } async function loadSwitchData() { const response = await fetch(`${CC_SERVER_URL}/cookies/cookie-types`); return await response.json(); } async function trackBannerLoad(bannerId) { const response = await fetch( `${CC_SERVER_URL}/banner/tracking?id=${bannerId}`, ); return await response.json(); } async function getCookiesFromServer() { const response = await fetch(`${CC_SERVER_URL}/banner/get-cookies`, { method: 'GET', credentials: 'include', // Ensure cookies are included in the request }); return await response.json(); } async function removeFromTheServer(cookieNames) { await fetch(`${CC_SERVER_URL}/banner/remove-cookies`, { method: 'POST', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ cookieNames, }), }); } async function updateStatus(bannerId, status) { await fetch(`${CC_SERVER_URL}/bannerTracking/banner/${bannerId}`, { method: 'PUT', headers: { Accept: 'application/json', 'Content-Type': 'application/json', }, body: JSON.stringify({ status: status, }), }); } function mergeCookieList(cookieList) { const firstPartyValues = Object.keys(cookieList.firstParty); const dataToReturn = firstPartyValues .reduce((accumulator, key) => { if (CC_MODES_ALLOWED.includes(key)) return accumulator; return [ ...accumulator, ...cookieList.firstParty[key].data, ...cookieList.thirdParty[key].data, ]; }, []) .map(({ name, domain, path }) => ({ name, domain, path })); return dataToReturn; } function getCookieTypesToNotRemove(switchData, bannerDisplays) { return switchData.reduce((accumulator, { key, id }) => { const foundItem = getItemConfiguration(bannerDisplays, id); const shouldNotRemoveIt = foundItem.checked && !foundItem.display; const checked = document.getElementById(key)?.checked; if (checked || CC_MODES_ALLOWED.includes(key) || shouldNotRemoveIt) return [...accumulator, key]; return accumulator; }, []); } function removeKeyFromObject(cookieList, listToNotRemove) { const localCookieList = { ...cookieList }; listToNotRemove.forEach((key) => { delete localCookieList.firstParty[key]; delete localCookieList.thirdParty[key]; }); return localCookieList; } function addCookie(suffix) { var now = new Date(); var time = now.getTime(); var expireTime = time + 1000 * 60 * 60 * 24 * 30; now.setTime(expireTime); document.cookie = `${CC_COOKIE_MODAL}_${suffix}=ok; Expires=` + now.toUTCString() + '; Path=/;'; } function closeModal(scannerId) { document.getElementById(CC_MODAL_ID).style.display = 'none'; addCookie(scannerId); } function addClickHandlerByClass(className, cb) { const elements = document.querySelectorAll(`.${className}`); elements.forEach((element) => { element.onclick = function () { cb(); }; }); } function modalActions( cookieList, switchData, scannerId, bannerId, bannerDisplays, configuration, ) { const selectedCookieButton = document.getElementById( 'cc-cookie-simple-button_id', ); const selectedCloseButton = document.getElementById( 'cc-cookie-simple-button-close_id', ); const selectedModalCloseButton = document.getElementById('cc-modal-close-all'); const externalButton = document.getElementById('id-open-settings-cc'); addClickHandlerByClass('cc-modal-logo-footer-compliance', () => window.open('https://www.captaincompliance.com/', '_blank'), ); addClickHandlerByClass('captain-compliance-open-settings', () => { document.getElementById(CC_MODAL_ID).style.display = 'none'; document.getElementById(CC_MODAL_ID_SETTINGS).style.visibility = 'visible'; }); if (externalButton) { externalButton.onclick = function () { document.getElementById(CC_MODAL_ID).style.display = 'none'; document.getElementById(CC_MODAL_ID_SETTINGS).style.visibility = 'visible'; }; } selectedCookieButton.onclick = function () { if (configuration.mode === CC_STANDARD_MODE_ONLY_SETTINGS) { document.getElementById(CC_MODAL_ID_SETTINGS).style.visibility = 'visible'; } else { document.getElementById(CC_MODAL_ID).style.display = 'block'; } selectedCookieButton.style.visibility = 'hidden'; }; selectedCloseButton.onclick = function () { document.getElementById(CC_MODAL_ID).style.display = 'none'; selectedCookieButton.style.visibility = 'visible'; }; selectedModalCloseButton.onclick = function () { const cookieRemoved = isCookieRemoved(`${CC_COOKIE_MODAL}_${scannerId}`); if (cookieRemoved) { document.getElementById(CC_MODAL_ID).style.display = 'block'; } if (configuration.mode === CC_STANDARD_MODE_ONLY_SETTINGS) { selectedCookieButton.style.visibility = 'visible'; } document.getElementById(CC_MODAL_ID_SETTINGS).style.visibility = 'hidden'; }; const selectionButton = document.getElementById('cc-modal-allow-selection'); const allowAllButton = document.getElementById('cc-modal-allow-all'); const rejectAllButton = document.getElementById('cc-modal-reject-all'); if (allowAllButton) { allowAllButton.onclick = function () { closeModal(scannerId); updateStatus(bannerId, 'ALLOWED'); }; } if (selectionButton) { selectionButton.onclick = function () { closeModal(scannerId); updateStatus(bannerId, 'PARTIALLY_ALLOWED'); const listToNotRemove = getCookieTypesToNotRemove( switchData, bannerDisplays, ); document.getElementById(CC_MODAL_ID_SETTINGS).style.visibility = 'hidden'; const listUpdated = removeKeyFromObject(cookieList, listToNotRemove); reject(mergeCookieList(listUpdated)); }; } if (rejectAllButton) { rejectAllButton.onclick = function () { closeModal(scannerId); updateStatus(bannerId, 'REJECTED'); reject(mergeCookieList(cookieList)); }; } } async function removeCookiesOnList(cookieList) { const regex = /captain_cookie_consent_\d+/; const cookies = document.cookie.split(';'); const cookiesServer = await getCookiesFromServer(); const cookiesServerArray = cookiesServer.cookies ? Object.keys(cookiesServer.cookies) : []; const fromServer = []; cookieList.forEach((cookieItem) => { [...cookies, ...cookiesServerArray].forEach((cookie) => { const [name] = cookie.trim().split('='); if (name.startsWith(cookieItem.name) && !regex.test(name)) { document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:01 GMT;`; document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/;`; document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/; domain=${cookieItem.domain}`; document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/; domain=${cookieItem.domain}; secure`; document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:01 GMT; path=/; domain=${cookieItem.domain}; SameSite=None; Secure`; fromServer.push(name); const cookieRemoved = isCookieRemoved(name); if (!cookieRemoved) { let date = new Date(); date.setTime(date.getTime() + -1 * 24 * 60 * 60 * 1000); const expires = '; expires=' + date.toGMTString(); document.cookie = `${name}=${expires}; path=/;`; } } }); }); removeFromTheServer(fromServer); } function checkAndRemoveMissingCookies() { const storedPendingCookies = localStorage.getItem(CC_PENDING_COOKIES_KEY); const pendingCookies = storedPendingCookies ? JSON.parse(storedPendingCookies) : []; removeCookiesOnList(pendingCookies); } function isCookieRemoved(cookieName) { return document.cookie.split(';').every((cookie) => { return cookie.trim().startsWith(`${cookieName}=`) === false; }); } function reject(cookieList) { removeCookiesOnList(cookieList); localStorage.setItem(CC_PENDING_COOKIES_KEY, JSON.stringify(cookieList)); } function rejectPassive(cookieList) { console.log('cookieList:', cookieList); removeCookiesOnList(cookieList); } function shouldDisplayBanner(bannerConfiguration, banner) { if ( !!bannerConfiguration && !bannerConfiguration.active && !bannerConfiguration.region.isGlobal ) { return false; } const cookieRemoved = isCookieRemoved( `${CC_COOKIE_MODAL}_${banner.scannerId}`, ); if (!cookieRemoved) return false; return true; } function getDomain() { return window.location.hostname.replace('www.', ''); } function getDomainFromString(urlString) { try { const url = new URL(urlString); return url.hostname.replace('www.', ''); } catch (error) { return 'Invalid URL'; } } async function renderModal() { const data = await loadBannerData(); const currentDomain = getDomain(); const expectedDomain = getDomainFromString(data.banner.scanner.domain); if (currentDomain !== expectedDomain) { console.log( '%cSORRY THIS IS NOT THE ALLOWED DOMAIN', 'color: red; font-size: 20px', ); return; } const { bannerConfiguration, bannerDisplays, banner, report } = data; const bannerReport = await loadData(banner.scannerId); if (bannerReport) { rejectPassive(mergeCookieList(bannerReport.reportInformation.cookies)); const switchData = await loadSwitchData(); trackBannerLoad(banner.id); addStyles(bannerConfiguration, banner); addModal(bannerConfiguration, switchData, bannerDisplays, report); modalActions( bannerReport.reportInformation.cookies, switchData, banner.scannerId, banner.id, bannerDisplays, bannerConfiguration, ); toggleActions(); } } (function () { const originalPushState = history.pushState; const originalReplaceState = history.replaceState; function onRouteChange(url) { console.log(`Navigated to: ${url}`); checkAndRemoveMissingCookies(); } history.pushState = function (...args) { originalPushState.apply(this, args); // Call the original method onRouteChange(window.location.href); // Trigger the route change handler }; history.replaceState = function (...args) { originalReplaceState.apply(this, args); // Call the original method onRouteChange(window.location.href); // Trigger the route change handler }; window.addEventListener('popstate', function () { onRouteChange(window.location.href); }); })(); (function () { function onContentLoaded() { console.log('All content loaded successfully.'); checkAndRemoveMissingCookies(); } if (document.readyState === 'complete') { onContentLoaded(); } else { window.addEventListener('load', onContentLoaded); } console.log('Content load listener initialized.'); })(); window.addEventListener('beforeunload', function () { checkAndRemoveMissingCookies(); }); renderModal();