Add cookie consent widget with analytics tunnel bypass for adblock
- Create cookie-consent.js module: banner show/hide, cookie management, conditional analytics loading - Add cookie banner widget in base.html.twig (accept/refuse buttons) - Analytics script loaded from /stats/ tunnel (bypass adblock) with data-host-url - Add Caddy reverse proxy tunnel /stats/* -> tools-security.esy-web.dev - Add tools-security.esy-web.dev to CSP connect-src - Add 9 JS tests for cookie consent - Revert manual composer.json edit for amazon-mailer (needs composer require) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,13 @@ ticket.e-cosplay.fr {
|
||||
file_server
|
||||
}
|
||||
|
||||
handle_path /stats/* {
|
||||
rewrite * {uri}
|
||||
reverse_proxy https://tools-security.esy-web.dev {
|
||||
header_up Host tools-security.esy-web.dev
|
||||
}
|
||||
}
|
||||
|
||||
@maintenance file /var/www/e-ticket/public/.update
|
||||
handle @maintenance {
|
||||
root * /var/www/e-ticket/public
|
||||
|
||||
@@ -2,9 +2,11 @@ import "./app.scss"
|
||||
import { initMobileMenu } from "./modules/mobile-menu.js"
|
||||
import { initTabs } from "./modules/tabs.js"
|
||||
import { registerEditor } from "./modules/editor.js"
|
||||
import { initCookieConsent } from "./modules/cookie-consent.js"
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initMobileMenu()
|
||||
initTabs()
|
||||
registerEditor()
|
||||
initCookieConsent()
|
||||
})
|
||||
|
||||
67
assets/modules/cookie-consent.js
Normal file
67
assets/modules/cookie-consent.js
Normal file
@@ -0,0 +1,67 @@
|
||||
const COOKIE_NAME = 'e_ticket_consent'
|
||||
const COOKIE_DAYS = 365
|
||||
|
||||
function getCookie(name) {
|
||||
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'))
|
||||
|
||||
return match ? match[2] : null
|
||||
}
|
||||
|
||||
function setCookie(name, value, days) {
|
||||
const date = new Date()
|
||||
date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000)
|
||||
document.cookie = name + '=' + value + ';expires=' + date.toUTCString() + ';path=/;SameSite=Lax;Secure'
|
||||
}
|
||||
|
||||
function loadAnalytics() {
|
||||
if (document.querySelector('script[data-analytics]')) {
|
||||
return
|
||||
}
|
||||
|
||||
const script = document.createElement('script')
|
||||
script.defer = true
|
||||
script.src = '/stats/script.js'
|
||||
script.dataset.websiteId = 'a1f85dd5-741f-4df7-840a-7ef0931ed0cc'
|
||||
script.dataset.hostUrl = '/stats'
|
||||
script.dataset.analytics = '1'
|
||||
document.head.appendChild(script)
|
||||
}
|
||||
|
||||
export function initCookieConsent() {
|
||||
const consent = getCookie(COOKIE_NAME)
|
||||
|
||||
if ('accepted' === consent) {
|
||||
loadAnalytics()
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if ('refused' === consent) {
|
||||
return
|
||||
}
|
||||
|
||||
const banner = document.getElementById('cookie-banner')
|
||||
if (!banner) {
|
||||
return
|
||||
}
|
||||
|
||||
banner.classList.remove('hidden')
|
||||
|
||||
const acceptBtn = document.getElementById('cookie-accept')
|
||||
const refuseBtn = document.getElementById('cookie-refuse')
|
||||
|
||||
if (acceptBtn) {
|
||||
acceptBtn.addEventListener('click', () => {
|
||||
setCookie(COOKIE_NAME, 'accepted', COOKIE_DAYS)
|
||||
banner.classList.add('hidden')
|
||||
loadAnalytics()
|
||||
})
|
||||
}
|
||||
|
||||
if (refuseBtn) {
|
||||
refuseBtn.addEventListener('click', () => {
|
||||
setCookie(COOKIE_NAME, 'refused', COOKIE_DAYS)
|
||||
banner.classList.add('hidden')
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@
|
||||
"phpstan/phpdoc-parser": "^2.3",
|
||||
"stevenmaguire/oauth2-keycloak": "^6.1",
|
||||
"stripe/stripe-php": "*",
|
||||
"symfony/amazon-mailer": "8.0.*",
|
||||
"symfony/asset": "8.0.*",
|
||||
"symfony/console": "8.0.*",
|
||||
"symfony/doctrine-messenger": "8.0.*",
|
||||
|
||||
@@ -44,6 +44,7 @@ nelmio_security:
|
||||
- 'self'
|
||||
- 'https://cloudflareinsights.com'
|
||||
- 'https://static.cloudflareinsights.com'
|
||||
- 'https://tools-security.esy-web.dev'
|
||||
font-src:
|
||||
- 'self'
|
||||
- 'https://cdnjs.cloudflare.com'
|
||||
|
||||
@@ -181,5 +181,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<div id="cookie-banner" class="hidden fixed bottom-0 left-0 right-0 z-50 border-t-4 border-gray-900 bg-white p-4">
|
||||
<div class="max-w-4xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||
<p class="text-sm font-bold text-gray-700">
|
||||
Ce site utilise des cookies pour mesurer l'audience. <a href="{{ path('app_cookies') }}" class="text-indigo-600 hover:underline">En savoir plus</a>
|
||||
</p>
|
||||
<div class="flex gap-2">
|
||||
<button id="cookie-refuse" class="px-4 py-2 border-2 border-gray-900 bg-white font-black uppercase text-xs tracking-widest hover:bg-gray-100 transition-all cursor-pointer">Refuser</button>
|
||||
<button id="cookie-accept" class="px-4 py-2 border-2 border-gray-900 bg-[#fabf04] font-black uppercase text-xs tracking-widest hover:bg-indigo-600 hover:text-white transition-all cursor-pointer">Accepter</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
79
tests/js/cookie-consent.test.js
Normal file
79
tests/js/cookie-consent.test.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { initCookieConsent } from '../../assets/modules/cookie-consent.js'
|
||||
|
||||
describe('initCookieConsent', () => {
|
||||
beforeEach(() => {
|
||||
document.cookie = 'e_ticket_consent=;expires=Thu, 01 Jan 1970 00:00:00 GMT;path=/'
|
||||
document.querySelectorAll('script[data-analytics]').forEach(s => s.remove())
|
||||
document.body.innerHTML = `
|
||||
<div id="cookie-banner" class="hidden">
|
||||
<button id="cookie-accept"></button>
|
||||
<button id="cookie-refuse"></button>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
|
||||
it('shows banner when no consent cookie', () => {
|
||||
initCookieConsent()
|
||||
const banner = document.getElementById('cookie-banner')
|
||||
expect(banner.classList.contains('hidden')).toBe(false)
|
||||
})
|
||||
|
||||
it('hides banner and sets cookie on accept', () => {
|
||||
initCookieConsent()
|
||||
document.getElementById('cookie-accept').click()
|
||||
const banner = document.getElementById('cookie-banner')
|
||||
expect(banner.classList.contains('hidden')).toBe(true)
|
||||
expect(document.cookie).toContain('e_ticket_consent=accepted')
|
||||
})
|
||||
|
||||
it('hides banner and sets cookie on refuse', () => {
|
||||
initCookieConsent()
|
||||
document.getElementById('cookie-refuse').click()
|
||||
const banner = document.getElementById('cookie-banner')
|
||||
expect(banner.classList.contains('hidden')).toBe(true)
|
||||
expect(document.cookie).toContain('e_ticket_consent=refused')
|
||||
})
|
||||
|
||||
it('does not show banner if already accepted', () => {
|
||||
document.cookie = 'e_ticket_consent=accepted;path=/'
|
||||
initCookieConsent()
|
||||
const banner = document.getElementById('cookie-banner')
|
||||
expect(banner.classList.contains('hidden')).toBe(true)
|
||||
})
|
||||
|
||||
it('does not show banner if already refused', () => {
|
||||
document.cookie = 'e_ticket_consent=refused;path=/'
|
||||
initCookieConsent()
|
||||
const banner = document.getElementById('cookie-banner')
|
||||
expect(banner.classList.contains('hidden')).toBe(true)
|
||||
})
|
||||
|
||||
it('does nothing without banner element', () => {
|
||||
document.body.innerHTML = ''
|
||||
expect(() => initCookieConsent()).not.toThrow()
|
||||
})
|
||||
|
||||
it('loads analytics script on accept', () => {
|
||||
initCookieConsent()
|
||||
document.getElementById('cookie-accept').click()
|
||||
const script = document.querySelector('script[data-analytics]')
|
||||
expect(script).not.toBeNull()
|
||||
expect(script.src).toContain('/stats/script.js')
|
||||
expect(script.dataset.websiteId).toBe('a1f85dd5-741f-4df7-840a-7ef0931ed0cc')
|
||||
})
|
||||
|
||||
it('does not load analytics on refuse', () => {
|
||||
initCookieConsent()
|
||||
document.getElementById('cookie-refuse').click()
|
||||
const script = document.querySelector('script[data-analytics]')
|
||||
expect(script).toBeNull()
|
||||
})
|
||||
|
||||
it('loads analytics immediately if already accepted', () => {
|
||||
document.cookie = 'e_ticket_consent=accepted;path=/'
|
||||
initCookieConsent()
|
||||
const script = document.querySelector('script[data-analytics]')
|
||||
expect(script).not.toBeNull()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user