Skip to content

Commit

Permalink
Merge pull request #1 from replete/invalidate-deletions
Browse files Browse the repository at this point in the history
Add cookie deletion functionality
  • Loading branch information
replete authored Jun 21, 2024
2 parents f6b7382 + cc4ab88 commit 53bac3d
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 28 deletions.
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@

#### [View demo](https://replete.github.io/biscuitman)

I didn't like sending 100KB+ for a simple cookie consent solution so I wrote this. It's currently **2.9kB/br or 3.4kB/gz**, including CSS. It's designed to be as small as possible with a good enough featureset for basic cookie consent.
I didn't like sending 100KB+ for a simple cookie consent solution so I wrote this. It's currently **3.1kB/br or 3.6kB/gz**, including CSS. It's designed to be as small as possible with an adequate featureset for basic website cookie consent.

- Stores consent in `localStorage`, exposes in `window.Consent` and through custom events fired on `document`
- Handles consent granulated by custom sections (e.g. essential, performance, analytics...)
- Optionally shows user specific cookie details
- Cookies/localstorage items removed on rejection/invalidation, if cookie details added
- Fully customizable strings so you can serve localized strings if you want
- Overridable localStorage key, consent global
- Simple flat configuration object
Expand Down Expand Up @@ -93,7 +94,8 @@ While you have the option to enable or disable some or all of these cookies, not
analyticsTitle: 'Analytics',
analyticsMessage: 'Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics such as the number of visitors, bounce rate, traffic source, etc.',
// Optionally include details of the cookies in use for a section, add them like a name/value dictionary like so:
// (Optional) Include details of the cookies in use for a section, add them like a name/value dictionary
// NOTE: By default, if these exist, then when when consent is rejected/invalidated, these cookies/localStorage entries will be immediately removed. Wildcards only work at the end of a string.
analyticsCookies: {
'_ga': 'This cookie, set by Google Analytics, computes visitor, session, and campaign data, tracking site usage for analytical reports. It stores information anonymously, assigning a randomly generated number to identify unique visitors',
'_ga_*': 'Google Analytics uses this cookie for storing page view count'
Expand Down Expand Up @@ -151,6 +153,7 @@ The easiest way to see how events work is to view the `console.debug()` calls in
- `biscuitman:inject` => `{el: $Element, parent?: $Element, time: 1718914784624}` script injected to DOM. if parent exists, it's a new tag inserted after a `src` script loaded which also had text content (a 'dependent' script = tidier convenient markup)
- `biscuitman:invalidate` => `{data: {...consentObjectJustDeleted}, time: 1718915128298}` consent invalidated
- `biscuitman:update` => `{data: {...currentConsentObject}, time: 1718914784624}` returns current consent object and time
- `biscuitman:delete` => `{localStorage|cookie: 'cookieName', time: 1718914784624}` fires when consent is rejected or invalidated and cookies/localStorage entries are deleted

You can watch for these events like this:
```js
Expand Down
64 changes: 50 additions & 14 deletions biscuitman.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
((d, w, bm)=>{
((d, w, Object, bm)=>{
const defaults = {
storageKey: 'myconsent',
global: 'Consent',
Expand Down Expand Up @@ -122,11 +122,11 @@
let id = e.target.dataset.id
dispatch('button', {id})
switch (id) {
case 'accept': saveConsent(true); break;
case 'accept': saveConsents(true); break;
case 'close': dialog.close(); break;
case 'settings': openModal(); break;
case 'save': saveConsent(); break;
case 'reject': saveConsent(false); break;
case 'save': saveConsents(); break;
case 'reject': saveConsents(false); break;
}
}

Expand All @@ -153,9 +153,9 @@
console.debug(name, payload);
}

/* Consents & Injection */
/* Data */

function readConsent() {
function readConsents() {
try {
return JSON.parse(localStorage.getItem(o.storageKey))
} catch (err) {
Expand All @@ -165,19 +165,53 @@
}
}

function saveConsent(value) {
function clearStorages() {
const localStores = Object.fromEntries(Object.entries(localStorage))
const cookies = Object.fromEntries(
d.cookie.split('; ').map(cookie => cookie.split('='))
)
const { consentTime, ...consents } = readConsents()

for (let [section, sectionConsent] of Object.entries(consents)) {
if (sectionConsent) continue
let sectionCookieNames = Object.keys(o[`${section}Cookies`] || {})

sectionCookieNames
.filter(name => name.endsWith('*'))
.map(wildcardName => {
Object.keys({...cookies, ...localStores}).map(name => {
if (name.startsWith(wildcardName.slice(0, -1))) sectionCookieNames.push(name)
})
})

for (const name of sectionCookieNames) {
if (cookies[name]) {
d.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`
dispatch('delete',{cookie : name})
}
if (localStores[name]) {
localStorage.removeItem(name)
dispatch('delete',{localStorage : name})
}
}
}
}

function saveConsents(value) {
const willReadValues = value === undefined
w[o.global].consentTime = +new Date()
o.sections.forEach(section => {
if (section === 'essential') return false
let sectionElement = ui.querySelector(`[data-s=${section}]`)
w[o.global][section] = willReadValues
let sectionConsent = willReadValues
? sectionElement.checked
: value
w[o.global][section] = sectionConsent
if (!willReadValues) sectionElement.checked = value
})
localStorage.setItem(o.storageKey, JSON.stringify(w[o.global]))
dispatch('save', {data: w[o.global]})
clearStorages()
insertScripts()
dialog.close()
displayUI(false)
Expand Down Expand Up @@ -208,15 +242,17 @@
});
}



/* Start */

w[o.global] = readConsent() || {}
w[o.global] = readConsents() || {}

// Optional Non-EU auto-consent
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone
const isEuropeTimezone = /^(GMT|UTC)$/.test(tz) || /(Europe|BST|CEST|CET|EET|IST|WEST|WET|GMT-1|GMT-2|UTC+1|UTC+2|UTC+3)/.test(tz)
if (o.acceptNonEU && !isEuropeTimezone) {
saveConsent(true)
saveConsents(true)
displayUI(false)
}

Expand All @@ -232,14 +268,14 @@
// Helper methods
// <a onclick="bmInvalidate()" href="javascript:void(0)">Delete Consent Preferences</a>
w.bmInvalidate = () => {
dispatch('invalidate', {data: readConsent()})
saveConsent(false)
dispatch('invalidate', {data: readConsents()})
saveConsents(false)
localStorage.removeItem(o.storageKey)
displayUI(true)
}
// <a onclick="bmUpdate()" href="javascript:void(0)">Update Consent Preferences</a>
w.bmUpdate = () => {
dispatch('update', {data: readConsent()})
dispatch('update', {data: readConsents()})
openModal()
}
})(document, window, 'biscuitman')
})(document, window, Object, 'biscuitman')
2 changes: 1 addition & 1 deletion dist/biscuitman.min.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 53bac3d

Please sign in to comment.