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}
`;
}, '');
}
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
? ''
: `
`;
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 += `
${switchList}
${poweredBy}
`;
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();