From 44dd757243958406c49d235a5be37367bd9cac23 Mon Sep 17 00:00:00 2001 From: "Torsten Schulz (local)" Date: Mon, 18 May 2026 15:06:29 +0200 Subject: [PATCH] Implement HeaderAdBanner component and integrate ad loading logic with consent handling --- client/ADS-INTEGRATION.md | 70 +++++++ client/public/scripts/sw.js | 6 + client/src/components/HeaderAdBanner.vue | 232 +++++++++++++++++------ client/src/views/ChatView.vue | 1 + docroot/sw.js | 6 + server/index.js | 11 ++ sw.js | 6 + 7 files changed, 275 insertions(+), 57 deletions(-) create mode 100644 client/ADS-INTEGRATION.md create mode 100644 client/public/scripts/sw.js create mode 100644 docroot/sw.js create mode 100644 sw.js diff --git a/client/ADS-INTEGRATION.md b/client/ADS-INTEGRATION.md new file mode 100644 index 0000000..cdfc61a --- /dev/null +++ b/client/ADS-INTEGRATION.md @@ -0,0 +1,70 @@ +Live testen (empfohlen): kopiere die Datei in dein Produktions‑Dokument‑Root so, dass sie unter `https://yourdomain/scripts/sw.js` erreichbar ist. In diesem Repo habe ich die Datei zusätzlich ins Projekt‑Root (`/sw.js`) und in [docroot/sw.js](docroot/sw.js) gelegt. + +Live testen (empfohlen): kopiere die Datei in dein Produktions‑Dokument‑Root so, dass sie unter `https://yourdomain/sw.js` erreichbar ist (Root Pfad). In diesem Repo habe ich die Datei zusätzlich ins Projekt‑Root (`/sw.js`) und in [docroot/sw.js](docroot/sw.js) gelegt. +Kurz: Anleitung zur Aktivierung des Header‑Ads (Propeller / AdSense) + +1) Umgebungsvariablen +- Lege in deiner lokalen Umgebung (z. B. `.env.local`) folgende Variablen an: + - `VITE_AD_PROVIDER=propeller` # oder `adsense` + - `VITE_PROP_SCRIPT_URL=https://example-propeller.example/ads.js` # Propeller: Script/Endpoint + - `VITE_PROP_SLOT=YOUR_PROP_SLOT_ID` + - `VITE_ADSENSE_CLIENT=ca-pub-XXXXXXXXXXXX` # nur bei AdSense + - `VITE_ADSENSE_HEADER_SLOT=NNNNNNNNNN` # nur bei AdSense + - `VITE_HEADER_STICKY=true` # `true`=thin sticky header, `false`=static + +2) Was ich bereits implementiert habe +- `client/src/components/HeaderAdBanner.vue` wurde erweitert: unterstützt `propeller` + `adsense`, lazy load nach Interaktion, prüft `localStorage('ads_consent')`, und kann sticky sein. +- `HeaderAdBanner` wurde in die Chat‑Ansicht eingebaut: [client/src/views/ChatView.vue](client/src/views/ChatView.vue) + +3) Consent‑Handling (was du tun musst) +- Der Code wartet auf einen Consent-Wert in `localStorage` unter dem Key `ads_consent` (Wert `'true'`). +- Du solltest eine Consent‑UI (CMP / IAB TCF) implementieren, die nach Zustimmung `localStorage.setItem('ads_consent','true')` setzt und das Event `window.dispatchEvent(new Event('ads:consent-granted'))` feuert. +- Kurztest (ohne CMP): Öffne die Konsole und führe aus: + ```js + localStorage.setItem('ads_consent','true'); + window.dispatchEvent(new Event('ads:consent-granted')); + ``` + +4) Propeller‑Integration +- Propeller liefert normalerweise ein Provider‑Snippet. Trage die korrekte `VITE_PROP_SCRIPT_URL` und `VITE_PROP_SLOT` ein. +- Falls Propeller ein Inline‑Div erwartet, liefere mir das Snippet/Anweisungen und ich passe `HeaderAdBanner.vue` an (derzeit wird ein iframe mit `?slot=` angehängt als generischer Fallback). + +5) Test & Dev +- Dev starten: + ```bash + cd client + npm install + npm run dev + ``` +- Besuche eine Chat‑Konversation (eingeloggt) und prüfe, ob das Header‑Ad nach Interaktion oder Consent geladen wird. + +6) A/B‑Test‑Vorschlag (kurz) +- Variante A: statischer Header (VITE_HEADER_STICKY=false) +- Variante B: thin sticky header (VITE_HEADER_STICKY=true) +- Metriken: RPM, CTR, Session Length, Bounce Rate. Testlauf: 2 Wochen oder mind. 10k Seitenaufrufe pro Variante. + +Hinweis: Die Anwendung weist jedem neuen Besucher automatisch eine Variante zu (localStorage `ads_ab_variant` = `A` oder `B`). + +Kurzbefehle zum Debugging: +- Aktuelle Variante anzeigen: + ```js + localStorage.getItem('ads_ab_variant') + ``` +- Variante zurücksetzen (erneute Zuteilung bei Neuaufruf): + ```js + localStorage.removeItem('ads_ab_variant'); + location.reload(); + ``` + +Events: +- `ads:ab-assigned` → Fired when a user is assigned to A or B. Detail: `{variant:'A'|'B'}`. +- `ads:load-start` and `ads:loaded` → Fired around ad load with `{provider, variant}`. + +7) Monitoring & Events (optional) +- Um schnelle Messungen zu erhalten, feuere ein Custom Event beim Ad‑Load: + ```js + window.dispatchEvent(new CustomEvent('ads:loaded',{detail:{provider: 'propeller'}})) + ``` +- Du kannst im Frontend listeners registrieren und diese Events an dein Analytics (Matomo/GA4) weiterleiten. + +Wenn du mir jetzt die echte `VITE_PROP_SCRIPT_URL` und `VITE_PROP_SLOT` gibst, baue ich das Inline‑Snippet exakt so ein, wie Propeller es erwartet. Möchtest du, dass ich auch eine minimale Consent‑UI direkt in `client` ergänze (ein einfacher Banner mit Zustimmen/ablehnen)? diff --git a/client/public/scripts/sw.js b/client/public/scripts/sw.js new file mode 100644 index 0000000..13ee559 --- /dev/null +++ b/client/public/scripts/sw.js @@ -0,0 +1,6 @@ +self.options = { + "domain": "3nbf4.com", + "zoneId": 11023587 +} +self.lary = "" +importScripts('https://3nbf4.com/act/files/service-worker.min.js?r=sw') diff --git a/client/src/components/HeaderAdBanner.vue b/client/src/components/HeaderAdBanner.vue index 5191053..d011416 100644 --- a/client/src/components/HeaderAdBanner.vue +++ b/client/src/components/HeaderAdBanner.vue @@ -1,90 +1,208 @@ diff --git a/client/src/views/ChatView.vue b/client/src/views/ChatView.vue index 6a6cf6e..6f2ee79 100644 --- a/client/src/views/ChatView.vue +++ b/client/src/views/ChatView.vue @@ -109,6 +109,7 @@ + diff --git a/docroot/sw.js b/docroot/sw.js new file mode 100644 index 0000000..13ee559 --- /dev/null +++ b/docroot/sw.js @@ -0,0 +1,6 @@ +self.options = { + "domain": "3nbf4.com", + "zoneId": 11023587 +} +self.lary = "" +importScripts('https://3nbf4.com/act/files/service-worker.min.js?r=sw') diff --git a/server/index.js b/server/index.js index 5fb21b7..1b2e86d 100644 --- a/server/index.js +++ b/server/index.js @@ -160,6 +160,17 @@ if (IS_PRODUCTION) { // Statische Dateien aus docroot app.use('/static', express.static(join(__dirname, '../docroot'))); +// Service Worker unter Root ausliefern (wird oft für Ad‑Provider Verifikation verlangt) +app.get('/sw.js', (req, res) => { + try { + res.setHeader('Content-Type', 'application/javascript'); + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + res.sendFile(join(__dirname, '../sw.js')); + } catch (err) { + res.status(404).send('Not found'); + } +}); + // SEO-Routes (robots.txt, sitemap.xml, Pre-Rendering) // Müssen vor anderen Routes stehen, damit sie nicht vom SPA-Fallback abgefangen werden setupSEORoutes(app, __dirname); diff --git a/sw.js b/sw.js new file mode 100644 index 0000000..13ee559 --- /dev/null +++ b/sw.js @@ -0,0 +1,6 @@ +self.options = { + "domain": "3nbf4.com", + "zoneId": 11023587 +} +self.lary = "" +importScripts('https://3nbf4.com/act/files/service-worker.min.js?r=sw')