Skip to content

Instantly share code, notes, and snippets.

@mbaersch
Last active September 25, 2024 18:38
Show Gist options
  • Save mbaersch/2d1081377e5a6de4791a6d377a7a77c9 to your computer and use it in GitHub Desktop.
Save mbaersch/2d1081377e5a6de4791a6d377a7a77c9 to your computer and use it in GitHub Desktop.
Cross Domain Messages Demo Pages

DEMO: Messages für iFrame Vermessung

Einfache Demo zur Nutzung von Messages für die Kommunikation zwischen einem iFrame ("Frame Seite") und der beinhaltenden Seite ("Hauptseite") zum Zweck des zustimmungskonformen Trackings aus dem Kontext der Hauptseite heraus.

Mit dieser Methode ist es...

  • unnötig, einen Consent Dialog oder direktes Tracking innerhalb der Frame Seite zu betreiben
  • möglich, die Nachrichten der Frame Seite in der Hauptseite zu verarbeiten oder zu ignorieren, je nach Consent-Lage
  • technisch irrelevant, ob der iFrame eine Frame Seite der eigenen oder einer fremden Domain ausspielt. Solange CSP und Zustimmung das Laden der Inhalte im iFrame nicht verhindern, kann so ein Tracking ohne weitere technische Mittel nur über die Hauptseite implementiert werden

Dateien

Zum Beispiel gehören generell die Dateien

Wenn beide in einem gemeinsamen Ordner liegen, kann in der Konsole nachvollzogen werden, wie Nachrichten gesendet und empfangen werden, wenn die entsprechenden Buttons in der Frame Seite betätigt werden. Dies funktioniert freilich auch lokal.

Die zusätzliche Datei ga4-crossdomain-messages-iframe-demo.html zeigt zudem, wie statt der lokalen Hauptseite auch eine Demo Seite inkl. direkt integriertem GA4 Tagging (um das Beispiel einfach zu halten) die Nachrichten von einer Frame Seite (die dann auf einem anderen Webserver bereitgestellt werden kann, um Cross-Domain Verhalten zu testen) empfängt und in GA4 Events umwandelt.

Mit einer auf markus-baersch.de bereitgestellten Seite kann das (ohne GA4) auch mit Hilfe der Konsole des Browsers oder der Umgebung in einem Fiddle getestet werden.

Hinweis: Der hier verwendete Code ist absichtlich einfach gehalten und basiert auf einer direkten Implementierung der Messages im Code der Frame Seite. Wenn es dort bereits einen dataLayer gibt, kann dieser entweder generell auch als Message weitergeleitet werden oder es wird eine Kommunikation zwischen Hauptseite und Frame Seite etabliert, die nur dann die Messages aus dem dataLayer weiterleitet, wenn es eine Hauptseite gibt, die diese auch empfangen "will" und dazu bereit ist. Eine entsprechende Lösung unter Verwendung des GTM (die aber auch ohne Tag Manager implementiert werden kann) findet sich bei Simo Ahava im Blog (wo auch sonst).

