Add element deactivation with cookie removal on consent withdrawal
When a user changes their consent preferences, elements for rejected categories are now deactivated: data attributes are restored, resources are unloaded, and associated cookies are removed. Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -31,6 +31,7 @@ const ACTIVATABLE_TAGS = new Set([
|
||||
function activateScript(el: HTMLScriptElement): void {
|
||||
const replacement = document.createElement("script");
|
||||
const originalType = el.getAttribute("data-type");
|
||||
const category = el.getAttribute(ATTR_CATEGORY);
|
||||
|
||||
for (const attr of el.attributes) {
|
||||
if (
|
||||
@@ -50,7 +51,7 @@ function activateScript(el: HTMLScriptElement): void {
|
||||
if (originalType) {
|
||||
replacement.setAttribute("type", originalType);
|
||||
}
|
||||
replacement.setAttribute(ATTR_ACTIVATED, "");
|
||||
replacement.setAttribute(ATTR_ACTIVATED, category || "");
|
||||
|
||||
if (el.textContent) {
|
||||
replacement.textContent = el.textContent;
|
||||
@@ -60,6 +61,8 @@ function activateScript(el: HTMLScriptElement): void {
|
||||
}
|
||||
|
||||
function activateElement(el: Element): void {
|
||||
const category = el.getAttribute(ATTR_CATEGORY);
|
||||
|
||||
const src = el.getAttribute(ATTR_SRC);
|
||||
if (src) {
|
||||
el.setAttribute("src", src);
|
||||
@@ -73,7 +76,7 @@ function activateElement(el: Element): void {
|
||||
}
|
||||
|
||||
el.removeAttribute(ATTR_CATEGORY);
|
||||
el.setAttribute(ATTR_ACTIVATED, "");
|
||||
el.setAttribute(ATTR_ACTIVATED, category || "");
|
||||
}
|
||||
|
||||
function tryActivate(
|
||||
@@ -100,6 +103,107 @@ function tryActivate(
|
||||
}
|
||||
}
|
||||
|
||||
function deactivateScript(el: HTMLScriptElement): void {
|
||||
const category = el.getAttribute(ATTR_ACTIVATED);
|
||||
const currentType = el.getAttribute("type");
|
||||
const replacement = document.createElement("script");
|
||||
|
||||
for (const attr of el.attributes) {
|
||||
if (
|
||||
attr.name === ATTR_ACTIVATED ||
|
||||
attr.name === "type" ||
|
||||
attr.name === "src"
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
replacement.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
|
||||
const src = el.getAttribute("src");
|
||||
if (src) {
|
||||
replacement.setAttribute(ATTR_SRC, src);
|
||||
}
|
||||
|
||||
if (currentType) {
|
||||
replacement.setAttribute("data-type", currentType);
|
||||
}
|
||||
replacement.setAttribute("type", "text/plain");
|
||||
|
||||
if (category) {
|
||||
replacement.setAttribute(ATTR_CATEGORY, category);
|
||||
}
|
||||
|
||||
if (el.textContent) {
|
||||
replacement.textContent = el.textContent;
|
||||
}
|
||||
|
||||
el.parentNode!.replaceChild(replacement, el);
|
||||
}
|
||||
|
||||
function deactivateElement(el: Element): void {
|
||||
const category = el.getAttribute(ATTR_ACTIVATED);
|
||||
|
||||
const src = el.getAttribute("src");
|
||||
if (src) {
|
||||
el.setAttribute(ATTR_SRC, src);
|
||||
el.removeAttribute("src");
|
||||
}
|
||||
|
||||
const href = el.getAttribute("href");
|
||||
if (href) {
|
||||
el.setAttribute(ATTR_HREF, href);
|
||||
el.removeAttribute("href");
|
||||
}
|
||||
|
||||
if (category) {
|
||||
el.setAttribute(ATTR_CATEGORY, category);
|
||||
}
|
||||
el.removeAttribute(ATTR_ACTIVATED);
|
||||
}
|
||||
|
||||
function removeCookies(names: string[]): void {
|
||||
const parts = location.hostname.split(".");
|
||||
const rootDomain =
|
||||
parts.length > 1 ? "." + parts.slice(-2).join(".") : location.hostname;
|
||||
|
||||
for (const name of names) {
|
||||
document.cookie = `${name}=; path=/; max-age=0`;
|
||||
document.cookie = `${name}=; path=/; domain=${rootDomain}; max-age=0`;
|
||||
}
|
||||
}
|
||||
|
||||
export function deactivateElements(
|
||||
consentData: Record<string, boolean>,
|
||||
categoryCookies: Record<string, string[]>,
|
||||
): void {
|
||||
const elements = document.querySelectorAll(`[${ATTR_ACTIVATED}]`);
|
||||
const cookiesToRemove = new Set<string>();
|
||||
|
||||
for (const el of elements) {
|
||||
const category = el.getAttribute(ATTR_ACTIVATED);
|
||||
if (!category || consentData[category]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (el instanceof HTMLScriptElement) {
|
||||
deactivateScript(el);
|
||||
} else {
|
||||
deactivateElement(el);
|
||||
}
|
||||
|
||||
const cookies = categoryCookies[category];
|
||||
if (cookies) {
|
||||
for (const name of cookies) {
|
||||
cookiesToRemove.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cookiesToRemove.size > 0) {
|
||||
removeCookies([...cookiesToRemove]);
|
||||
}
|
||||
}
|
||||
|
||||
export function activateElements(
|
||||
consentData: Record<string, boolean>,
|
||||
): void {
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
import { activateElements, observeAndActivate } from "./activation";
|
||||
import {
|
||||
activateElements,
|
||||
deactivateElements,
|
||||
observeAndActivate,
|
||||
} from "./activation";
|
||||
import { getConsentCookie, setConsentCookie } from "./cookie";
|
||||
import { NotFoundError } from "./errors";
|
||||
import { fetchJSON } from "./http";
|
||||
@@ -236,6 +240,12 @@ export class CookieBannerClient {
|
||||
}
|
||||
|
||||
private activate(consentData: Record<string, boolean>): void {
|
||||
const categoryCookies: Record<string, string[]> = {};
|
||||
for (const cat of this.config.categories) {
|
||||
categoryCookies[cat.name] = cat.cookies.map((c) => c.name);
|
||||
}
|
||||
|
||||
deactivateElements(consentData, categoryCookies);
|
||||
activateElements(consentData);
|
||||
if (this.observer) {
|
||||
this.observer.disconnect();
|
||||
|
||||
Reference in New Issue
Block a user