orestbida / cookieconsent

:cookie: Simple cross-browser cookie-consent plugin written in vanilla js
https://playground.cookieconsent.orestbida.com/
MIT License
3.68k stars 388 forks source link

[Feat]: Auto-clear localStorage and sessionStorage #611

Open mcaskill opened 6 months ago

mcaskill commented 6 months ago

Description

Are there any plans to support erasing records from localStorage (and sessionStorage)?

Thank you for your work on this plugin.

Proposed solution

This would involve modifying the cookie helper functions to extract keys from the Storage instances, filter matching cookies, and remove them.

The internal implementation wouldn't be too complicated to add (see example below). I just don't know how this should be configurable for users integrating this plugin.

Option 1: `[category].autoClear.clear*Storage` ```js CookieConsent.run({ categories: { analytics: { autoClear: { clearLocalStorage: true, clearSessionStorage: true, cookies: [ { name: '_uetsid' } ] } } } }) ```
Option 2: `[category].autoClear.cookies[].clear*Storage` ```js CookieConsent.run({ categories: { analytics: { autoClear: { cookies: [ { name: '_uetsid', localStorage: true, sessionStorage: true } ] } } } }) ```
Option 3: `[category].autoClear.*Storage[]` ```js CookieConsent.run({ categories: { analytics: { autoClear: { cookies: [ { name: '_uetsid' } ], localStorage: [ { name: '_uetsid' } ], sessionStorage: [ { name: '_uetsid' } ] } } } }) ```

The following is a stripped down working example I've tested on a client project (that does not implement the aformentioned configuration variants):

import { isLocalStorageAvailable, isSessionStorageAvailable } from '../utils/environment.js';
import * as CookieConsent from 'vanilla-cookieconsent';

CookieConsent.run({
    // ...
    onConsent: () => {
        toggleCategoryAnalytics();
    },
    onChange: ({ changedCategories }) => {
        if (changedCategories.includes('analytics')) {
            toggleCategoryAnalytics();
        }
    },
});

function toggleCategoryAnalytics() {
    const isAccepted = CookieConsent?.acceptedCategory('analytics');

    window.dataLayer?.push([ 'consent', 'update', {
        'analytics_storage': (isAccepted ? 'granted' : 'denied'),
    } ]);

    if (!isAccepted) {
        const cookies = CookieConsent.getConfig('categories')?.analytics?.autoClear?.cookies;
        if (cookies.length) {
            eraseStorageItems(cookies);
        }
    }
}

function eraseStorageItems(cookies) {
    let localStorageKeys;
    let sessionStorageKeys;

    for (const { name: cookieName } of cookies) {
        if (isLocalStorageAvailable()) {
            localStorageKeys ??= getAllStorageKeys(window.localStorage);

            const foundCookies = findMatchingCookies(localStorageKeys, cookieName);
            for (const foundCookie of foundCookies) {
                window.localStorage.removeItem(foundCookie);
            }
        }

        if (isSessionStorageAvailable()) {
            sessionStorageKeys ??= getAllStorageKeys(window.sessionStorage);

            const foundCookies = findMatchingCookies(sessionStorageKeys, cookieName);
            for (const foundCookie of foundCookies) {
                window.sessionStorage.removeItem(foundCookie);
            }
        }
    }
}

/**
 * This function is copied from `findMatchingCookies()` from
 * {@link https://github.com/orestbida/cookieconsent/blob/v3.0.0-rc.17/src/utils/cookies.js vanilla-cookieconsent}.
 *
 * @param {string[]} allCookies
 * @param {string}   cookieName
 */
function findMatchingCookies(allCookies, cookieName) {
    // ...
}

/**
 * Returns array with all the cookie names.
 *
 * This function is based on `getAllCookies()` from
 * {@link https://github.com/orestbida/cookieconsent/blob/v3.0.0-rc.17/src/utils/cookies.js vanilla-cookieconsent}.
 *
 * @param   {Storage} storage
 * @param   {?RegExp} [regex]
 * @returns {string[]}
 */
function getAllStorageKeys(storage, regex) {
    /**
     * @type {string[]}
     */
    const cookieNames = [];

    /**
     * Save only the item names
     */
    for (const name of Object.keys(storage)) {
        if (regex) {
            try {
                regex.test(name) && cookieNames.push(name);
            // eslint-disable-next-line no-empty
            } catch (e) {}
        } else {
            cookieNames.push(name);
        }
    }

    return cookieNames;
}

Additional details

No response

orestbida commented 4 months ago

I think adding an option to delete items in localstorage is useful, but I don't agree on session storage ones, since they are deleted at the end of the browsing session.