Files
Webklar-Kundenbereich/analytics/client/core.js
T

111 lines
4.0 KiB
JavaScript

// Reine Funktionen des Analyse-Skripts (ohne DOM-Zugriff ausser an uebergebenen Objekten).
// Wird von w.js/h.js importiert; build.js fuegt alles zu je einer Datei ohne Import zusammen.
export const CONSENT_KEY = 'wk-consent';
export const CONSENT_MAX_AGE = 183 * 86400000;
export function vpClass(width) {
const w = Number(width);
if (w < 768) return 'm';
if (w < 1024) return 't';
return 'd';
}
export function cleanPath(href) {
let p = '/';
const h = String(href == null ? '' : href);
if (h.charAt(0) === '/') {
p = h.replace(/[?#].*$/, '') || '/'; // wie location.pathname, auch '//foo'
} else {
try { p = new URL(h, 'http://x.invalid/').pathname || '/'; } catch (e) { /* bleibt '/' */ }
}
try { p = decodeURIComponent(p); } catch (e) { /* kaputtes Encoding: roh lassen */ }
if (p.charAt(0) !== '/') p = '/' + p;
return p.slice(0, 300);
}
const stripWww = (h) => String(h).toLowerCase().replace(/^www\./, '');
export function refHost(referrer, ownHost) {
if (!referrer) return '';
try {
const u = new URL(String(referrer));
if (u.protocol !== 'http:' && u.protocol !== 'https:') return '';
const h = u.hostname.toLowerCase();
if (!h || stripWww(h) === stripWww(ownHost || '')) return '';
return h;
} catch (e) { return ''; }
}
// Nur Tag-Namen mit :nth-of-type, keine IDs/Klassen (koennten personenbezogene Texte enthalten).
export function cssPath(el) {
const parts = [];
let n = el;
while (n && n.tagName && parts.length < 6) {
const tag = String(n.tagName).toLowerCase().replace(/[^a-z0-9-]/g, '');
if (!tag) break;
let idx = 1;
for (let s = n.previousElementSibling; s; s = s.previousElementSibling) {
if (String(s.tagName).toLowerCase() === String(n.tagName).toLowerCase()) idx++;
}
parts.unshift(tag + ':nth-of-type(' + idx + ')');
if (tag === 'body' || tag === 'html') break;
n = n.parentNode;
}
let out = parts.join(' > ');
while (out.length > 200 && parts.length > 1) { parts.shift(); out = parts.join(' > '); }
return out.slice(0, 200);
}
function parseHref(a) {
const href = a && (typeof a.href === 'string' && a.href ? a.href : (a.getAttribute && a.getAttribute('href'))) || '';
return String(href).trim();
}
export function linkHost(a) {
try { return new URL(parseHref(a)).hostname.toLowerCase(); } catch (e) { return ''; }
}
export function contactKind(a, ownHost) {
const href = parseHref(a);
if (!href) return null;
const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(href);
if (!scheme) return null; // relativ, Anker
const s = scheme[1].toLowerCase();
if (s === 'tel') return 'tel';
if (s === 'mailto') return 'mail';
if (s === 'whatsapp') return 'wa';
if (s !== 'http' && s !== 'https') return null;
const host = linkHost(a);
if (!host) return null;
if (host === 'wa.me' || host === 'api.whatsapp.com') return 'wa';
const own = ownHost == null ? (typeof location !== 'undefined' ? location.hostname : '') : ownHost;
return stripWww(host) === stripWww(own || '') ? null : 'out';
}
export function readConsent(storage, now) {
try {
const raw = storage.getItem(CONSENT_KEY);
if (!raw) return null;
const c = JSON.parse(raw);
if (!c || c.v !== 1 || (c.a !== 'accept' && c.a !== 'reject') || typeof c.t !== 'number' || !isFinite(c.t)) return null;
const age = now - c.t;
if (age < 0 || age >= CONSENT_MAX_AGE) return null;
return { v: 1, a: c.a, t: c.t };
} catch (e) { return null; }
}
export function contrastOk(hex) {
if (typeof hex !== 'string' || !/^#[0-9a-f]{6}$/i.test(hex)) return false;
const lin = (i) => {
const c = parseInt(hex.slice(1 + i * 2, 3 + i * 2), 16) / 255;
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
};
const L = 0.2126 * lin(0) + 0.7152 * lin(1) + 0.0722 * lin(2);
return 1.05 / (L + 0.05) >= 4.5;
}
export function escapeHtml(s) {
return String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
}