gdpr-consent
Version:
GDPR banner to comply with the European cookie law. Inspired by tarteaucitronjs.
477 lines (426 loc) • 16.5 kB
text/typescript
import { escape, trigger, updateCSSOfElement } from "@lesjoursfr/browser-tools";
import { DefaulGDPRConsentParameters } from "./gdpr-consent-parameters.js";
import {
GDPRConsentParameters,
GDPRConsentState,
GDPRConsentUser,
LangInterface,
LanguagesLoader,
ServiceInterface,
ServicesCategories,
ServicesLoader,
} from "./interfaces/index.js";
import { getLanguage } from "./languages/index.js";
import {
activate,
checkCount,
closePanel,
create,
hashchangeEvent,
isActivated,
keydownEvent,
openAlert,
openPanel,
order,
read,
respond,
respondAll,
respondEffect,
toggle,
} from "./modules/index.js";
class GDPRConsentInstance implements GDPRConsentState {
user: GDPRConsentUser;
lang!: LangInterface;
services: { [key: string]: ServiceInterface };
added: { [key: string]: boolean };
state: { [key: string]: boolean | string };
launch: { [key: string]: boolean };
parameters!: GDPRConsentParameters;
reloadThePage: boolean;
alreadyLaunch: number;
loaded: boolean;
languagesLoader?: LanguagesLoader;
servicesLoader?: ServicesLoader;
job!: string[];
public constructor() {
this.user = {};
this.services = {};
this.added = {};
this.state = {};
this.launch = {};
this.reloadThePage = false;
this.alreadyLaunch = 0;
this.loaded = false;
}
public withLanguages(loader: LanguagesLoader): void {
this.languagesLoader = loader;
}
public withServices(loader: ServicesLoader): void {
this.servicesLoader = loader;
}
public init(params: Partial<GDPRConsentParameters> = {}): void {
// Get params
this.parameters = Object.assign({}, structuredClone(DefaulGDPRConsentParameters), structuredClone(params));
// Launch
if (this.alreadyLaunch === 0) {
this.alreadyLaunch = 1;
// Bind events listeners
window.addEventListener("keydown", (evt) => keydownEvent(this, evt), false);
window.addEventListener("hashchange", () => hashchangeEvent(this), false);
// Check if the DOM is already loaded
if (window.document.readyState === "complete") {
this.load();
} else {
window.addEventListener("load", () => this.load(), false);
}
}
}
public load(): void {
// Check if we have loaders
if (typeof this.languagesLoader !== "function") {
throw new Error("Missing languages loader !");
}
if (typeof this.servicesLoader !== "function") {
throw new Error("Missing services loader !");
}
// Load language and services
const lang = getLanguage(this.languagesLoader());
if (lang === undefined) {
throw new Error("Missing english translation !");
}
this.lang = lang;
this.services = this.servicesLoader(this.user);
// Delete loaders
delete this.languagesLoader;
delete this.servicesLoader;
const body = document.body;
const div = document.createElement("div");
let html = "";
let index;
let cat: ServicesCategories[] = ["ads", "analytic", "api", "comment", "social", "support", "video", "other"];
let i;
cat = cat.sort((a, b) => {
if (this.lang[a].title > this.lang[b].title) {
return 1;
}
if (this.lang[a].title < this.lang[b].title) {
return -1;
}
return 0;
});
// Prepare the html
// For the Pannel
html +=
'<button type="button" id="tarteaucitron-back" onclick="GDPRConsent.closePanel();" aria-label="' +
this.lang.close +
'"></button>';
html += '<div id="tarteaucitron" role="dialog" aria-labelledby="dialogTitle">';
html += ' <button type="button" id="tarteaucitron-close-panel" onclick="GDPRConsent.closePanel();">X</button>';
html += ' <div id="tarteaucitron-services">';
// L'en-tête des services
html += ' <div id="tarteaucitron-services-top">';
html +=
' <span class="tarteaucitron-h1" role="heading" aria-level="1" id="dialogTitle">' +
this.lang.title +
"</span>";
html += ' <div id="tarteaucitron-info">';
html += " " + this.lang.disclaimer;
if (this.parameters.websiteName) {
html += " " + this.lang.disclaimerWebsite + " " + escape(this.parameters.websiteName) + ".";
}
html += " </div>";
// Accepter tout ou interdire tout
html += ' <div class="tarteaucitron-line">';
html += ' <span class="tarteaucitron-h3" role="heading" aria-level="2">' + this.lang.all + "</span>";
html += ' <div class="tarteaucitron-ask">';
html +=
' <button type="button" id="tarteaucitron-all-allowed" class="tarteaucitron-allow" onclick="GDPRConsent.respondAll(true, true);">';
html += " ✓ " + this.lang.allowAll;
html += " </button> ";
html +=
' <button type="button" id="tarteaucitron-all-denied" class="tarteaucitron-deny" onclick="GDPRConsent.respondAll(false, true);">';
html += " ✗ " + this.lang.denyAll;
html += " </button>";
html += " </div>";
html += " </div>";
html += " </div>";
// La liste des Services
html += ' <div id="tarteaucitron-services-list">';
html += ' <div class="clear"></div>';
if (this.parameters.mandatory === true) {
html += '<div class="tarteaucitron-cookie-group">';
html += ' <div class="tarteaucitron-cookie-text">';
html +=
' <span class="tarteaucitron-h3" role="heading" aria-level="2">' + this.lang.mandatoryTitle + "</span>";
html += ' <span class="tarteaucitron-description">' + this.lang.mandatoryText + "</span>";
html += " </div>";
html += ' <div class="tarteaucitron-cookie-buttons">';
html += ' <button type="button" class="tarteaucitron-allow solo">';
html += " ✓ " + this.lang.allow;
html += " </button> ";
html += " </div>";
html += "</div>";
}
for (i = 0; i < cat.length; i += 1) {
html += ' <li id="tarteaucitron-services-title_' + cat[i] + '" class="tarteaucitron-hidden">';
html += ' <div class="tarteaucitron-title">';
html +=
' <button type="button" onclick="GDPRConsent.toggle(\'tarteaucitron-details' +
cat[i] +
"', 'tarteaucitron-info-box');return false\">✛ " +
this.lang[cat[i]].title +
"</button>";
html += " </div>";
html +=
' <div id="tarteaucitron-details' +
cat[i] +
'" class="tarteaucitron-details tarteaucitron-info-box">';
html += " " + this.lang[cat[i]].details;
html += " </div>";
html += ' <ul id="tarteaucitron-services_' + cat[i] + '"></ul></li>';
}
html +=
' <li id="tarteaucitron-no-services-title" class="tarteaucitron-line">' +
this.lang.noServices +
"</li>";
html += " </ul>";
html += ' <div id="tarteaucitron-services-bottom">';
html +=
' <button type="button" id="tarteaucitron-save-responses" onclick="GDPRConsent.closePanel();">' +
this.lang.save +
"</button>";
html += " </div>";
html += " </div>";
html += " </div>";
html += "</div>";
// For the Banner
if (!this.parameters.acceptAllCta) {
html += '<div id="tarteaucitron-alert-big" class="tarteaucitron-alert-big-bottom">';
if (this.parameters.siteDisclaimerTitle !== "" && this.parameters.siteDisclaimerMessage !== "") {
html += '<div id="tarteaucitron-wrapper">';
html += ' <div id="tarteaucitron-disclaimer-texte">';
html += ' <span id="tarteaucitron-site-disclaimer-title">';
html += " " + this.parameters.siteDisclaimerTitle;
html += " </span>";
html += ' <span id="tarteaucitron-site-disclaimer-message">';
html += " " + this.parameters.siteDisclaimerMessage + "<br />";
html += " </span>";
}
html += ' <span id="tarteaucitron-disclaimer-alert">';
html += " " + this.lang.alertBigPrivacy;
html += " </span>";
html += " </div>";
html += ' <div id="tarteaucitron-disclaimer-buttons">';
html += ' <button type="button" id="tarteaucitron-personalize" onclick="GDPRConsent.openPanel();">';
html += " " + this.lang.personalize;
html += " </button>";
html += " </div>";
html += " </div>";
html += "</div>";
} else {
html += '<div id="tarteaucitron-alert-big" class="tarteaucitron-alert-big-bottom">';
if (this.parameters.siteDisclaimerTitle !== "" && this.parameters.siteDisclaimerMessage !== "") {
html += '<div id="tarteaucitron-wrapper">';
html += ' <div id="tarteaucitron-disclaimer-texte">';
html += ' <span id="tarteaucitron-site-disclaimer-title">';
html += " " + this.parameters.siteDisclaimerTitle;
html += " </span>";
html += ' <span id="tarteaucitron-site-disclaimer-message">';
html += " " + this.parameters.siteDisclaimerMessage + "<br />";
html += " </span>";
}
html += ' <span id="tarteaucitron-disclaimer-alert">';
html += " " + this.lang.alertBigPrivacy;
html += " </span>";
html += " </div>";
html += ' <div id="tarteaucitron-disclaimer-buttons">';
html +=
' <button type="button" id="tarteaucitron-continue" onclick="GDPRConsent.alertRespondAll(false);">';
html += " → " + this.lang.continue;
html += " </button>";
html += ' <div id="tarteaucitron-group-buttons">';
html +=
' <button type="button" id="tarteaucitron-personalize" onclick="GDPRConsent.alertRespondAll(true);">';
html += " ✓ " + this.lang.acceptAll;
html += " </button>";
html +=
' <button type="button" id="tarteaucitron-close-alert" onclick="GDPRConsent.alertOpenPanel();">';
html += " " + this.lang.personalize;
html += " </button>";
html += " </div>";
html += " </div>";
html += " </div>";
html += "</div>";
}
div.id = "tarteaucitron-root";
// Append tarteaucitron: #tarteaucitron-root last-child of the body
body.appendChild(div);
div.innerHTML = html;
// Send an event
trigger(window, "tac.root_available");
if (this.job !== undefined) {
this.job = this.cleanArray(this.job);
for (index = 0; index < this.job.length; index += 1) {
this.addService(this.job[index]);
}
} else {
this.job = [];
}
this.job.push = (id: string): number => {
if (this.job.indexOf(id) === -1) {
Array.prototype.push.call(this.job, id);
}
this.launch[id] = false;
this.addService(id);
return this.job.length;
};
if (document.location.hash === this.parameters.hashtag) {
openPanel(this);
}
this.loaded = true;
// Send an event
trigger(window, "tac.loaded");
}
public addService(serviceId: string): void {
const s = this.services;
const service = s[serviceId];
const cookie = read(this.parameters);
const isDenied = cookie.indexOf(service.key + "=false") >= 0;
const isAllowed =
cookie.indexOf(service.key + "=true") >= 0 ||
(!service.needConsent && cookie.indexOf(service.key + "=false") < 0);
const isResponded = cookie.indexOf(service.key + "=false") >= 0 || cookie.indexOf(service.key + "=true") >= 0;
let html = "";
if (this.added[service.key] !== true) {
this.added[service.key] = true;
html += '<div id="' + service.key + '-line" class="tarteaucitron-cookie-group">';
html += ' <div class="tarteaucitron-cookie-text">';
html += ' <span class="tarteaucitron-h3" role="heading" aria-level="3">' + service.name + "</span>";
html += ' <span id="tacCL' + service.key + '" class="tarteaucitron-description"></span>';
if (this.parameters.moreInfoLink === true) {
html +=
' <a href="' +
service.uri +
'" target="_blank" rel="noreferrer noopener" title="' +
service.name +
" " +
this.lang.newWindow +
'">';
html += " " + this.lang.source;
html += " </a>";
}
html += " </div>";
html += ' <div class="tarteaucitron-cookie-buttons">';
html +=
' <span id="' +
service.key +
'Allowed" class="tarteaucitron-switch-state" onclick="GDPRConsent.respond(this, event);">' +
this.lang.allow +
"</span>";
html +=
' <div class="tarteaucitron-switch" id="' +
service.key +
'Switch" onclick="GDPRConsent.respond(this, event);">';
html += ' <button type="button" class="tarteaucitron-switch-button"></button>';
html += " </div> ";
html +=
' <span id="' +
service.key +
'Denied" class="tarteaucitron-switch-state" onclick="GDPRConsent.respond(this, event);">' +
this.lang.deny +
"</span>";
html += " </div>";
html += "</li>";
updateCSSOfElement("tarteaucitron-services-title_" + service.type, "display", "block");
const serviceTypeEl = document.getElementById("tarteaucitron-services_" + service.type);
if (serviceTypeEl !== null) {
serviceTypeEl.innerHTML += html;
}
updateCSSOfElement("tarteaucitron-no-services-title", "display", "none");
order(service.type, this);
}
if (isAllowed) {
if (this.launch[service.key] !== true) {
this.launch[service.key] = true;
service.js();
trigger(window, service.key + "_loaded");
}
this.state[service.key] = true;
} else if (isDenied) {
if (typeof service.fallback === "function") {
service.fallback(this.lang);
}
this.state[service.key] = false;
} else if (!isResponded) {
create(service.key, "wait", this.parameters);
if (typeof service.fallback === "function") {
service.fallback(this.lang);
}
if (service.lazyConsent !== true) {
openAlert();
}
}
checkCount(service.key, service, this.lang);
trigger(window, service.key + "_added");
}
public cleanArray(arr: string[]): string[] {
const s = this.services;
const len = arr.length;
let out = [];
const obj: { [key: string]: boolean } = {};
for (let i = 0; i < len; i += 1) {
if (!obj[arr[i]]) {
obj[arr[i]] = true;
if (s[arr[i]] !== undefined) {
out.push(arr[i]);
}
}
}
out = out.sort(function (a, b) {
if (s[a].type + s[a].key > s[b].type + s[b].key) {
return 1;
}
if (s[a].type + s[a].key < s[b].type + s[b].key) {
return -1;
}
return 0;
});
return out;
}
public closePanel(): void {
closePanel(this);
}
public openPanel(): void {
openPanel(this);
}
public respondEffect(key: string, status: boolean): void {
respondEffect(key, status, this);
}
public respondAll(status: boolean, closePanelAfter?: boolean): void {
respondAll(status, this, this.parameters);
if (closePanelAfter) {
closePanel(this);
}
}
public respond(el: HTMLElement, evt: MouseEvent): void {
respond(el, this, this.parameters, evt);
}
public activate(id: string): void {
activate(id, this, this.parameters);
}
public isActivated(id: string): boolean {
return isActivated(id, this);
}
public toggle(id: string, closeClass: string): void {
toggle(id, closeClass);
}
public alertOpenPanel(): void {
openPanel(this);
trigger(window, "tac.alert_outcome", { outcome: "personalize" });
}
public alertRespondAll(status: boolean): void {
respondAll(status, this, this.parameters);
trigger(window, "tac.alert_outcome", { outcome: status === true ? "acceptAll" : "denyAll" });
}
}
export const GDPRConsent = new GDPRConsentInstance();