Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question / Using google tag manager from web component does not connect to https://www.googletagmanager.com #1229

Open
patrick-austermann opened this issue Mar 10, 2025 · 3 comments
Labels
bug Something isn't working

Comments

@patrick-austermann
Copy link

Describe the bug
I'm trying to use google tag manager on my minimal web application. To include the tag manager js snippet, I added a web component. The web component does render on my page (the div is visible), but it does not seem to connect back to https://www.googletagmanager.com/

To Reproduce

header_component.js (backticks removed)

import {
LitElement,
html,
} from 'https://cdn.jsdelivr.net/gh/lit/dist@3/core/lit-core.min.js';

class HeaderComponent extends LitElement {

render() {
    return html'<script async src="https://www.googletagmanager.com/gtag/js?id=G-SJF91PFP7P"></script><div></small>(gtm)</small></div>
<script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-SJF91PFP7P'); console.log("gtag loaded "); </script>

;
}
}
customElements.define('header-component', HeaderComponent);

header_component.py:

from typing import Any, Callable
import mesop.labs as mel

@mel.web_component(path="./header_component.js")
def HeaderComponent(key: str | None = None):
return mel.insert_web_component(
name="header-component",
key=key,
)

mesop app: (after many experiments)
@me.page(
path="/",
title="Integral Health Scheduling",
on_load=load,
# stylesheets=["https://www.gstatic.com/firebasejs/ui/6.1.0/firebase-ui-auth.css"],
security_policy=me.SecurityPolicy(
dangerously_disable_trusted_types=True,
allowed_connect_srcs=[".googleapis.com", "https://www.googletagmanager.com"],
allowed_script_srcs=["localhost:32123", "
.google.com", "*.gstatic.com", "https://cdn.jsdelivr.net", "https://www.googletagmanager.com"],
),
)
def main():

HeaderComponent()
....

Expected behavior
The page should invoke the tag manager API on google's side. Nothing seems to make it through. (also per the browser's network monitor)

Desktop System Info

  • OS: Win 11
  • Browser recent Chrome or Firefox
@patrick-austermann patrick-austermann added the bug Something isn't working label Mar 10, 2025
@richard-to
Copy link
Collaborator

In your browser, do you see any networks requests.


I don't have a google analytics account, so it's hard for me to test it out.

I did try asking Claude. Here's the output. I think it could work. Basically they programmatically add the script tags to the head element. I have not tried this out, so it may work or it may not. Seems worth a try.

You'll need to modify it to work with Mesop Web Components.

import { LitElement, html } from 'lit';

/**
 * A web component that loads Google Tag Manager.
 * 
 * @customElement google-tag-manager
 * @attribute gtm-id - The Google Tag Manager container ID (e.g., 'GTM-XXXXXX')
 * @attribute datalayer - Optional initial data layer object as a JSON string
 */
export class GoogleTagManager extends LitElement {
  static properties = {
    gtmId: { type: String, attribute: 'gtm-id' },
    datalayer: { type: String, attribute: 'datalayer' }
  };

  constructor() {
    super();
    this.gtmId = '';
    this.datalayer = '{}';
  }

  connectedCallback() {
    super.connectedCallback();
    this._loadGTM();
  }

  _loadGTM() {
    if (!this.gtmId) {
      console.error('Google Tag Manager ID is required');
      return;
    }

    // Parse the datalayer if provided
    let dataLayer = [];
    try {
      const initialDataLayer = JSON.parse(this.datalayer);
      dataLayer.push(initialDataLayer);
    } catch (e) {
      console.error('Invalid datalayer JSON', e);
    }

    // Define dataLayer if it doesn't exist
    window.dataLayer = window.dataLayer || dataLayer;

    // Add GTM script to head
    const scriptElement = document.createElement('script');
    scriptElement.innerHTML = `
      (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
      new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
      j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
      'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
      })(window,document,'script','dataLayer','${this.gtmId}');
    `;
    document.head.appendChild(scriptElement);

    // Add GTM noscript iframe to body for browsers with JavaScript disabled
    const noscriptElement = document.createElement('noscript');
    const iframeElement = document.createElement('iframe');
    iframeElement.src = `https://www.googletagmanager.com/ns.html?id=${this.gtmId}`;
    iframeElement.height = '0';
    iframeElement.width = '0';
    iframeElement.style.display = 'none';
    iframeElement.style.visibility = 'hidden';
    
    noscriptElement.appendChild(iframeElement);
    document.body.appendChild(noscriptElement);
  }

  render() {
    return html`
      <!-- Component doesn't render any visible content -->
    `;
  }

  // Prevent shadow DOM creation since we don't need it for this component
  createRenderRoot() {
    return this;
  }
}

// Define the custom element
customElements.define('google-tag-manager', GoogleTagManager);

@patrick-austermann
Copy link
Author

Thank you @richard-to - I was able to get the component to load, and I see one call going out to google tag manager now (I did not before, to that is progress!). I'm now stuck with gettign past an error my browser throws in the new component:

gtm_component.js:64 Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self' 'nonce-sBz_5BrI5YH7Idy58KP29A' *.google.com *.gstatic.com https://cdn.jsdelivr.net *.googletagmanager.com". Either the 'unsafe-inline' keyword, a hash ('sha256-XDyli+U+lJogpfhG0OZnd4hKHixWzYEGQauaOqWQriU='), or a nonce ('nonce-...') is required to enable inline execution.

I've been experimenting with the security policy and can't quite get it to work fully:

@me.page(
path="/",
title="Integral Health Scheduling",
security_policy=me.SecurityPolicy(
dangerously_disable_trusted_types=True,
allowed_connect_srcs=[".googleapis.com", ".googletagmanager.com",],
allowed_script_srcs=[".google.com", ".gstatic.com", "https://cdn.jsdelivr.net", "www.googletagmanager.com"],
),
)
def main():

# HeaderComponent(key='key')
GoogleTagManager(gtmId=".......")

This tip

⚠️ Content Security Policy Error ⚠️
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Directive: script-src-elem
Blocked URL: inline
App path: /

ℹ️ If this is coming from your web component,
update your security policy like this:

@me.page(
security_policy=me.SecurityPolicy(
allowed_script_srcs=[
'://',
]
)
)

For more info:
https://google.github.io/mesop/web-components/troubleshooting/
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

does not do anything - the browser (Chrome) complains about the '://' as invalid.

Do you have any tips about circumventing this issue?

Thanks again for your help!

@richard-to
Copy link
Collaborator

richard-to commented Mar 11, 2025

My guess if you may need to get the CSP nonce for inline scripts.

Mesop creates a nonce (ngCspNonce) that maybe you can use in for inline script. This nonce should be included in the CSP. It's a difference nonce every time, so not something you can hardcode.

Here's what Mesop does:

Image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants