Skip to content

Commit 53bac3d

Browse files
authored
Merge pull request #1 from replete/invalidate-deletions
Add cookie deletion functionality
2 parents f6b7382 + cc4ab88 commit 53bac3d

7 files changed

Lines changed: 90 additions & 28 deletions

File tree

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33

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

6-
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.
6+
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.
77

88
- Stores consent in `localStorage`, exposes in `window.Consent` and through custom events fired on `document`
99
- Handles consent granulated by custom sections (e.g. essential, performance, analytics...)
1010
- Optionally shows user specific cookie details
11+
- Cookies/localstorage items removed on rejection/invalidation, if cookie details added
1112
- Fully customizable strings so you can serve localized strings if you want
1213
- Overridable localStorage key, consent global
1314
- Simple flat configuration object
@@ -93,7 +94,8 @@ While you have the option to enable or disable some or all of these cookies, not
9394
analyticsTitle: 'Analytics',
9495
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.',
9596
96-
// Optionally include details of the cookies in use for a section, add them like a name/value dictionary like so:
97+
// (Optional) Include details of the cookies in use for a section, add them like a name/value dictionary
98+
// 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.
9799
analyticsCookies: {
98100
'_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',
99101
'_ga_*': 'Google Analytics uses this cookie for storing page view count'
@@ -151,6 +153,7 @@ The easiest way to see how events work is to view the `console.debug()` calls in
151153
- `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)
152154
- `biscuitman:invalidate` => `{data: {...consentObjectJustDeleted}, time: 1718915128298}` consent invalidated
153155
- `biscuitman:update` => `{data: {...currentConsentObject}, time: 1718914784624}` returns current consent object and time
156+
- `biscuitman:delete` => `{localStorage|cookie: 'cookieName', time: 1718914784624}` fires when consent is rejected or invalidated and cookies/localStorage entries are deleted
154157

155158
You can watch for these events like this:
156159
```js

biscuitman.js

Lines changed: 50 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
((d, w, bm)=>{
1+
((d, w, Object, bm)=>{
22
const defaults = {
33
storageKey: 'myconsent',
44
global: 'Consent',
@@ -122,11 +122,11 @@
122122
let id = e.target.dataset.id
123123
dispatch('button', {id})
124124
switch (id) {
125-
case 'accept': saveConsent(true); break;
125+
case 'accept': saveConsents(true); break;
126126
case 'close': dialog.close(); break;
127127
case 'settings': openModal(); break;
128-
case 'save': saveConsent(); break;
129-
case 'reject': saveConsent(false); break;
128+
case 'save': saveConsents(); break;
129+
case 'reject': saveConsents(false); break;
130130
}
131131
}
132132

@@ -153,9 +153,9 @@
153153
console.debug(name, payload);
154154
}
155155

156-
/* Consents & Injection */
156+
/* Data */
157157

158-
function readConsent() {
158+
function readConsents() {
159159
try {
160160
return JSON.parse(localStorage.getItem(o.storageKey))
161161
} catch (err) {
@@ -165,19 +165,53 @@
165165
}
166166
}
167167

168-
function saveConsent(value) {
168+
function clearStorages() {
169+
const localStores = Object.fromEntries(Object.entries(localStorage))
170+
const cookies = Object.fromEntries(
171+
d.cookie.split('; ').map(cookie => cookie.split('='))
172+
)
173+
const { consentTime, ...consents } = readConsents()
174+
175+
for (let [section, sectionConsent] of Object.entries(consents)) {
176+
if (sectionConsent) continue
177+
let sectionCookieNames = Object.keys(o[`${section}Cookies`] || {})
178+
179+
sectionCookieNames
180+
.filter(name => name.endsWith('*'))
181+
.map(wildcardName => {
182+
Object.keys({...cookies, ...localStores}).map(name => {
183+
if (name.startsWith(wildcardName.slice(0, -1))) sectionCookieNames.push(name)
184+
})
185+
})
186+
187+
for (const name of sectionCookieNames) {
188+
if (cookies[name]) {
189+
d.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`
190+
dispatch('delete',{cookie : name})
191+
}
192+
if (localStores[name]) {
193+
localStorage.removeItem(name)
194+
dispatch('delete',{localStorage : name})
195+
}
196+
}
197+
}
198+
}
199+
200+
function saveConsents(value) {
169201
const willReadValues = value === undefined
170202
w[o.global].consentTime = +new Date()
171203
o.sections.forEach(section => {
172204
if (section === 'essential') return false
173205
let sectionElement = ui.querySelector(`[data-s=${section}]`)
174-
w[o.global][section] = willReadValues
206+
let sectionConsent = willReadValues
175207
? sectionElement.checked
176208
: value
209+
w[o.global][section] = sectionConsent
177210
if (!willReadValues) sectionElement.checked = value
178211
})
179212
localStorage.setItem(o.storageKey, JSON.stringify(w[o.global]))
180213
dispatch('save', {data: w[o.global]})
214+
clearStorages()
181215
insertScripts()
182216
dialog.close()
183217
displayUI(false)
@@ -208,15 +242,17 @@
208242
});
209243
}
210244

245+
246+
211247
/* Start */
212248

213-
w[o.global] = readConsent() || {}
249+
w[o.global] = readConsents() || {}
214250

215251
// Optional Non-EU auto-consent
216252
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone
217253
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)
218254
if (o.acceptNonEU && !isEuropeTimezone) {
219-
saveConsent(true)
255+
saveConsents(true)
220256
displayUI(false)
221257
}
222258

@@ -232,14 +268,14 @@
232268
// Helper methods
233269
// <a onclick="bmInvalidate()" href="javascript:void(0)">Delete Consent Preferences</a>
234270
w.bmInvalidate = () => {
235-
dispatch('invalidate', {data: readConsent()})
236-
saveConsent(false)
271+
dispatch('invalidate', {data: readConsents()})
272+
saveConsents(false)
237273
localStorage.removeItem(o.storageKey)
238274
displayUI(true)
239275
}
240276
// <a onclick="bmUpdate()" href="javascript:void(0)">Update Consent Preferences</a>
241277
w.bmUpdate = () => {
242-
dispatch('update', {data: readConsent()})
278+
dispatch('update', {data: readConsents()})
243279
openModal()
244280
}
245-
})(document, window, 'biscuitman')
281+
})(document, window, Object, 'biscuitman')

dist/biscuitman.min.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)