@decocms/blocks 7.47.0 → 7.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/blocks",
3
- "version": "7.47.0",
3
+ "version": "7.48.0",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"
@@ -18,9 +18,15 @@ export function useSendEvent({ on, event }: DataEventParams) {
18
18
  /**
19
19
  * Inline script that observes data-event attributes and dispatches events.
20
20
  * Inject once in the root layout via a <script> tag.
21
+ *
22
+ * Prerender-safe: while a Speculation Rules prerender is running the document
23
+ * is hidden and its IntersectionObserver could fire phantom `view` events, so
24
+ * all init is deferred until the page is actually activated. On a normal load
25
+ * `document.prerendering` is false and it runs immediately (no behavior change).
21
26
  */
22
27
  export const ANALYTICS_SCRIPT = `
23
28
  (function() {
29
+ function start() {
24
30
  function dispatch(event) {
25
31
  if (window.dataLayer) {
26
32
  window.dataLayer.push({ event: event.name, ...event.params });
@@ -67,11 +73,75 @@ export const ANALYTICS_SCRIPT = `
67
73
  } else {
68
74
  setTimeout(function() { mo.observe(document.body, { childList: true, subtree: true }); }, 0);
69
75
  }
76
+ }
77
+
78
+ if (document.prerendering) {
79
+ document.addEventListener('prerenderingchange', start, { once: true });
80
+ } else {
81
+ start();
82
+ }
83
+ })();
84
+ `;
85
+
86
+ /**
87
+ * Dev-only guardrail for Speculation Rules. While a prerender is running (the
88
+ * hidden pre-navigation document), NOTHING analytics-related should hit the
89
+ * network — otherwise the pageview/event is counted for a navigation that may
90
+ * never happen. This script patches the common egress sinks (sendBeacon, fetch,
91
+ * injected tracker <script>/<img>) and `console.error`s the offending call so
92
+ * the dev knows exactly which loader still needs a `document.prerendering`
93
+ * guard before activating speculation rules on that site.
94
+ *
95
+ * Emit only in dev (see DecoRootLayout) — it self-disables outside prerender,
96
+ * so it is inert on real navigations.
97
+ */
98
+ export const SPECULATION_DEV_WARN_SCRIPT = `
99
+ (function(){
100
+ if (!document.prerendering) return;
101
+ var TRACKERS = /googletagmanager\\.com|google-analytics\\.com|analytics\\.google|gtag\\/js|gtm\\.js|connect\\.facebook\\.net|analytics\\.tiktok|hotjar|clarity\\.ms|doubleclick|\\/sgtm|sgtm\\.|\\/collect(\\?|$)|\\/j\\.php|vwo|tr\\?id=/i;
102
+ function err(what, detail){
103
+ console.error(
104
+ '[deco][speculation] ' + what + ' ran during prerender and will double-count when the page activates. ' +
105
+ 'Guard its loader with: if (document.prerendering) { document.addEventListener("prerenderingchange", init, { once: true }); } else { init(); }',
106
+ detail
107
+ );
108
+ }
109
+ var sb = navigator.sendBeacon;
110
+ if (sb) navigator.sendBeacon = function(url){ err('navigator.sendBeacon', url); return sb.apply(navigator, arguments); };
111
+ var of = window.fetch;
112
+ if (of) window.fetch = function(input){
113
+ var u = (input && input.url) || input;
114
+ if (TRACKERS.test(String(u))) err('fetch()', u);
115
+ return of.apply(window, arguments);
116
+ };
117
+ var mo = new MutationObserver(function(muts){
118
+ for (var i=0;i<muts.length;i++){
119
+ var nodes = muts[i].addedNodes || [];
120
+ for (var j=0;j<nodes.length;j++){
121
+ var n = nodes[j];
122
+ if (!n || !n.tagName) continue;
123
+ if (n.tagName === 'SCRIPT' && n.src && TRACKERS.test(n.src)) err('tracker <script> load', n.src);
124
+ else if (n.tagName === 'IMG' && n.src && TRACKERS.test(n.src)) err('tracker pixel <img>', n.src);
125
+ }
126
+ }
127
+ });
128
+ mo.observe(document.documentElement, { childList: true, subtree: true });
129
+ document.addEventListener('prerenderingchange', function(){
130
+ mo.disconnect();
131
+ if (sb) navigator.sendBeacon = sb;
132
+ if (of) window.fetch = of;
133
+ }, { once: true });
70
134
  })();
71
135
  `;
72
136
 
73
- /** Returns a GTM container snippet. Returns empty string if no containerId. */
137
+ /**
138
+ * Returns a GTM container snippet. Returns empty string if no containerId.
139
+ *
140
+ * Prerender-safe: container load is deferred until the page is activated so a
141
+ * Speculation Rules prerender that never activates can't fire GTM (pageview /
142
+ * tags) in the hidden document. No-op guard on normal loads.
143
+ */
74
144
  export function gtmScript(containerId?: string): string {
75
145
  if (!containerId) return "";
76
- return `(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','${containerId}');`;
146
+ return `(function(){function boot(){(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','${containerId}');}if(document.prerendering){document.addEventListener('prerenderingchange',boot,{once:true});}else{boot();}})();`;
77
147
  }