peertube-plugin-cookie-consent
Version:
GDPR/DSGVO compliant cookie consent banner for PeerTube instances with script blocking functionality
697 lines (693 loc) • 22.6 kB
JavaScript
// PeerTube Cookie Consent Plugin - ES Module
// node_modules/universal-cookie/esm/index.mjs
var cookie = {};
var hasRequiredCookie;
function requireCookie() {
if (hasRequiredCookie) return cookie;
hasRequiredCookie = 1;
cookie.parse = parse;
cookie.serialize = serialize;
var __toString = Object.prototype.toString;
var __hasOwnProperty = Object.prototype.hasOwnProperty;
var cookieNameRegExp = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
var cookieValueRegExp = /^("?)[\u0021\u0023-\u002B\u002D-\u003A\u003C-\u005B\u005D-\u007E]*\1$/;
var domainValueRegExp = /^([.]?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)([.][a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i;
var pathValueRegExp = /^[\u0020-\u003A\u003D-\u007E]*$/;
function parse(str, opt) {
if (typeof str !== "string") {
throw new TypeError("argument str must be a string");
}
var obj = {};
var len = str.length;
if (len < 2) return obj;
var dec = opt && opt.decode || decode;
var index = 0;
var eqIdx = 0;
var endIdx = 0;
do {
eqIdx = str.indexOf("=", index);
if (eqIdx === -1) break;
endIdx = str.indexOf(";", index);
if (endIdx === -1) {
endIdx = len;
} else if (eqIdx > endIdx) {
index = str.lastIndexOf(";", eqIdx - 1) + 1;
continue;
}
var keyStartIdx = startIndex(str, index, eqIdx);
var keyEndIdx = endIndex(str, eqIdx, keyStartIdx);
var key = str.slice(keyStartIdx, keyEndIdx);
if (!__hasOwnProperty.call(obj, key)) {
var valStartIdx = startIndex(str, eqIdx + 1, endIdx);
var valEndIdx = endIndex(str, endIdx, valStartIdx);
if (str.charCodeAt(valStartIdx) === 34 && str.charCodeAt(valEndIdx - 1) === 34) {
valStartIdx++;
valEndIdx--;
}
var val = str.slice(valStartIdx, valEndIdx);
obj[key] = tryDecode(val, dec);
}
index = endIdx + 1;
} while (index < len);
return obj;
}
function startIndex(str, index, max) {
do {
var code = str.charCodeAt(index);
if (code !== 32 && code !== 9) return index;
} while (++index < max);
return max;
}
function endIndex(str, index, min) {
while (index > min) {
var code = str.charCodeAt(--index);
if (code !== 32 && code !== 9) return index + 1;
}
return min;
}
function serialize(name, val, opt) {
var enc = opt && opt.encode || encodeURIComponent;
if (typeof enc !== "function") {
throw new TypeError("option encode is invalid");
}
if (!cookieNameRegExp.test(name)) {
throw new TypeError("argument name is invalid");
}
var value = enc(val);
if (!cookieValueRegExp.test(value)) {
throw new TypeError("argument val is invalid");
}
var str = name + "=" + value;
if (!opt) return str;
if (null != opt.maxAge) {
var maxAge = Math.floor(opt.maxAge);
if (!isFinite(maxAge)) {
throw new TypeError("option maxAge is invalid");
}
str += "; Max-Age=" + maxAge;
}
if (opt.domain) {
if (!domainValueRegExp.test(opt.domain)) {
throw new TypeError("option domain is invalid");
}
str += "; Domain=" + opt.domain;
}
if (opt.path) {
if (!pathValueRegExp.test(opt.path)) {
throw new TypeError("option path is invalid");
}
str += "; Path=" + opt.path;
}
if (opt.expires) {
var expires = opt.expires;
if (!isDate(expires) || isNaN(expires.valueOf())) {
throw new TypeError("option expires is invalid");
}
str += "; Expires=" + expires.toUTCString();
}
if (opt.httpOnly) {
str += "; HttpOnly";
}
if (opt.secure) {
str += "; Secure";
}
if (opt.partitioned) {
str += "; Partitioned";
}
if (opt.priority) {
var priority = typeof opt.priority === "string" ? opt.priority.toLowerCase() : opt.priority;
switch (priority) {
case "low":
str += "; Priority=Low";
break;
case "medium":
str += "; Priority=Medium";
break;
case "high":
str += "; Priority=High";
break;
default:
throw new TypeError("option priority is invalid");
}
}
if (opt.sameSite) {
var sameSite = typeof opt.sameSite === "string" ? opt.sameSite.toLowerCase() : opt.sameSite;
switch (sameSite) {
case true:
str += "; SameSite=Strict";
break;
case "lax":
str += "; SameSite=Lax";
break;
case "strict":
str += "; SameSite=Strict";
break;
case "none":
str += "; SameSite=None";
break;
default:
throw new TypeError("option sameSite is invalid");
}
}
return str;
}
function decode(str) {
return str.indexOf("%") !== -1 ? decodeURIComponent(str) : str;
}
function isDate(val) {
return __toString.call(val) === "[object Date]";
}
function tryDecode(str, decode2) {
try {
return decode2(str);
} catch (e) {
return str;
}
}
return cookie;
}
var cookieExports = requireCookie();
function hasDocumentCookie() {
const testingValue = typeof global === "undefined" ? void 0 : global.TEST_HAS_DOCUMENT_COOKIE;
if (typeof testingValue === "boolean") {
return testingValue;
}
return typeof document === "object" && typeof document.cookie === "string";
}
function parseCookies(cookies2) {
if (typeof cookies2 === "string") {
return cookieExports.parse(cookies2);
} else if (typeof cookies2 === "object" && cookies2 !== null) {
return cookies2;
} else {
return {};
}
}
function readCookie(value, options = {}) {
const cleanValue = cleanupCookieValue(value);
if (!options.doNotParse) {
try {
return JSON.parse(cleanValue);
} catch (e) {
}
}
return value;
}
function cleanupCookieValue(value) {
if (value && value[0] === "j" && value[1] === ":") {
return value.substr(2);
}
return value;
}
var Cookies = class {
constructor(cookies2, defaultSetOptions = {}) {
this.changeListeners = [];
this.HAS_DOCUMENT_COOKIE = false;
this.update = () => {
if (!this.HAS_DOCUMENT_COOKIE) {
return;
}
const previousCookies = this.cookies;
this.cookies = cookieExports.parse(document.cookie);
this._checkChanges(previousCookies);
};
const domCookies = typeof document === "undefined" ? "" : document.cookie;
this.cookies = parseCookies(cookies2 || domCookies);
this.defaultSetOptions = defaultSetOptions;
this.HAS_DOCUMENT_COOKIE = hasDocumentCookie();
}
_emitChange(params) {
for (let i = 0; i < this.changeListeners.length; ++i) {
this.changeListeners[i](params);
}
}
_checkChanges(previousCookies) {
const names = new Set(Object.keys(previousCookies).concat(Object.keys(this.cookies)));
names.forEach((name) => {
if (previousCookies[name] !== this.cookies[name]) {
this._emitChange({
name,
value: readCookie(this.cookies[name])
});
}
});
}
_startPolling() {
this.pollingInterval = setInterval(this.update, 300);
}
_stopPolling() {
if (this.pollingInterval) {
clearInterval(this.pollingInterval);
}
}
get(name, options = {}) {
if (!options.doNotUpdate) {
this.update();
}
return readCookie(this.cookies[name], options);
}
getAll(options = {}) {
if (!options.doNotUpdate) {
this.update();
}
const result = {};
for (let name in this.cookies) {
result[name] = readCookie(this.cookies[name], options);
}
return result;
}
set(name, value, options) {
if (options) {
options = Object.assign(Object.assign({}, this.defaultSetOptions), options);
} else {
options = this.defaultSetOptions;
}
const stringValue = typeof value === "string" ? value : JSON.stringify(value);
this.cookies = Object.assign(Object.assign({}, this.cookies), { [name]: stringValue });
if (this.HAS_DOCUMENT_COOKIE) {
document.cookie = cookieExports.serialize(name, stringValue, options);
}
this._emitChange({ name, value, options });
}
remove(name, options) {
const finalOptions = options = Object.assign(Object.assign(Object.assign({}, this.defaultSetOptions), options), { expires: new Date(1970, 1, 1, 0, 0, 1), maxAge: 0 });
this.cookies = Object.assign({}, this.cookies);
delete this.cookies[name];
if (this.HAS_DOCUMENT_COOKIE) {
document.cookie = cookieExports.serialize(name, "", finalOptions);
}
this._emitChange({ name, value: void 0, options });
}
addChangeListener(callback) {
this.changeListeners.push(callback);
if (this.HAS_DOCUMENT_COOKIE && this.changeListeners.length === 1) {
if (typeof window === "object" && "cookieStore" in window) {
window.cookieStore.addEventListener("change", this.update);
} else {
this._startPolling();
}
}
}
removeChangeListener(callback) {
const idx = this.changeListeners.indexOf(callback);
if (idx >= 0) {
this.changeListeners.splice(idx, 1);
}
if (this.HAS_DOCUMENT_COOKIE && this.changeListeners.length === 0) {
if (typeof window === "object" && "cookieStore" in window) {
window.cookieStore.removeEventListener("change", this.update);
} else {
this._stopPolling();
}
}
}
};
// client/common-client-plugin.js
var cookies = new Cookies();
function getConsentCategories() {
return cookies.get("cookieConsentCategories") || null;
}
function setConsentCategories(categories, days) {
cookies.set("cookieConsentCategories", categories, {
path: "/",
maxAge: (days || 180) * 24 * 60 * 60,
secure: location.protocol === "https:",
sameSite: "lax"
});
}
function injectCss(css) {
const style = document.createElement("style");
style.innerHTML = css;
document.head.appendChild(style);
}
function injectScripts(scripts, allowedCategories) {
allowedCategories = allowedCategories || [];
for (let i = 0; i < scripts.length; i++) {
const s = scripts[i];
if (!s.category || allowedCategories.indexOf(s.category) !== -1) {
const el = document.createElement("script");
el.src = s.src;
if (s.async) el.async = true;
if (s.defer) el.defer = true;
document.head.appendChild(el);
}
}
}
function createElementFromMarkdown(markdown) {
const container = document.createElement("div");
container.innerHTML = markdown.replace(/\n/g, "<br>").replace(/\[(.*?)\]\((.*?)\)/g, '<a href="$2" target="_blank" rel="noopener">$1</a>');
return container;
}
function safeParse(jsonInput) {
try {
return JSON.parse(jsonInput);
} catch (e) {
console.error("[Cookie Consent Plugin] Fehler beim Parsen von Skripten:", e);
return [];
}
}
function applyConsent(settings) {
const scripts = safeParse(settings.scripts);
const categories = getConsentCategories();
if (!categories) return;
const allowed = [];
const keys = Object.keys(categories);
for (let i = 0; i < keys.length; i++) {
if (categories[keys[i]] === true) {
allowed.push(keys[i]);
}
}
injectScripts(scripts, allowed);
}
function darkenColor(color, percent) {
const num = parseInt(color.replace("#", ""), 16);
const amt = Math.round(2.55 * percent);
const R = (num >> 16) - amt;
const G = (num >> 8 & 255) - amt;
const B = (num & 255) - amt;
return "#" + (16777216 + (R > 255 ? 255 : R < 0 ? 0 : R) * 65536 + (G > 255 ? 255 : G < 0 ? 0 : G) * 256 + (B > 255 ? 255 : B < 0 ? 0 : B)).toString(16).slice(1);
}
function showCookieSettings(settings) {
const existing = document.getElementById("cookie-settings-modal");
if (existing) existing.remove();
const overlay = document.createElement("div");
overlay.id = "cookie-settings-modal";
overlay.style.cssText = "position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.7);z-index:99999;display:flex;align-items:center;justify-content:center;";
const borderRadius = parseInt(settings.modalBorderRadius || "12");
const accentColor = settings.modalAccentColor || "#007bff";
const accentHover = darkenColor(accentColor, 15);
const modal = document.createElement("div");
modal.style.cssText = `background:#fff;padding:2em;border-radius:${borderRadius}px;max-width:450px;text-align:left;font-family:Arial,sans-serif;color:#333;box-shadow:0 8px 32px rgba(0,0,0,0.3);`;
const currentPrefs = getConsentCategories() || { funktional: true, statistik: false, marketing: false };
const modalContent = `
<h2 style="color:#333;margin-top:0;margin-bottom:1.5em;font-size:1.4em;">Cookie-Einstellungen</h2>
<style>
.cookie-option {
display: flex;
align-items: center;
margin: 15px 0;
padding: 8px 0;
}
.cookie-checkbox {
width: 18px;
height: 18px;
margin-right: 12px;
accent-color: ${accentColor};
transform: scale(1.2);
}
.cookie-label {
color: #333 !important;
font-size: 16px;
font-weight: 500;
cursor: pointer;
user-select: none;
}
.cookie-buttons {
margin-top: 2em;
padding-top: 1em;
border-top: 1px solid #eee;
}
.cookie-btn-primary {
background: ${accentColor};
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
margin-right: 12px;
transition: background 0.2s;
}
.cookie-btn-primary:hover {
background: ${accentHover};
}
.cookie-btn-secondary {
background: #6c757d;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background 0.2s;
}
.cookie-btn-secondary:hover {
background: #545b62;
}
</style>
<form id="cookie-categories-form">
<div class="cookie-option">
<input type="checkbox" class="cookie-checkbox" name="funktional" id="funktional" ${currentPrefs.funktional ? "checked" : ""}>
<label class="cookie-label" for="funktional">Funktionalit\xE4t</label>
</div>
<div class="cookie-option">
<input type="checkbox" class="cookie-checkbox" name="statistik" id="statistik" ${currentPrefs.statistik ? "checked" : ""}>
<label class="cookie-label" for="statistik">Statistik</label>
</div>
<div class="cookie-option">
<input type="checkbox" class="cookie-checkbox" name="marketing" id="marketing" ${currentPrefs.marketing ? "checked" : ""}>
<label class="cookie-label" for="marketing">Marketing</label>
</div>
<div class="cookie-buttons">
<button type="submit" class="cookie-btn-primary">Speichern</button>
<button type="button" id="cancelCookieSettings" class="cookie-btn-secondary">Abbrechen</button>
</div>
</form>
`;
modal.innerHTML = modalContent;
overlay.appendChild(modal);
document.body.appendChild(overlay);
document.getElementById("cancelCookieSettings").onclick = function() {
overlay.remove();
};
document.getElementById("cookie-categories-form").onsubmit = function(e) {
e.preventDefault();
const form = new FormData(e.target);
const prefs = {
funktional: form.has("funktional"),
statistik: form.has("statistik"),
marketing: form.has("marketing")
};
setConsentCategories(prefs, 180);
overlay.remove();
location.reload();
};
}
function getManageButtonIcon(style) {
switch (style) {
case "icon-gear":
return "\u2699\uFE0F";
case "icon-cookie":
return "\u{1F36A}";
case "icon-settings":
return "\u{1F4CB}";
default:
return "\u2699\uFE0F";
}
}
function getPositionStyle(position) {
switch (position) {
case "bottom-left":
return "bottom: 20px; left: 20px;";
case "top-right":
return "top: 20px; right: 20px;";
case "top-left":
return "top: 20px; left: 20px;";
default:
return "bottom: 20px; right: 20px;";
}
}
function addManageButton(settings) {
if (document.getElementById("cookie-manage-btn")) return;
const buttonStyle = settings.manageButtonStyle || "icon-gear";
const buttonColor = settings.manageButtonColor || "#007bff";
const buttonPosition = settings.manageButtonPosition || "bottom-right";
const buttonColorHover = darkenColor(buttonColor, 15);
const manageBtn = document.createElement("button");
manageBtn.id = "cookie-manage-btn";
manageBtn.title = "Cookie-Einstellungen verwalten";
if (buttonStyle.startsWith("icon-")) {
manageBtn.innerHTML = getManageButtonIcon(buttonStyle);
manageBtn.style.cssText = `
position: fixed;
${getPositionStyle(buttonPosition)}
width: 48px;
height: 48px;
font-size: 18px;
z-index: 9999;
background: ${buttonColor};
color: white;
border: none;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 4px 12px ${buttonColor}40;
transition: all 0.3s ease;
backdrop-filter: blur(10px);
`;
} else {
manageBtn.textContent = "Cookie-Einstellungen";
const isSmall = buttonStyle === "text-small";
manageBtn.style.cssText = `
position: fixed;
${getPositionStyle(buttonPosition)}
font-size: ${isSmall ? "12px" : "14px"};
z-index: 9999;
background: ${buttonColor};
color: white;
border: none;
padding: ${isSmall ? "6px 12px" : "8px 16px"};
border-radius: 4px;
cursor: pointer;
box-shadow: 0 2px 8px ${buttonColor}40;
transition: all 0.3s ease;
`;
}
manageBtn.onmouseenter = function() {
this.style.background = buttonColorHover;
if (buttonStyle.startsWith("icon-")) {
this.style.transform = "scale(1.1)";
}
};
manageBtn.onmouseleave = function() {
this.style.background = buttonColor;
if (buttonStyle.startsWith("icon-")) {
this.style.transform = "scale(1)";
}
};
manageBtn.onclick = function() {
showCookieSettings(settings);
};
document.body.appendChild(manageBtn);
}
function createStyledButton(text, color, onClick) {
const button = document.createElement("button");
const hoverColor = darkenColor(color, 15);
button.textContent = text;
button.style.cssText = `
margin: 0 8px;
padding: 10px 20px;
background: ${color};
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: background 0.2s;
`;
button.onmouseenter = function() {
this.style.background = hoverColor;
};
button.onmouseleave = function() {
this.style.background = color;
};
button.onclick = onClick;
return button;
}
function register({ registerHook, peertubeHelpers }) {
console.log("[Cookie Consent Plugin] Register function called");
if (!registerHook || !peertubeHelpers) {
console.error("[Cookie Consent Plugin] Missing required options");
return;
}
console.log("[Cookie Consent Plugin] Registering hooks...");
registerHook({
target: "action:application.init",
handler: function() {
console.log("[Cookie Consent Plugin] Application initialized!");
setTimeout(function() {
initCookieBanner(peertubeHelpers);
}, 1e3);
}
});
}
function initCookieBanner(peertubeHelpers) {
console.log("[Cookie Consent Plugin] Initializing cookie banner...");
peertubeHelpers.getSettings().then(function(settings) {
console.log("[Cookie Consent Plugin] Settings loaded:", settings);
if (!settings || !settings.enableConsentBanner) {
console.log("[Cookie Consent Plugin] Banner disabled in settings");
return;
}
const categories = getConsentCategories();
if (categories) {
console.log("[Cookie Consent Plugin] Consent already given:", categories);
applyConsent(settings);
addManageButton(settings);
return;
}
console.log("[Cookie Consent Plugin] Creating banner...");
if (settings.customCss) {
injectCss(settings.customCss);
}
const bannerBgColor = settings.bannerBackgroundColor || "#000000";
const bannerTextColor = settings.bannerTextColor || "#ffffff";
const wrapper = document.createElement("div");
wrapper.className = "cookie-banner";
wrapper.style.cssText = `
position: fixed;
bottom: 0;
left: 0;
width: 100%;
background: ${bannerBgColor};
color: ${bannerTextColor};
padding: 1.5em;
text-align: center;
z-index: 9999;
font-family: Arial, sans-serif;
box-shadow: 0 -4px 16px rgba(0,0,0,0.3);
`;
const content = createElementFromMarkdown(settings.bannerMarkdown || "Diese Website verwendet Cookies.");
content.style.cssText = "margin-bottom: 15px; line-height: 1.4; font-size: 15px;";
const btnAcceptAll = createStyledButton(
"Alle akzeptieren",
settings.buttonAcceptColor || "#28a745",
function() {
const allCategories = { funktional: true, statistik: true, marketing: true };
setConsentCategories(allCategories, 180);
wrapper.remove();
applyConsent(settings);
addManageButton(settings);
}
);
const btnEssenzielle = createStyledButton(
"Nur essentielle Cookies",
settings.buttonEssentialColor || "#6c757d",
function() {
const essentialOnly = { funktional: true, statistik: false, marketing: false };
setConsentCategories(essentialOnly, 180);
wrapper.remove();
addManageButton(settings);
}
);
const btnSettings = createStyledButton(
"Einstellungen",
settings.buttonSettingsColor || "#007bff",
function() {
showCookieSettings(settings);
}
);
wrapper.appendChild(content);
wrapper.appendChild(btnAcceptAll);
wrapper.appendChild(btnEssenzielle);
wrapper.appendChild(btnSettings);
document.body.appendChild(wrapper);
console.log("[Cookie Consent Plugin] Banner displayed successfully!");
}).catch(function(error) {
console.error("[Cookie Consent Plugin] Error loading settings:", error);
});
}
export {
register
};
/*! Bundled license information:
universal-cookie/esm/index.mjs:
(*!
* cookie
* Copyright(c) 2012-2014 Roman Shtylman
* Copyright(c) 2015 Douglas Christopher Wilson
* MIT Licensed
*)
*/