<!doctype html><html lang="de">
<head>
<title>DEMO: GA4 iFrame + Messages</title>
<style>
body {font-family:arial;color:#fff;line-height:1.4em;background:#222;text-align:center}
#cnt {position: relative; max-width:1000px; margin: 10px auto; text-align:left; padding:1em}
a, h1, h2, h3, h4 {color:#ffa072; line-height:1em}
</style>
<!-- Standard Google tag (gtag.js) tracking code with active debugging -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-xxxxxxxx"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-xxxxxxx', {'debug_mode': true});
</script>
</head>
<body>
<div id="cnt">
<h1>Testseite - (externer) iFrame mit Messages DEMO</h1>
<p id="infotxt">Das hier ist noch Content der beinhaltenden Seite <b style="color:yellow">inkl. GA4 Tracking</b>. Es folgt der eingebundene iFrame mit der inneren Seite. Alle Nachrichten, die hier empfangen werden, werden in die Console geschrieben und <b style="color:yellow">als Event an GA4 gesendet</b>.</p>
<iframe src="https://www.third-party-domain.de/messages-demo-inner.html" style="width:100%;height:1000px;overflow-y:scroll"></iframe>
</div> <!--cnt -->
<script>
//Einfache Cross-Browser Variante des Empfangs:
function bindEvent(element, eventName, eventHandler) {
if (element.addEventListener){
element.addEventListener(eventName, eventHandler, false);
} else if (element.attachEvent) {
element.attachEvent('on' + eventName, eventHandler);
}
}
//"Kernfunktion" zum Auslesen aller als "iFrameTrackingMessage" markierten Nachrichten
//im der "vereinbarten" Objektstruktur mit "type" und "payload" als Schlüsseln
bindEvent(window, 'message', function (e) {
if (!e || !e.data || e.data.type !== "iFrameTrackingMessage" || !e.data.payload) return;
var pl = e.data.payload;
console.log("%cOUTER: %cNachricht emfangen.",
"font-weight:bold;color:yellow", "", e.data.payload);
var obj = {};
if (typeof(pl) === "string") try {
obj = JSON.parse(e.data.payload);
} catch(err) {
//kein JSON, Event ist nur ein normaler Wert, der als Eventname dient
obj.event = e.data.payload;
} else obj = pl;
if (obj && !obj.event) obj.event = "iFrameEvent";
//Senden der empfangenen (EEC) Nutzlast als GA4 Event
gtag('event', obj.event, obj.params || obj.ecommerce);
});
</script>
</body>
</html>
<!doctype html><html lang="de">
<head>
<title>DEMO: iFrame + Messages (Inner)</title>
<style>
body {font-family:arial;color:#fff;line-height:1.4em;background:#222;text-align:center}
#cnt {position: relative; max-width:1000px; margin: 10px auto; text-align:left; padding:1em}
a, h1, h2, h3, h4 {color:#ffa072; line-height:1em}
input[type="button"], button {padding:10px 20px; border:0;color:#fff;background:#1c8e1c;font-size:1.1em;border-radius:8px}
input[type="button"]:hover, button:hover {cursor:pointer; background:#20a520}
input {padding:3px;} fieldset{border:0}
input[type="text"]{width:25em; max-width:100%; border:1px solid #666; border-radius:4px; padding:5px; margin:0.2em 0 0.4em 0;}
</style>
</head>
<body>
<div id="cnt">
<span id="ue1"><h1>Testseite - iFrame Messages DEMO</h1></span>
<p id="infotxt">Wir sind hier im inneren iFrame.</p>
<p>Hier können Events ausgelöst und als Message an den Parent gesendet werden.</p>
<h3>Einfache Events senden</h3>
<p>Eventname ist "orderSuccess", es gibt keine weitere Nutzlast</p>
<input type="button" onclick="sendToParent('orderSuccess')" value='"orderSuccess" senden' />
<p>Oder: Beliebige Zeichenkette als Eventnamen oder gültigen JSON String eingeben und versenden mit Button.</p>
<b>String / Nutzlast</b><br>
<input id="evname" type="text" value="testPayload 123" />
<input type="button" onclick="sendToParent(document.getElementById('evname').value)" value="Senden" />
<h3>JSON Objekt senden</h3>
<p>Mit diesem Button wird ein Beispiel JSON Objekt erstellt und versendet. Es sieht so aus: </p>
<pre>
{
event:"JSON DEMO event",
params: {
step: 1,
value: 50,
title: "Ein Beispiel String"
}
}
</pre>
<input type="button" onclick="sendJSONDemo()" value="JSON senden" />
<h3>GA4 Purchase</h3>
<p>Eine GA4-Kaufabschluss Beispiel-Nutzlast aus der
<a href="https://developers.google.com/analytics/devguides/collection/ga4/ecommerce?client_type=gtm#make_a_purchase_or_issue_a_refund">
Google Hilfe</a> senden:</p>
<input type="button" onclick="sendGa4Purchase()" value="GA4 Purchase senden" />
</div> <!--cnt -->
<script>
//"Kernfunktion" zur Weitergabe beliebiger Nachrichten an den Parent mit einer
//eindeutigen Kennzeichnung als "iFrameTrackingMessage" in einer hier frei gewählten
//Objektstruktur mit "type" und "payload" als Schlüsseln. Der Empfänger muss das
//Format also kennen und entsprechend auslesen
function sendToParent(msg) {
if (!msg) return;
var payload = {type: "iFrameTrackingMessage", payload: msg};
window.parent.postMessage(payload, '*');
console.log("%cINNER: %cNachricht versendet.",
"font-weight:bold;color:green", "", msg);
}
/****************************/
//exemplarische Erkennung, ob im iFrame:
var isInFrame = true;
try {
isInFrame = window.self !== window.top;
} catch (e) {
console.warn("INNER: Status unklar");
}
if (!isInFrame) {
console.warn("INNER: nicht in iFrame");
document.getElementById("infotxt").innerHTML =
"<strong style='color:red'>Achtung: </strong> "+
"diese Seite wurde nicht in einem iFrame geladen.";
} else {
document.getElementById("ue1").innerHTML =
"<h2>Beispiel iFrame Content</h2>";
//Aufruf im iFrame als Beispiel-Event "process_step_1" senden:
sendToParent("process_step_1");
}
/****************************/
//Beispiel: Senden eines beliebigen JSON codierten Events mit Parametern
//als "params", die beim Empfänger ausgelesen werden können
function sendJSONDemo() {
sendToParent(JSON.stringify({
event:"JSON DEMO event",
params: {
step: 1,
value: 50,
title: "Ein Beispiel String"
}
}));
}
//Beispiel: EEC Event als Objekt ohne Serialisierung als JSON
function sendGa4Purchase() {
sendToParent({
event: "purchase",
ecommerce: {
transaction_id: (Math.round(Math.random()*1000)).toString(),
affiliation: "Google Merchandise Store",
value: 25.42,
tax: 4.90,
shipping: 5.99,
currency: "USD",
coupon: "SUMMER_SALE",
items: [
{
item_id: "SKU_12345",
item_name: "Stan and Friends Tee",
affiliation: "Google Merchandise Store",
coupon: "SUMMER_FUN",
discount: 2.22,
index: 0,
item_brand: "Google",
item_category: "Apparel",
item_category2: "Adult",
item_category3: "Shirts",
item_category4: "Crew",
item_category5: "Short sleeve",
item_list_id: "related_products",
item_list_name: "Related Products",
item_variant: "green",
location_id: "ChIJIQBpAG2ahYAR_6128GcTUEo",
price: 9.99,
quantity: 1
},
{
item_id: "SKU_12346",
item_name: "Google Grey Women's Tee",
affiliation: "Google Merchandise Store",
coupon: "SUMMER_FUN",
discount: 3.33,
index: 1,
item_brand: "Google",
item_category: "Apparel",
item_category2: "Adult",
item_category3: "Shirts",
item_category4: "Crew",
item_category5: "Short sleeve",
item_list_id: "related_products",
item_list_name: "Related Products",
item_variant: "gray",
location_id: "ChIJIQBpAG2ahYAR_6128GcTUEo",
price: 20.99,
promotion_id: "P_12345",
promotion_name: "Summer Sale",
quantity: 1
}]
}
});
}
</script>
</body>
</html>
<!doctype html><html lang="de">
<head>
<title>DEMO: iFrame + Messages (Outer)</title>
<style>
body {font-family:arial;color:#fff;line-height:1.4em;background:#222;text-align:center}
#cnt {position: relative; max-width:1000px; margin: 10px auto; text-align:left; padding:1em}
a, h1, h2, h3, h4 {color:#ffa072; line-height:1em}
</style>
</head>
<body>
<div id="cnt">
<h1>Testseite - iFrame Messages DEMO</h1>
<p id="infotxt">Das hier ist noch Content der beinhaltenden Seite. Es folgt der eingebundene iFrame mit der inneren Seite. Alle Nachrichten, die hier empfangen werden, werden in die Console geschrieben und exemplarisch in einen dataLayer als Event geschrieben.</p>
<iframe src="messages-demo-inner.html" style="width:100%;height:1000px;overflow-y:scroll"></iframe>
</div> <!--cnt -->
<script>
//Einfache Cross-Browser Variante des Empfangs:
function bindEvent(element, eventName, eventHandler) {
if (element.addEventListener){
element.addEventListener(eventName, eventHandler, false);
} else if (element.attachEvent) {
element.attachEvent('on' + eventName, eventHandler);
}
}
//"Kernfunktion" zum Auslesen aller als "iFrameTrackingMessage" markierten Nachrichten
//im der "vereinbarten" Objektstruktur mit "type" und "payload" als Schlüsseln
bindEvent(window, 'message', function (e) {
if (!e || !e.data || e.data.type !== "iFrameTrackingMessage" || !e.data.payload) return;
var pl = e.data.payload;
console.log("%cOUTER: %cNachricht emfangen.",
"font-weight:bold;color:yellow", "", e.data.payload);
var obj = {};
if (typeof(pl) === "string") try {
obj = JSON.parse(e.data.payload);
} catch(err) {
//kein JSON, Event ist nur ein normaler Wert, der als Eventname dient
obj.event = e.data.payload;
} else obj = pl;
if (obj && !obj.event) obj.event = "iFrameEvent";
//Exempl. Verarbeitung: Schreiben der empfangenen Nutzlast in dataLayer
window.dataLayer = window.dataLayer || [];
window.dataLayer.push(obj);
});
</script>
</body>
</html>
@mbaersch
Copy link
Author

mbaersch commented May 8, 2024

Wer Bewegtbild mag, findet dazu auch ein Video unter https://www.youtube.com/watch?v=5BuijXlQGlw

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment