diff --git a/Readme.md b/Readme.md index 44b4230c..35411db4 100644 --- a/Readme.md +++ b/Readme.md @@ -12,7 +12,7 @@ changes. Thanks and credits to [Orest Bida](https://github.com/orestbida/cookiec
-A __lightweight__ & __gdpr compliant__ cookie consent plugin written in plain javascript. An "all-in-one" solution which also allows you to write your cookie policy inside it without the need of having a dedicated page. +A __lightweight__ & __gdpr compliant__ cookie consent plugin written in plain javascript.
@@ -22,17 +22,20 @@ A __lightweight__ & __gdpr compliant__ cookie consent plugin written in plain ja
## Table of contents -1. [Key features](#key-features) -2. [Installation & Usage](#installation--usage) -4. [Layout options & customization](#layout-options--customization) -5. [API methods](#api-methods) -6. [Available callbacks](#available-callbacks) -7. [All configuration options](#all-configuration-options) -8. [How to block/manage scripts](#how-to-blockmanage-scripts) -9. [Configuration examples](#full-example-configurations) -10. [How to enable/manage revisions](#how-to-enablemanage-revisions) -11. [FAQ](#faq) -12. [License](#license) +- [Table of contents](#table-of-contents) +- [Key features](#key-features) +- [Installation & Usage](#installation--usage) +- [Layout options & customization](#layout-options--customization) +- [How to block/manage scripts](#how-to-blockmanage-scripts) +- [API methods](#api-methods) +- [Available `data-cc` actions](#available-data-cc-actions) +- [Available callbacks](#available-callbacks) + - [All configuration options](#all-configuration-options) +- [Full example configurations](#full-example-configurations) + - [How to configure languages & cookie settings](#how-to-configure-languages--cookie-settings) +- [How to enable/manage revisions](#how-to-enablemanage-revisions) +- [FAQ](#faq) +- [License](#license) ## Key features - __Lightweight__ @@ -49,8 +52,8 @@ A __lightweight__ & __gdpr compliant__ cookie consent plugin written in plain ja ```bash # CDN links - https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@v2.8.0/dist/cookieconsent.js - https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@v2.8.0/dist/cookieconsent.css + https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@v2.8.1/dist/cookieconsent.js + https://cdn.jsdelivr.net/gh/orestbida/cookieconsent@v2.8.1/dist/cookieconsent.css ``` Thanks to [Till Sanders](https://github.com/tillsanders) for bringing the plugin on npm. @@ -63,14 +66,18 @@ A __lightweight__ & __gdpr compliant__ cookie consent plugin written in plain ja 1. Import the plugin: add a `script` tag pointing to `cookieconsent.js` ```html - + + + + + ``` - Note: replace `` with a valid path! + Note: replace `` and `` with valid paths!
3. Configure and run diff --git a/dist/cookieconsent.js b/dist/cookieconsent.js index 5b9be8b9..ce524ec9 100644 --- a/dist/cookieconsent.js +++ b/dist/cookieconsent.js @@ -1,43 +1,43 @@ /* - CookieConsent v3.0.0-beta.1 + CookieConsent v2.8.1 https://www.github.com/orestbida/cookieconsent Author Orest Bida Released under the MIT License */ -(function(){var hb=function(ab){var f={mode:"opt-in",current_lang:"en",auto_language:null,autorun:!0,page_scripts:!0,hide_from_bots:!0,cookie_name:"cc_cookie",cookie_expiration:182,cookie_domain:window.location.hostname,cookie_path:"/",cookie_same_site:"Lax",use_rfc_cookie:!1,autoclear_cookies:!0,revision:0,script_selector:"data-cookiecategory"},n={},g,u={},B=null,J=!1,O=!1,ma=!1,Ba=!1,na=!1,v,X,T,oa,Ca,Da,Qa=!1,ha=!0,U=[],wa=!1,Ea,Fa=[],Ra=[],Ga=[],Sa=!1,pa,Ha,Ia=[],ia=[],P=[],G=[],xa=[],qa=document.documentElement, -Q,ra,x,Y,sa,V,R,S,Z,E,K,ta,ja,ka,y,aa,ba,ca,da,Ta=function(a){function b(l){return(a||document).querySelectorAll('a[data-cc="'+l+'"], button[data-cc="'+l+'"]')}function c(l,q){l.preventDefault?l.preventDefault():l.returnValue=!1;n.accept(q);n.hideSettings();n.hide()}for(var d=b("c-settings"),e=b("accept-all"),m=b("accept-necessary"),p=b("accept-custom"),k=0;k
\x3c!--\x3e
\x3c!--
\x3c!--\x3e
\x3c!-->a/4).toString(16)})},gb=function(a,b){return"browser"===e.auto_language?(b=navigator.language||navigator.browserLanguage, +2 retrieve category states from cookie + * If consent is valid => retrieve category states from cookie * Otherwise use states defined in the user_config. object */ - if(cookie_consent_accepted){ + if(!invalid_consent){ if(_inArray(saved_cookie_content['categories'], cookie_category) > -1){ block_switch.checked = true; !new_settings_blocks && toggle_states.push(true); @@ -1115,20 +1152,35 @@ /** * Clear cookies when settings/preferences change */ - if(cookie_consent_accepted && _config.autoclear_cookies && changed_settings.length > 0) + if(!invalid_consent && _config.autoclear_cookies && changed_settings.length > 0) _autoclearCookies(); + if(!consent_date) consent_date = new Date(); + if(!consent_uuid) consent_uuid = _uuidv4(); + saved_cookie_content = { "categories": accepted_categories, "revision": _config.revision, "data": cookie_data, - "rfc_cookie": _config.use_rfc_cookie + "rfc_cookie": _config.use_rfc_cookie, + "consent_date": consent_date.toISOString(), + "consent_uuid": consent_uuid } // save cookie with preferences 'categories' (only if never accepted or settings were updated) - if(!cookie_consent_accepted || changed_settings.length > 0 || !valid_revision){ + if(invalid_consent || changed_settings.length > 0){ valid_revision = true; + /** + * Update "last_consent_update" only if it is invalid (after t) + */ + if(!last_consent_update) + last_consent_update = consent_date; + else + last_consent_update = new Date(); + + saved_cookie_content['last_consent_update'] = last_consent_update.toISOString(); + /** * Update accept type */ @@ -1136,12 +1188,14 @@ _setCookie(_config.cookie_name, JSON.stringify(saved_cookie_content)); _manageExistingScripts(); + + //_printConsentDateHTML(); } - if(!cookie_consent_accepted){ + if(invalid_consent){ /** - * Delete unused/"zombie" cookies the very-first time + * Delete unused/"zombie" cookies if consent is not valid (not yet expressed or cookie has expired) */ if(_config.autoclear_cookies) _autoclearCookies(true); @@ -1152,7 +1206,10 @@ if(typeof onAccept === 'function') onAccept(saved_cookie_content); - cookie_consent_accepted = true; + /** + * Set consent as valid + */ + invalid_consent = false; if(_config.mode === 'opt-in') return; } @@ -1200,6 +1257,17 @@ return el; } + /** + * Generate RFC4122-compliant UUIDs. + * https://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid?page=1&tab=votes#tab-top + * @returns {string} + */ + var _uuidv4 = function(){ + return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, function(c){ + return (c ^ (window.crypto || window.msCrypto).getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16) + }); + } + /** * Resolve which language should be used. * @@ -1393,7 +1461,7 @@ */ _cookieconsent.allowedCategory = function(cookie_category){ - if(cookie_consent_accepted || _config.mode === 'opt-in') + if(!invalid_consent || _config.mode === 'opt-in') var allowed_categories = JSON.parse(_getCookie(_config.cookie_name, 'one', true) || '{}')['categories'] || [] else // mode is 'opt-out' var allowed_categories = default_enabled_categories; @@ -1415,25 +1483,31 @@ // Retrieve cookie value (if set) saved_cookie_content = JSON.parse(_getCookie(_config.cookie_name, 'one', true) || "{}"); - cookie_consent_accepted = saved_cookie_content['categories'] !== undefined; - /** - * Immediately retrieve the 'data' field from cookie - * (since this value is allowed to be accessed/used before the .run method) - */ + // Retrieve "consent_uuid" + consent_uuid = saved_cookie_content['consent_uuid']; + + // If "consent_uuid" is present => assume that consent was previously given + var cookie_consent_accepted = consent_uuid !== undefined; + + // Retrieve "consent_date" + consent_date = saved_cookie_content['consent_date']; + consent_date && (consent_date = new Date(consent_date)); + + // Retrieve "last_consent_update" + last_consent_update = saved_cookie_content['last_consent_update']; + last_consent_update && (last_consent_update = new Date(last_consent_update)); + + // Retrieve "data" cookie_data = saved_cookie_content['data'] !== undefined ? saved_cookie_content['data'] : null; - // Compare current revision with the one retrieved from cookie - valid_revision = typeof user_config['revision'] === "number" - ? cookie_consent_accepted - ? user_config['revision'] > -1 - ? saved_cookie_content['revision'] === _config.revision - : true - : true - : true; + // If revision is enabled and current value !== saved value inside the cookie => revision is not valid + if(revision_enabled && saved_cookie_content['revision'] !== _config.revision){ + valid_revision = false; + } - // If invalid revision or cookie is empty => create consent modal - consent_modal_exists = (!cookie_consent_accepted || !valid_revision); + // If consent is not valid => create consent modal + consent_modal_exists = invalid_consent = (!cookie_consent_accepted || !valid_revision || !consent_date || !last_consent_update || !consent_uuid); // Generate cookie-settings dom (& consent modal) _createCookieConsentHTML(); @@ -1452,7 +1526,8 @@ // Accessibility :=> if tab pressed => trap focus inside modal setTimeout(function(){_handleFocusTrap();}, 100); - if(cookie_consent_accepted && valid_revision){ + // If consent is valid + if(!invalid_consent){ var rfc_prop_exists = typeof saved_cookie_content['rfc_cookie'] === "boolean"; /* @@ -1473,10 +1548,14 @@ if(typeof onAccept === 'function') onAccept(saved_cookie_content); + _log("CookieConsent [NOTICE]: consent already given!", saved_cookie_content); - }else if(_config.mode === 'opt-out'){ - _log("CookieConsent [CONFIG] mode='" + _config.mode + "', default enabled categories:", default_enabled_categories); - _manageExistingScripts(default_enabled_categories); + }else{ + if(_config.mode === 'opt-out'){ + _log("CookieConsent [CONFIG] mode='" + _config.mode + "', default enabled categories:", default_enabled_categories); + _manageExistingScripts(default_enabled_categories); + } + _log("CookieConsent [NOTICE]: ask for consent!"); } }else{ _log("CookieConsent [NOTICE]: cookie consent already attached to body!"); @@ -1657,43 +1736,6 @@ return set; } - /** - * Forcefully set a specific revision and show consent modal - * @param {number} new_revision - * @param {boolean} [prompt_consent] - * @returns {boolean} - */ - var _setRevision = function(new_revision, prompt_consent, message){ - - // If plugin has been initialized and new revision is valid - if( - main_container - && typeof new_revision === "number" - && saved_cookie_content['revision'] !== new_revision - ){ - - revision_enabled = true; - revision_message = message; - valid_revision = false; - _config.revision = new_revision; - - // Show consent modal ? - if(prompt_consent === true){ - _createConsentModal(user_config); - _guiManager(user_config['gui_options'], true); - _getModalFocusableData(); - _cookieconsent.show(); - }else { - // If revision was modified, save cookie with the new revision - _cookieconsent.accept(); - } - - return true; - } - - return false; - } - /** * Helper method to set a variety of fields * @param {string} field @@ -1801,7 +1843,7 @@ * Dynamically load script (append to head) * @param {string} src * @param {scriptLoaded} callback - * @param {string[]} attrs + * @param {object[]} [attrs] Custom attributes */ _cookieconsent.loadScript = function(src, callback, attrs){ @@ -1821,16 +1863,7 @@ // if callback function defined => run callback onload if(function_defined){ - if(script.readyState) { // only required for IE <9 - script.onreadystatechange = function() { - if ( script.readyState === "loaded" || script.readyState === "complete" ) { - script.onreadystatechange = null; - callback(); - } - }; - }else{ //Others - script.onload = callback; - } + script.onload = callback; } script.src = src; @@ -1838,7 +1871,7 @@ /** * Append script to head */ - (document.head ? document.head : document.getElementsByTagName('head')[0]).appendChild(script); + document.head.appendChild(script); }else{ function_defined && callback(); } @@ -2056,7 +2089,8 @@ document.cookie = cookieStr; - _log("CookieConsent [SET_COOKIE]: cookie "+ name + "='" + value + "' was set! Expires after " + cookie_expiration + " days"); + _log("CookieConsent [SET_COOKIE]: cookie '" + name + "'=", JSON.parse(value)); + _log("CookieConsent [SET_COOKIE]: '" + name + "' expires after " + cookie_expiration + " day(s)"); } /** @@ -2064,8 +2098,8 @@ * returns the cookie value if found (or an array * of cookies if filter provided), otherwise empty string: "" * @param {string} name - * @param {string} filter - 'one' or 'all' - * @param {boolean} get_value - set to true to obtain its value + * @param {string} filter 'one' or 'all' + * @param {boolean} [get_value] set to true to obtain its value * @returns {string|string[]} */ var _getCookie = function(name, filter, get_value) { @@ -2137,20 +2171,10 @@ * @param {Element} elem * @param {string} event * @param {eventFired} fn - * @param {boolean} passive + * @param {boolean} [isPassive] */ - var _addEvent = function(elem, event, fn, _passive) { - var passive = _passive === true; - - if (elem.addEventListener) { - passive ? elem.addEventListener(event, fn , { passive: true }) : elem.addEventListener(event, fn, false); - } else { - /** - * For old browser, add 'on' before event: - * 'click':=> 'onclick' - */ - elem.attachEvent("on" + event, fn); - } + var _addEvent = function(elem, event, fn, isPassive) { + elem.addEventListener(event, fn , isPassive === true ? { passive: true } : false); } /** @@ -2159,10 +2183,7 @@ */ var _getKeys = function(obj){ if(typeof obj === "object"){ - var keys = [], i = 0; - for (var key in obj) - keys[i++] = key; - return keys; + return Object.keys(obj); } } @@ -2172,12 +2193,7 @@ * @param {string} classname */ var _addClass = function (elem, classname){ - if(elem.classList) - elem.classList.add(classname) - else{ - if(!_hasClass(elem, classname)) - elem.className += ' '+classname; - } + elem.classList.add(classname); } /** @@ -2186,7 +2202,7 @@ * @param {string} classname */ var _removeClass = function (el, className) { - el.classList ? el.classList.remove(className) : el.className = el.className.replace(new RegExp('(\\s|^)' + className + '(\\s|$)'), ' '); + el.classList.remove(className); } /** @@ -2195,10 +2211,7 @@ * @param {string} className */ var _hasClass = function(el, className) { - if (el.classList) { - return el.classList.contains(className); - } - return !!el.className.match(new RegExp('(\\s|^)' + className + '(\\s|$)')); + return el.classList.contains(className); } return _cookieconsent; @@ -2208,7 +2221,7 @@ /** * Make CookieConsent object accessible globally */ - if(typeof window[init] !== 'function'){ + if(typeof window !== 'undefined' && typeof window[init] !== 'function'){ window[init] = CookieConsent } })(); \ No newline at end of file diff --git a/types/types.d.ts b/types/types.d.ts new file mode 100644 index 00000000..650abf93 --- /dev/null +++ b/types/types.d.ts @@ -0,0 +1,146 @@ +export {} + +declare global { + type MODE = 'opt-in' | 'opt-out' + + type AUTO_LANGUAGE_ORIGIN = 'browser' | 'document' + + type ModalLayout = 'box' | 'cloud' | 'bar' + + type ModalPosition = 'bottom left' | 'bottom right' | 'bottom center' | 'middle left' | 'middle right' | 'middle center' | 'top left' | 'top right' | 'top center' + + type GUITransition = 'zoom' | 'slide' + + type SettingLayout = 'box' | 'bar' + + type SettingPosition = 'left' | 'right' + + interface SavedCookieContent { + categories: string[] + revision: number + data?: null | Record | string> + rfc_cookie: boolean + consent_date: string + consent_uuid: string + last_consent_update: string + } + + interface GUIOptions { + consent_modal?: { + layout?: ModalLayout + position?: ModalPosition + transition?: GUITransition + swap_buttons?: boolean + }, + settings_modal?: { + layout?: SettingLayout + position?: SettingPosition + transition?: GUITransition + } + } + + interface UserPreferences { + accept_type: string + accepted_categories: string[] + rejected_categories: string[] + } + + type PrimaryButtonRole = 'accept_selected' | 'accept_all' + + type SecondaryButtonRole = 'settings' | 'accept_necessary' + + interface ButtonSetting { + text: string + role: Role + } + + interface ConsentModalLanguageSetting { + title?: string + description?: string + primary_btn?: ButtonSetting + secondary_btn?: ButtonSetting + revision_message?: string + } + + interface ToggleSetting { + value: string + enabled?: boolean + readonly?: boolean + } + + interface BlockSetting { + title: string + description: string + toggle?: ToggleSetting + cookie_table_headers?: Record[] + cookie_table?: Record[] + } + + interface SettingsModalLanguageSetting { + title?: string + save_settings_btn?: string + accept_all_btn?: string + reject_all_btn?: string + blocks?: BlockSetting[] + } + + interface LanguageSetting { + consent_modal?: ConsentModalLanguageSetting + settings_modal?: SettingsModalLanguageSetting + } + + interface UserConfig { + autorun?: boolean + delay?: number + mode?: MODE + cookie_expiration?: number + cookie_necessary_only_expiration?: number + cookie_path?: string + cookie_domain?: string + cookie_same_site?: string + use_rfc_cookie?: boolean + force_consent?: boolean + revision?: number + current_lang?: string + auto_language?: AUTO_LANGUAGE_ORIGIN + autoclear_cookies?: boolean + page_scripts?: boolean + remove_cookie_tables?: boolean + hide_from_bots?: boolean + gui_options?: GUIOptions + onAccept?: (savedCookieContent: SavedCookieContent) => void + onChange?: (cookie: SavedCookieContent, changedCookieCategories: string[]) => void + onFirstAction? :(userPreferences: UserPreferences, cookie: SavedCookieContent) => void + languages?: Record + } + + interface ScriptAttribute { + name: string + value: any + } + + interface CookieConsent { + run(config: UserConfig): void + showSettings(delay: number): void + accept(_categories: string | string[], _exclusions?: string[]): void + hideSettings(): void + hide(): void + updateLanguage(lang: string, force: boolean): boolean + getUserPreferences(): UserPreferences + loadScript(src: string, callback: () => void | typeof onload, attrs?: ScriptAttribute[]): void + updateScripts(): void + show(delay?: number, create_modal?: boolean): void + eraseCookies(_cookies: string | string[], _path?: string, _domain?: string): void + validCookie(cookie_name: string): boolean + allowedCategory(cookie_category: string): boolean + set(field: string, data: Record): boolean + get(field: string, cookie_name?: string): Record + getConfig(field: Field): UserConfig[Field] + } + + function initCookieConsent(root?: HTMLElement): CookieConsent + + interface Window { + initCookieConsent: typeof initCookieConsent + } +} \ No newline at end of file