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鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[馃悶]partytown throws error in chrome developer console and not able to connect to GTM preview mode #563

Open
maruthasalamr opened this issue Feb 27, 2024 · 13 comments
Labels
bug Something isn't working

Comments

@maruthasalamr
Copy link

maruthasalamr commented Feb 27, 2024

Describe the bug

To improve the performance of the website and to reduce the impact of third party scripts i'm using partytown service worker to offload the GTM scripts from the main thread.
Application - Gatsby
"gatsby": "5.11.0"
"@builder.io/partytown": "^0.9.2"

I will share my partytown gatsby implementation code here

Gatsby-ssr.js

import React from "react";
import { Partytown } from "@builder.io/partytown/react";

export const onRenderBody = ({ setHeadComponents, setPreBodyComponents }) => {
  //const gtmTrackingId = process.env.GATSBY_GOOGLE_TAG_MANAGER_TRACKING_ID;
  setHeadComponents([
    <Partytown 
    key="partytown"
    resolveUrl={(url, location) => {
      var proxyUrl;
      if (url.hostname === 'www.google-analytics.com' && url.pathname.endsWith('.js')) {
         proxyUrl = new URL(`${location.origin}/google-analytics`);
        proxyUrl.searchParams.append('url', url.href);
        return proxyUrl;
      }
      if (url.pathname.includes("/debug/bootstrap")) {
         proxyUrl = new URL(`${location.origin}/googletagmanager/debug/bootstrap`);
        proxyUrl.searchParams.append("url", url.href);
        return proxyUrl;
      }
      return url;
      }}
     debug={true} 
     forward={["dataLayer.push"]}
      />,
    <script 
      key="plugin-google-tagmanager" 
      type="text/partytown"
      dangerouslySetInnerHTML={{
        __html:`(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','GTM-MKPX3TV4'`
      }} 
      />
  ]);

  setPreBodyComponents([
    <noscript
      key="gtm"
      dangerouslySetInnerHTML={{
        __html: `
        <iframe src="https://www.googletagmanager.com/ns.html?id=GTM-MKPX3TV4" height="0" width="0" style="display: none; visibility: hidden" aria-hidden="true"></iframe>`,
      }}
    />,
  ]);
};`

gatsby-node.js
`exports.onPreBuild = async ({ actions: { createRedirect } }) => {
  //await copyLibFiles(path.join(__dirname, "static", "~partytown"));

  createRedirect({
    fromPath: `/google-analytics?url=:url`,
    toPath: `:url`,
    statusCode: 200,
  });

  // This is only for GTM preview mode for debugging
  createRedirect({
    fromPath: `/googletagmanager/debug/bootstrap?url=:url`,
    toPath: `:url`,
    statusCode: 200,
  });
};

Handled reverse proxy for partytown

I got this error 404 in the console

image

I'm not able to preview the tags in GTM

  1. I have added the google tag assistant chrome extension
image

Please help me on this

Reproduction

None

Steps to reproduce

Mentioned above

Browser Info

Chrome

Additional Information

No response

@maruthasalamr maruthasalamr added the bug Something isn't working label Feb 27, 2024
@j0Shi82
Copy link
Contributor

j0Shi82 commented Feb 28, 2024

The code snippet is hard to read, but I think you might indeed be missing a closing bracket in your dangerouslySetInnerHTML:

dangerouslySetInnerHTML={{
__html:(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','GTM-XXXXXXXX')
}}

Notice the trailing bracket after 'GTM-XXXXXXXX'. It's missing in your code above and hence leading to the JavaScript error.

Again: GTM preview is not possible at the moment with Partytown. For more information refer to #72.

@maruthasalamr
Copy link
Author

maruthasalamr commented Feb 28, 2024

@j0Shi82 thanks now the error is gone but how to fix the CORS error
can please help on this ?
image

image

@j0Shi82
Copy link
Contributor

j0Shi82 commented Feb 28, 2024

Sure. As mentioned in the docs, requests from within a Web Worker require the correct CORS headers. It's a security measure that all modern browsers implement.

Apparently, CookieBot does not serve their assets with CORS headers. What you can do is host the script yourself (I have no experience with Gatsby, but there surely is a public folder you can work with). In case the script is dynamic and you have to get it from the vendor directly, you will need to set up a proxy as described here.

Cloudflare offers 100k requests for free (https://developers.cloudflare.com/workers/platform/pricing/), but you can obviously use whatever service you wish or deploy your own server that proxies the requests with the correct CORS header. The Cloudflare worker could look like this:

export default {
  async fetch(request) {
    // node_modules/itty-router/dist/itty-router.min.mjs
    function e({ base: t = "", routes: n = [] } = {}) {
      return { __proto__: new Proxy({}, { get: (e2, a, o) => (e3, ...r) => n.push([a.toUpperCase(), RegExp(`^${(t + e3).replace(/(\/?)\*/g, "($1.*)?").replace(/\/$/, "").replace(/:(\w+)(\?)?(\.)?/g, "$2(?<$1>[^/]+)$2$3").replace(/\.(?=[\w(])/, "\\.").replace(/\)\.\?\(([^\[]+)\[\^/g, "?)\\.?($1(?<=\\.)[^\\.")}/*$`), r]) && o }), routes: n, async handle(e2, ...r) {
        let a, o, t2 = new URL(e2.url);
        e2.query = Object.fromEntries(t2.searchParams);
        for (var [p, s, u] of n)
          if ((p === e2.method || "ALL" === p) && (o = t2.pathname.match(s))) {
            e2.params = o.groups;
            for (var c of u)
              if (void 0 !== (a = await c(e2.proxy || e2, ...r)))
                return a;
          }
      } };
    }

    var router = e();

    router.get("/api/partytown/proxy", async (req) => {
      let url = new URL(req.url);

      if (!url.searchParams.has('url')) {
        return new Response("Invalid", { status: 400 });
      }

      const decodedUrl = decodeURIComponent(url.searchParams.get('url'));
      
      const res = await fetch(decodedUrl);
      const newHeaders = new Headers(res.headers)
      newHeaders.set('access-control-allow-origin', '*');
      const newResponse = new Response(res.body, {
        headers: newHeaders
      })  
      return newResponse;
    });
    router.all("*", () => new Response("Not Found.", { status: 404 }));
    
    return router.handle(request);
  }
};

Then setup the resolveUrl option in the Partytown config:

resolveUrl: (url, location, type) => {
  if (type === 'script' && url.hostname.includes('cookiebot')) {
    const proxyUrl = new URL('/api/partytown/proxy', YOUR_WORKER_DOMAIN);
    proxyUrl.searchParams.append('url', url.href);
    return proxyUrl;
  }
}

You might need to adjust the snippet to fit your needs, but it should poke you into the right direction, I hope. Happy coding!

@maruthasalamr
Copy link
Author

maruthasalamr commented Feb 28, 2024

@j0Shi82 thank you for your quick response i have added the proxy in partytown config and I'm using AWS Cloudfront
distribution for my website hosting
Now the CORS error is fixed but 404 error comes. please help me on this
This is my Partytown gatsby configuration

<Partytown
      key="partytown"
      resolveUrl={(url, location, type) => {
        let proxyUrl;
        if (type === 'script' && url.hostname.includes('cookiebot')) {
          proxyUrl = new URL('/api/partytown/proxy', 'http://localhost:9000/');
          proxyUrl.searchParams.append('url', url.href);
          return proxyUrl;
        }
        if (type === 'script' && url.hostname.includes('analytics')) {
          proxyUrl = new URL('/api/partytown/proxy', 'http://localhost:9000/');
          proxyUrl.searchParams.append('url', url.href);
          return proxyUrl;
        }
        return url;
      }}
      debug={true}
      forward={['dataLayer.push']}
    />,
    <script
      key="plugin-google-tagmanager"
      type="text/partytown"
      dangerouslySetInnerHTML={{
        __html: `(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','GTM-MKPX3TV4');`,
      }}
    />

This is error i got in network tab

image image

@maruthasalamr
Copy link
Author

@j0Shi82 can we do using cloudfront ?

Sure. As mentioned in the docs, requests from within a Web Worker require the correct CORS headers. It's a security measure that all modern browsers implement.

Apparently, CookieBot does not serve their assets with CORS headers. What you can do is host the script yourself (I have no experience with Gatsby, but there surely is a public folder you can work with). In case the script is dynamic and you have to get it from the vendor directly, you will need to set up a proxy as described here.

Cloudflare offers 100k requests for free (https://developers.cloudflare.com/workers/platform/pricing/), but you can obviously use whatever service you wish or deploy your own server that proxies the requests with the correct CORS header. The Cloudflare worker could look like this:

export default {
  async fetch(request) {
    // node_modules/itty-router/dist/itty-router.min.mjs
    function e({ base: t = "", routes: n = [] } = {}) {
      return { __proto__: new Proxy({}, { get: (e2, a, o) => (e3, ...r) => n.push([a.toUpperCase(), RegExp(`^${(t + e3).replace(/(\/?)\*/g, "($1.*)?").replace(/\/$/, "").replace(/:(\w+)(\?)?(\.)?/g, "$2(?<$1>[^/]+)$2$3").replace(/\.(?=[\w(])/, "\\.").replace(/\)\.\?\(([^\[]+)\[\^/g, "?)\\.?($1(?<=\\.)[^\\.")}/*$`), r]) && o }), routes: n, async handle(e2, ...r) {
        let a, o, t2 = new URL(e2.url);
        e2.query = Object.fromEntries(t2.searchParams);
        for (var [p, s, u] of n)
          if ((p === e2.method || "ALL" === p) && (o = t2.pathname.match(s))) {
            e2.params = o.groups;
            for (var c of u)
              if (void 0 !== (a = await c(e2.proxy || e2, ...r)))
                return a;
          }
      } };
    }

    var router = e();

    router.get("/api/partytown/proxy", async (req) => {
      let url = new URL(req.url);

      if (!url.searchParams.has('url')) {
        return new Response("Invalid", { status: 400 });
      }

      const decodedUrl = decodeURIComponent(url.searchParams.get('url'));
      
      const res = await fetch(decodedUrl);
      const newHeaders = new Headers(res.headers)
      newHeaders.set('access-control-allow-origin', '*');
      const newResponse = new Response(res.body, {
        headers: newHeaders
      })  
      return newResponse;
    });
    router.all("*", () => new Response("Not Found.", { status: 404 }));
    
    return router.handle(request);
  }
};

Then setup the resolveUrl option in the Partytown config:

resolveUrl: (url, location, type) => {
  if (type === 'script' && url.hostname.includes('cookiebot')) {
    const proxyUrl = new URL('/api/partytown/proxy', YOUR_WORKER_DOMAIN);
    proxyUrl.searchParams.append('url', url.href);
    return proxyUrl;
  }
}

You might need to adjust the snippet to fit your needs, but it should poke you into the right direction, I hope. Happy coding!

@j0Shi82
Copy link
Contributor

j0Shi82 commented Mar 2, 2024

Cloudfront functions are JavaScript as well afaik. So you should be able to use the code snippet with small adjustments. A quick search revealed that Cloudfront functions may be limited to 1ms of CPU time. Not sure it's still up-to-date. But our production workers use 1.5ms of CPU time on average. So you might run a quota issue there.

Sorry I cannot assist more specifically. You will need to work with the Cloudfront docs to set up your proxy.

@maruthasalamr
Copy link
Author

@j0Shi82 thank you for your response
CORS error is fixed and I have updated CORS in cloudfront behaviours.

Can we take it to production without the seeing the triggers/events are tracked in GTM preview mode ?

@j0Shi82
Copy link
Contributor

j0Shi82 commented Mar 4, 2024

What you can do to verify that the data is flowing is go into your connected Analytics account, monitor the live view, and refresh your page a couple times. In case events appear there, you should be ok. I personally always set up GTM without Partytown to be able to use the debug mode and then move it into the worker. Usually it works fine, but you should absolutely keep an eye on your connected vendors to make sure the data is coming in as expected. Remember Partytown is an open source beta so you'll always need to do maintenance yourself. 馃憤

@maruthasalamr
Copy link
Author

Yes, I can able to preview the tags in GTM without partytown and once i move the GTM into the worker i'm not able to preview the tags. Also i check the real time data in GA4 i can able to see the events appear there.
Thanks for your quick response

@maruthasalamr
Copy link
Author

maruthasalamr commented Mar 12, 2024

@j0Shi82

I'm using hubspot as tag which added in GTM so it is requesting for https://js.hs-analytics.net/analytics/1710241500000/000000.js but receiving 404 not found

This my proxy setup please help me on this

image
<Partytown
      key="partytown"
      resolveUrl={(url, location) => {
        if (url.hostname.includes('analytics')) {
          // Use a secure connection
          if (url?.protocol === 'http:') {
            url = new URL(url.href.replace('http', 'https'));
          }
          // Point to our proxied URL
          const proxyUrl = new URL(location.origin + '/__third-party-proxy');
          proxyUrl.searchParams.append('url', url.href);
          return proxyUrl;
        }
        return url;
      }}
      debug={true}
      forward={['dataLayer.push']}
    />

@maruthasalamr
Copy link
Author

maruthasalamr commented Mar 14, 2024

@j0Shi82
I have added the code in cloudfare worker and i have update config in partytown but still CORS issue occurs
can you please help on this ?

 <Partytown
      key="partytown"
      resolveUrl={(url,type) => {
        if (url?.protocol === 'http:') {
          url = new URL(url.href.replace('http', 'https'))
        }
          if (type === 'script' && url.hostname.includes('analytics')) {
            const proxyUrl = new URL('/api/partytown/proxy', 'https://reverse-proxy.maruthasalam-rajendran.workers.dev/');
            proxyUrl.searchParams.append('url', url.href);
            return proxyUrl;
          }

        return url;
      }}
      debug={true}
      forward={['dataLayer.push']}
    />
image image

@j0Shi82
Copy link
Contributor

j0Shi82 commented Mar 16, 2024

@maruthasalamr Unfortunately I'm a bit busy at the moment, but I'll try to look into it in the coming days.

@totodot
Copy link

totodot commented May 21, 2024

@j0Shi82 any update. It's look similar to resolveUrl issue for Partytown react component
#553

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

3 participants