@d8a-tech/ga4-duplicator 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 d8a.tech
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # GA4 Duplicator
2
+
3
+ Intercepts Google Analytics 4 (GA4) collect requests and duplicates them to a D8A server endpoint.
4
+
5
+ ## What it does
6
+
7
+ This library provides browser-based network interception to duplicate GA4 analytics calls. It intercepts requests made to Google Analytics and forwards them to a configured D8A server endpoint, enabling parallel data collection.
8
+
9
+ Supported interception methods:
10
+ - Fetch API requests
11
+ - XMLHttpRequest calls
12
+ - navigator.sendBeacon calls
13
+ - Script tag loads
14
+
15
+ ## Usage
16
+
17
+ Include the built script in your HTML and initialize the duplicator:
18
+
19
+ ```html
20
+ <script src="dist/gd.min.js"></script>
21
+ <script>
22
+ window.createGA4Duplicator({
23
+ server_container_url: "https://your-d8a-endpoint.com",
24
+ debug: false
25
+ });
26
+ </script>
27
+ ```
28
+
29
+ ### Configuration Options
30
+
31
+ - `server_container_url`: Default D8A server endpoint URL.Can be overridden for each destination.
32
+ - `destinations`: Array of destination objects with measurement_id and server_container_url (default: []).
33
+ - `debug`: Enable debug logging (default: false)
34
+
35
+ ### Multiple Destinations
36
+
37
+ ```javascript
38
+ window.createGA4Duplicator({
39
+ server_container_url: "https://default-endpoint.com",
40
+ destinations: [
41
+ {
42
+ measurement_id: "G-ABC123",
43
+ server_container_url: "https://endpoint1.com",
44
+ },
45
+ {
46
+ measurement_id: "G-XYZ789",
47
+ server_container_url: "https://endpoint2.com"
48
+ }
49
+ ]
50
+ });
51
+ ```
52
+
53
+ ## Build
54
+
55
+ ### Production build (minified):
56
+ ```bash
57
+ npm run build:prod
58
+ ```
59
+ Output: `dist/gd.min.js`
60
+
61
+ ### Development build:
62
+ ```bash
63
+ npm run build:dev
64
+ ```
65
+ Output: `dist/gd.js`
66
+
67
+ ## Source Code Hashing
68
+
69
+ The build process generates a SHA256 hash of all TypeScript source files in the `src/` directory and saves it to `src.hash`. This ensures build integrity and enables CI verification.
70
+
71
+ ### Verify source code integrity:
72
+ ```bash
73
+ npm run hash
74
+ ```
75
+
76
+ This command compares the stored hash with the current source code hash:
77
+ - **Exit code 0**: Source code matches the hash (build is up-to-date)
78
+ - **Exit code 1**: Source code has changed since last build
79
+
80
+ Use this in CI pipelines to ensure `dist/` files are regenerated when source code changes.
81
+
82
+ ## Test
83
+
84
+ ### Unit tests:
85
+ ```bash
86
+ npm test
87
+ ```
88
+
89
+ ### End-to-end tests:
90
+
91
+ 1. Install Playwright browsers (first time only):
92
+ ```bash
93
+ npx playwright install chromium
94
+ ```
95
+
96
+ 2. Run e2e tests:
97
+ ```bash
98
+ npm run test:e2e
99
+ ```
100
+
101
+ The e2e tests start a local HTTP server and use Playwright to verify that GA4 requests are properly duplicated to the D8A endpoint.
102
+
103
+ ### Manual testing:
104
+
105
+ Open `test.html` in a browser to manually test different network interception methods. The page includes buttons to trigger various types of GA4 requests and displays logs of duplication activity.
106
+
package/dist/gd.js ADDED
@@ -0,0 +1,316 @@
1
+ /* ga4-duplicator - built 2026-01-21T13:02:43.613Z */
2
+ "use strict";
3
+ (() => {
4
+ // src/ga4-duplicator.ts
5
+ window.createGA4Duplicator = function(options) {
6
+ class FetchInterceptor {
7
+ install(ctx) {
8
+ const originalFetch = window.fetch;
9
+ window.fetch = function(resource, config) {
10
+ const requestUrl = typeof resource === "string" ? resource : resource instanceof URL ? resource.toString() : resource.url;
11
+ const method = config && config.method || resource.method || "GET";
12
+ if (ctx.isTargetUrl(requestUrl)) {
13
+ const upperMethod = (method || "GET").toUpperCase();
14
+ let prepareBodyPromise = Promise.resolve(void 0);
15
+ if (upperMethod === "POST") {
16
+ if (config && Object.prototype.hasOwnProperty.call(config, "body")) {
17
+ prepareBodyPromise = Promise.resolve(config.body);
18
+ } else if (typeof Request !== "undefined" && resource instanceof Request) {
19
+ try {
20
+ const clonedReq = resource.clone();
21
+ prepareBodyPromise = clonedReq.blob().catch(() => void 0);
22
+ } catch (e) {
23
+ prepareBodyPromise = Promise.resolve(void 0);
24
+ }
25
+ }
26
+ }
27
+ const originalPromise = originalFetch.apply(this, arguments);
28
+ const duplicateUrl = ctx.buildDuplicateUrl(requestUrl);
29
+ if (upperMethod === "GET") {
30
+ originalFetch(duplicateUrl, { method: "GET", keepalive: true }).catch((error) => {
31
+ if (ctx.debug) console.error("gtm interceptor: error duplicating GET fetch:", error);
32
+ });
33
+ } else if (upperMethod === "POST") {
34
+ prepareBodyPromise.then((dupBody) => {
35
+ originalFetch(duplicateUrl, { method: "POST", body: dupBody, keepalive: true }).catch(
36
+ (error) => {
37
+ if (ctx.debug)
38
+ console.error("gtm interceptor: error duplicating POST fetch:", error);
39
+ }
40
+ );
41
+ });
42
+ }
43
+ return originalPromise;
44
+ }
45
+ return originalFetch.apply(this, arguments);
46
+ };
47
+ }
48
+ }
49
+ class XhrInterceptor {
50
+ install(ctx) {
51
+ const originalXHROpen = XMLHttpRequest.prototype.open;
52
+ const originalXHRSend = XMLHttpRequest.prototype.send;
53
+ XMLHttpRequest.prototype.open = function(method, url) {
54
+ this._requestMethod = method;
55
+ this._requestUrl = url;
56
+ return originalXHROpen.apply(this, arguments);
57
+ };
58
+ XMLHttpRequest.prototype.send = function(body) {
59
+ if (this._requestUrl && ctx.isTargetUrl(this._requestUrl)) {
60
+ const originalResult = originalXHRSend.apply(this, arguments);
61
+ try {
62
+ const method = (this._requestMethod || "GET").toUpperCase();
63
+ const duplicateUrl = ctx.buildDuplicateUrl(this._requestUrl);
64
+ if (method === "GET") {
65
+ fetch(duplicateUrl, { method: "GET", keepalive: true }).catch((error) => {
66
+ if (ctx.debug) console.error("gtm interceptor: error duplicating GET xhr:", error);
67
+ });
68
+ } else if (method === "POST") {
69
+ fetch(duplicateUrl, { method: "POST", body, keepalive: true }).catch(
70
+ (error) => {
71
+ if (ctx.debug)
72
+ console.error("gtm interceptor: error duplicating POST xhr:", error);
73
+ }
74
+ );
75
+ }
76
+ } catch (dupErr) {
77
+ if (ctx.debug) console.error("gtm interceptor: xhr duplication failed:", dupErr);
78
+ }
79
+ return originalResult;
80
+ }
81
+ return originalXHRSend.apply(this, arguments);
82
+ };
83
+ }
84
+ }
85
+ class BeaconInterceptor {
86
+ install(ctx) {
87
+ if (!navigator.sendBeacon) return;
88
+ const originalSendBeacon = navigator.sendBeacon;
89
+ navigator.sendBeacon = function(url, data) {
90
+ if (ctx.isTargetUrl(url)) {
91
+ const originalResult = originalSendBeacon.apply(this, arguments);
92
+ try {
93
+ originalSendBeacon.call(navigator, ctx.buildDuplicateUrl(url), data);
94
+ } catch (e) {
95
+ if (ctx.debug) console.error("gtm interceptor: error duplicating sendBeacon:", e);
96
+ }
97
+ return originalResult;
98
+ }
99
+ return originalSendBeacon.apply(this, arguments);
100
+ };
101
+ }
102
+ }
103
+ class ScriptInterceptor {
104
+ install(ctx) {
105
+ try {
106
+ const scriptSrcDescriptor = Object.getOwnPropertyDescriptor(
107
+ HTMLScriptElement.prototype,
108
+ "src"
109
+ );
110
+ const originalScriptSrcSetter = scriptSrcDescriptor && scriptSrcDescriptor.set;
111
+ const originalScriptSrcGetter = scriptSrcDescriptor && scriptSrcDescriptor.get;
112
+ const originalScriptSetAttribute = HTMLScriptElement.prototype.setAttribute;
113
+ const duplicateIfGA4Url = (urlString) => {
114
+ try {
115
+ if (!ctx.isTargetUrl(urlString)) return;
116
+ fetch(ctx.buildDuplicateUrl(urlString), { method: "GET", keepalive: true }).catch(
117
+ (error) => {
118
+ if (ctx.debug)
119
+ console.error("gtm interceptor: error duplicating script GET:", error);
120
+ }
121
+ );
122
+ } catch (e) {
123
+ }
124
+ };
125
+ if (originalScriptSrcSetter && originalScriptSrcGetter) {
126
+ const setter = originalScriptSrcSetter;
127
+ const getter = originalScriptSrcGetter;
128
+ Object.defineProperty(HTMLScriptElement.prototype, "src", {
129
+ configurable: true,
130
+ enumerable: true,
131
+ get: function() {
132
+ return getter.call(this);
133
+ },
134
+ set: function(value) {
135
+ try {
136
+ const last = this.__ga4LastSrcDuplicated;
137
+ if (value && value !== last) {
138
+ duplicateIfGA4Url(String(value));
139
+ this.__ga4LastSrcDuplicated = String(value);
140
+ }
141
+ const self = this;
142
+ const onloadOnce = function() {
143
+ try {
144
+ const finalUrl = self.src;
145
+ if (finalUrl && finalUrl !== self.__ga4LastSrcDuplicated) {
146
+ duplicateIfGA4Url(finalUrl);
147
+ self.__ga4LastSrcDuplicated = finalUrl;
148
+ }
149
+ } catch (e) {
150
+ }
151
+ self.removeEventListener("load", onloadOnce);
152
+ };
153
+ this.addEventListener("load", onloadOnce);
154
+ } catch (e) {
155
+ }
156
+ setter.call(this, value);
157
+ }
158
+ });
159
+ }
160
+ HTMLScriptElement.prototype.setAttribute = function(name, value) {
161
+ try {
162
+ if (String(name).toLowerCase() === "src") {
163
+ const v = String(value);
164
+ const last = this.__ga4LastSrcDuplicated;
165
+ if (v && v !== last) {
166
+ duplicateIfGA4Url(v);
167
+ this.__ga4LastSrcDuplicated = v;
168
+ }
169
+ const selfAttr = this;
170
+ const onloadOnceAttr = function() {
171
+ try {
172
+ const finalUrlAttr = selfAttr.src;
173
+ if (finalUrlAttr && finalUrlAttr !== selfAttr.__ga4LastSrcDuplicated) {
174
+ duplicateIfGA4Url(finalUrlAttr);
175
+ selfAttr.__ga4LastSrcDuplicated = finalUrlAttr;
176
+ }
177
+ } catch (e) {
178
+ }
179
+ selfAttr.removeEventListener("load", onloadOnceAttr);
180
+ };
181
+ this.addEventListener("load", onloadOnceAttr);
182
+ }
183
+ } catch (e) {
184
+ }
185
+ return originalScriptSetAttribute.apply(this, arguments);
186
+ };
187
+ } catch (e) {
188
+ }
189
+ }
190
+ }
191
+ if (window.__ga4DuplicatorInitialized) {
192
+ if (options.debug) console.warn("GA4 Duplicator: already initialized.");
193
+ return;
194
+ }
195
+ const destinations = [];
196
+ if (options.destinations && Array.isArray(options.destinations)) {
197
+ for (let i = 0; i < options.destinations.length; i++) {
198
+ destinations.push(options.destinations[i]);
199
+ }
200
+ }
201
+ if (options.server_container_url) {
202
+ destinations.push({
203
+ measurement_id: "*",
204
+ server_container_url: options.server_container_url
205
+ });
206
+ }
207
+ if (destinations.length === 0) {
208
+ console.error("GA4 Duplicator: either server_container_url or destinations array is required");
209
+ return;
210
+ }
211
+ function normalizePath(p) {
212
+ p = String(p || "");
213
+ p = p.replace(/\/+$/, "");
214
+ return p === "" ? "/" : p;
215
+ }
216
+ function matchesId(pattern, id) {
217
+ if (!pattern || pattern === "*") return true;
218
+ try {
219
+ const regexStr = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
220
+ return new RegExp("^" + regexStr + "$", "i").test(id);
221
+ } catch (e) {
222
+ return pattern.toLowerCase() === id.toLowerCase();
223
+ }
224
+ }
225
+ function getMeasurementId(url) {
226
+ try {
227
+ const parsed = new URL(url, location.href);
228
+ return parsed.searchParams.get("tid") || parsed.searchParams.get("id") || "";
229
+ } catch (e) {
230
+ const match = url.match(/[?&](?:tid|id)=([^&?#]+)/);
231
+ return match ? decodeURIComponent(match[1]) : "";
232
+ }
233
+ }
234
+ function getDestinationForId(id) {
235
+ for (let i = 0; i < destinations.length; i++) {
236
+ if (matchesId(destinations[i].measurement_id, id)) {
237
+ return destinations[i];
238
+ }
239
+ }
240
+ return null;
241
+ }
242
+ function getDuplicateEndpointUrl(dest) {
243
+ const trackingURL = String(dest.server_container_url || "").trim();
244
+ const u = new URL(trackingURL, location.href);
245
+ u.search = "";
246
+ u.hash = "";
247
+ return u;
248
+ }
249
+ function isTargetUrl(url) {
250
+ if (!url || typeof url !== "string") return false;
251
+ try {
252
+ const parsed = new URL(url, location.href);
253
+ for (let i = 0; i < destinations.length; i++) {
254
+ const duplicateTarget = getDuplicateEndpointUrl(destinations[i]);
255
+ if (parsed.origin === duplicateTarget.origin && normalizePath(parsed.pathname) === normalizePath(duplicateTarget.pathname)) {
256
+ return false;
257
+ }
258
+ }
259
+ const params = parsed.searchParams;
260
+ const hasGtm = params.has("gtm");
261
+ const hasTagExp = params.has("tag_exp");
262
+ const measurementId = params.get("tid") || params.get("id") || "";
263
+ const isMeasurementIdGA4 = /^G-[A-Z0-9]+$/i.test(measurementId);
264
+ return hasGtm && hasTagExp && isMeasurementIdGA4;
265
+ } catch (e) {
266
+ if (typeof url === "string") {
267
+ for (let j = 0; j < destinations.length; j++) {
268
+ try {
269
+ const target = getDuplicateEndpointUrl(destinations[j]);
270
+ const targetNoQuery = target.origin + target.pathname;
271
+ if (url.indexOf(targetNoQuery) !== -1) return false;
272
+ } catch (e2) {
273
+ }
274
+ }
275
+ const hasGtmFallback = url.indexOf("gtm=") !== -1;
276
+ const hasTagExpFallback = url.indexOf("tag_exp=") !== -1;
277
+ const idMatch = url.match(/[?&](?:tid|id)=G-[A-Za-z0-9]+/);
278
+ return !!(hasGtmFallback && hasTagExpFallback && idMatch);
279
+ }
280
+ return false;
281
+ }
282
+ }
283
+ function buildDuplicateUrl(originalUrl) {
284
+ const id = getMeasurementId(originalUrl);
285
+ const dest = getDestinationForId(id);
286
+ if (!dest) return "";
287
+ const dst = getDuplicateEndpointUrl(dest);
288
+ try {
289
+ const src = new URL(originalUrl, location.href);
290
+ dst.search = src.search;
291
+ } catch (e) {
292
+ }
293
+ return dst.toString();
294
+ }
295
+ const context = {
296
+ debug: !!options.debug,
297
+ isTargetUrl,
298
+ buildDuplicateUrl
299
+ };
300
+ const interceptors = [
301
+ new FetchInterceptor(),
302
+ new XhrInterceptor(),
303
+ new BeaconInterceptor(),
304
+ new ScriptInterceptor()
305
+ ];
306
+ for (let i = 0; i < interceptors.length; i++) {
307
+ try {
308
+ interceptors[i].install(context);
309
+ } catch (e) {
310
+ if (options.debug) console.error("GA4 Duplicator: failed to install interceptor", e);
311
+ }
312
+ }
313
+ window.__ga4DuplicatorInitialized = true;
314
+ };
315
+ })();
316
+ //# sourceMappingURL=gd.js.map
package/dist/gd.js.map ADDED
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/ga4-duplicator.ts"],
4
+ "sourcesContent": ["/**\n * GA4 Duplicator - Intercepts GA4 collect calls and sends duplicates to D8A.\n */\n\ninterface GA4Destination {\n measurement_id: string;\n server_container_url: string;\n}\n\ninterface GA4DuplicatorOptions {\n server_container_url?: string;\n destinations?: GA4Destination[];\n debug?: boolean;\n}\n\n(window as any).createGA4Duplicator = function (options: GA4DuplicatorOptions) {\n /**\n * Shared logic and configuration for interceptors.\n */\n interface InterceptorContext {\n debug: boolean;\n isTargetUrl(url: string | null | undefined): boolean;\n buildDuplicateUrl(url: string): string;\n }\n\n /**\n * Base interface for monkey-patching implementations.\n */\n interface NetworkInterceptor {\n install(context: InterceptorContext): void;\n }\n\n class FetchInterceptor implements NetworkInterceptor {\n install(ctx: InterceptorContext): void {\n const originalFetch = window.fetch;\n window.fetch = function (\n this: any,\n resource: RequestInfo | URL,\n config?: RequestInit,\n ): Promise<Response> {\n const requestUrl =\n typeof resource === \"string\"\n ? resource\n : resource instanceof URL\n ? resource.toString()\n : (resource as any).url;\n const method = (config && config.method) || (resource as any).method || \"GET\";\n\n if (ctx.isTargetUrl(requestUrl)) {\n const upperMethod = (method || \"GET\").toUpperCase();\n\n // If we need the body from a Request, clone BEFORE calling the original fetch\n let prepareBodyPromise: Promise<any> = Promise.resolve(undefined);\n if (upperMethod === \"POST\") {\n if (config && Object.prototype.hasOwnProperty.call(config, \"body\")) {\n prepareBodyPromise = Promise.resolve(config.body);\n } else if (typeof Request !== \"undefined\" && resource instanceof Request) {\n try {\n const clonedReq = resource.clone();\n prepareBodyPromise = clonedReq.blob().catch(() => undefined);\n } catch {\n prepareBodyPromise = Promise.resolve(undefined);\n }\n }\n }\n\n // First send original request to Google Analytics\n const originalPromise = originalFetch.apply(this, arguments as any);\n\n // Then send duplicate\n const duplicateUrl = ctx.buildDuplicateUrl(requestUrl);\n\n if (upperMethod === \"GET\") {\n originalFetch(duplicateUrl, { method: \"GET\", keepalive: true }).catch((error) => {\n if (ctx.debug) console.error(\"gtm interceptor: error duplicating GET fetch:\", error);\n });\n } else if (upperMethod === \"POST\") {\n prepareBodyPromise.then((dupBody) => {\n originalFetch(duplicateUrl, { method: \"POST\", body: dupBody, keepalive: true }).catch(\n (error) => {\n if (ctx.debug)\n console.error(\"gtm interceptor: error duplicating POST fetch:\", error);\n },\n );\n });\n }\n\n return originalPromise;\n }\n\n return originalFetch.apply(this, arguments as any);\n };\n }\n }\n\n class XhrInterceptor implements NetworkInterceptor {\n install(ctx: InterceptorContext): void {\n const originalXHROpen = XMLHttpRequest.prototype.open;\n const originalXHRSend = XMLHttpRequest.prototype.send;\n\n XMLHttpRequest.prototype.open = function (this: any, method: string, url: string | URL) {\n this._requestMethod = method;\n this._requestUrl = url;\n return originalXHROpen.apply(this, arguments as any);\n };\n\n XMLHttpRequest.prototype.send = function (\n this: any,\n body?: Document | XMLHttpRequestBodyInit | null,\n ) {\n if (this._requestUrl && ctx.isTargetUrl(this._requestUrl)) {\n // First send original request to Google Analytics\n const originalResult = originalXHRSend.apply(this, arguments as any);\n\n // Then send duplicate to our endpoint mimicking method and payload\n try {\n const method = (this._requestMethod || \"GET\").toUpperCase();\n const duplicateUrl = ctx.buildDuplicateUrl(this._requestUrl);\n\n if (method === \"GET\") {\n fetch(duplicateUrl, { method: \"GET\", keepalive: true }).catch((error) => {\n if (ctx.debug) console.error(\"gtm interceptor: error duplicating GET xhr:\", error);\n });\n } else if (method === \"POST\") {\n fetch(duplicateUrl, { method: \"POST\", body: body as any, keepalive: true }).catch(\n (error) => {\n if (ctx.debug)\n console.error(\"gtm interceptor: error duplicating POST xhr:\", error);\n },\n );\n }\n } catch (dupErr) {\n if (ctx.debug) console.error(\"gtm interceptor: xhr duplication failed:\", dupErr);\n }\n return originalResult;\n }\n return originalXHRSend.apply(this, arguments as any);\n };\n }\n }\n\n class BeaconInterceptor implements NetworkInterceptor {\n install(ctx: InterceptorContext): void {\n if (!navigator.sendBeacon) return;\n\n const originalSendBeacon = navigator.sendBeacon;\n navigator.sendBeacon = function (\n this: any,\n url: string | URL,\n data?: BodyInit | null,\n ): boolean {\n if (ctx.isTargetUrl(url as string)) {\n const originalResult = originalSendBeacon.apply(this, arguments as any);\n try {\n originalSendBeacon.call(navigator, ctx.buildDuplicateUrl(url as string), data);\n } catch (e) {\n if (ctx.debug) console.error(\"gtm interceptor: error duplicating sendBeacon:\", e);\n }\n return originalResult;\n }\n return originalSendBeacon.apply(this, arguments as any);\n };\n }\n }\n\n class ScriptInterceptor implements NetworkInterceptor {\n install(ctx: InterceptorContext): void {\n try {\n const scriptSrcDescriptor = Object.getOwnPropertyDescriptor(\n HTMLScriptElement.prototype,\n \"src\",\n );\n const originalScriptSrcSetter = scriptSrcDescriptor && scriptSrcDescriptor.set;\n const originalScriptSrcGetter = scriptSrcDescriptor && scriptSrcDescriptor.get;\n const originalScriptSetAttribute = HTMLScriptElement.prototype.setAttribute;\n\n const duplicateIfGA4Url = (urlString: string) => {\n try {\n if (!ctx.isTargetUrl(urlString)) return;\n fetch(ctx.buildDuplicateUrl(urlString), { method: \"GET\", keepalive: true }).catch(\n (error) => {\n if (ctx.debug)\n console.error(\"gtm interceptor: error duplicating script GET:\", error);\n },\n );\n } catch {\n // Intentionally empty\n }\n };\n\n if (originalScriptSrcSetter && originalScriptSrcGetter) {\n const setter = originalScriptSrcSetter;\n const getter = originalScriptSrcGetter;\n Object.defineProperty(HTMLScriptElement.prototype, \"src\", {\n configurable: true,\n enumerable: true,\n get: function (this: any) {\n return getter.call(this);\n },\n set: function (this: any, value: string) {\n try {\n const last = this.__ga4LastSrcDuplicated;\n if (value && value !== last) {\n duplicateIfGA4Url(String(value));\n this.__ga4LastSrcDuplicated = String(value);\n }\n const self = this;\n const onloadOnce = function () {\n try {\n const finalUrl = self.src;\n if (finalUrl && finalUrl !== self.__ga4LastSrcDuplicated) {\n duplicateIfGA4Url(finalUrl);\n self.__ga4LastSrcDuplicated = finalUrl;\n }\n } catch {}\n self.removeEventListener(\"load\", onloadOnce);\n };\n this.addEventListener(\"load\", onloadOnce);\n } catch {}\n setter.call(this, value);\n },\n });\n }\n\n HTMLScriptElement.prototype.setAttribute = function (\n this: any,\n name: string,\n value: string,\n ) {\n try {\n if (String(name).toLowerCase() === \"src\") {\n const v = String(value);\n const last = this.__ga4LastSrcDuplicated;\n if (v && v !== last) {\n duplicateIfGA4Url(v);\n this.__ga4LastSrcDuplicated = v;\n }\n const selfAttr = this;\n const onloadOnceAttr = function () {\n try {\n const finalUrlAttr = selfAttr.src;\n if (finalUrlAttr && finalUrlAttr !== selfAttr.__ga4LastSrcDuplicated) {\n duplicateIfGA4Url(finalUrlAttr);\n selfAttr.__ga4LastSrcDuplicated = finalUrlAttr;\n }\n } catch {}\n selfAttr.removeEventListener(\"load\", onloadOnceAttr);\n };\n this.addEventListener(\"load\", onloadOnceAttr);\n }\n } catch {\n // Intentionally empty\n }\n return originalScriptSetAttribute.apply(this, arguments as any);\n };\n } catch {}\n }\n }\n\n if ((window as any).__ga4DuplicatorInitialized) {\n if (options.debug) console.warn(\"GA4 Duplicator: already initialized.\");\n return;\n }\n\n const destinations: GA4Destination[] = [];\n if (options.destinations && Array.isArray(options.destinations)) {\n for (let i = 0; i < options.destinations.length; i++) {\n destinations.push(options.destinations[i]);\n }\n }\n\n if (options.server_container_url) {\n destinations.push({\n measurement_id: \"*\",\n server_container_url: options.server_container_url,\n });\n }\n\n if (destinations.length === 0) {\n console.error(\"GA4 Duplicator: either server_container_url or destinations array is required\");\n return;\n }\n\n function normalizePath(p: string): string {\n p = String(p || \"\");\n p = p.replace(/\\/+$/, \"\");\n return p === \"\" ? \"/\" : p;\n }\n\n function matchesId(pattern: string, id: string): boolean {\n if (!pattern || pattern === \"*\") return true;\n try {\n const regexStr = pattern.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/\\*/g, \".*\");\n return new RegExp(\"^\" + regexStr + \"$\", \"i\").test(id);\n } catch {\n return pattern.toLowerCase() === id.toLowerCase();\n }\n }\n\n function getMeasurementId(url: string): string {\n try {\n const parsed = new URL(url, location.href);\n return parsed.searchParams.get(\"tid\") || parsed.searchParams.get(\"id\") || \"\";\n } catch {\n const match = url.match(/[?&](?:tid|id)=([^&?#]+)/);\n return match ? decodeURIComponent(match[1]) : \"\";\n }\n }\n\n function getDestinationForId(id: string): GA4Destination | null {\n for (let i = 0; i < destinations.length; i++) {\n if (matchesId(destinations[i].measurement_id, id)) {\n return destinations[i];\n }\n }\n return null;\n }\n\n function getDuplicateEndpointUrl(dest: GA4Destination): URL {\n const trackingURL = String(dest.server_container_url || \"\").trim();\n const u = new URL(trackingURL, location.href);\n u.search = \"\";\n u.hash = \"\";\n return u;\n }\n\n function isTargetUrl(url: string | null | undefined): boolean {\n if (!url || typeof url !== \"string\") return false;\n try {\n const parsed = new URL(url, location.href);\n\n for (let i = 0; i < destinations.length; i++) {\n const duplicateTarget = getDuplicateEndpointUrl(destinations[i]);\n if (\n parsed.origin === duplicateTarget.origin &&\n normalizePath(parsed.pathname) === normalizePath(duplicateTarget.pathname)\n ) {\n return false;\n }\n }\n\n const params = parsed.searchParams;\n const hasGtm = params.has(\"gtm\");\n const hasTagExp = params.has(\"tag_exp\");\n const measurementId = params.get(\"tid\") || params.get(\"id\") || \"\";\n const isMeasurementIdGA4 = /^G-[A-Z0-9]+$/i.test(measurementId);\n\n return hasGtm && hasTagExp && isMeasurementIdGA4;\n } catch {\n if (typeof url === \"string\") {\n for (let j = 0; j < destinations.length; j++) {\n try {\n const target = getDuplicateEndpointUrl(destinations[j]);\n const targetNoQuery = target.origin + target.pathname;\n if (url.indexOf(targetNoQuery) !== -1) return false;\n } catch {\n // Intentionally empty\n }\n }\n\n const hasGtmFallback = url.indexOf(\"gtm=\") !== -1;\n const hasTagExpFallback = url.indexOf(\"tag_exp=\") !== -1;\n const idMatch = url.match(/[?&](?:tid|id)=G-[A-Za-z0-9]+/);\n return !!(hasGtmFallback && hasTagExpFallback && idMatch);\n }\n return false;\n }\n }\n\n function buildDuplicateUrl(originalUrl: string): string {\n const id = getMeasurementId(originalUrl);\n const dest = getDestinationForId(id);\n if (!dest) return \"\";\n\n const dst = getDuplicateEndpointUrl(dest);\n try {\n const src = new URL(originalUrl, location.href);\n dst.search = src.search;\n } catch {}\n return dst.toString();\n }\n\n const context: InterceptorContext = {\n debug: !!options.debug,\n isTargetUrl,\n buildDuplicateUrl,\n };\n\n const interceptors: NetworkInterceptor[] = [\n new FetchInterceptor(),\n new XhrInterceptor(),\n new BeaconInterceptor(),\n new ScriptInterceptor(),\n ];\n\n for (let i = 0; i < interceptors.length; i++) {\n try {\n interceptors[i].install(context);\n } catch (e) {\n if (options.debug) console.error(\"GA4 Duplicator: failed to install interceptor\", e);\n }\n }\n\n (window as any).__ga4DuplicatorInitialized = true;\n};\n"],
5
+ "mappings": "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",
6
+ "names": ["e"]
7
+ }
package/dist/gd.min.js ADDED
@@ -0,0 +1,3 @@
1
+ /* ga4-duplicator - built 2026-01-21T13:02:43.613Z */
2
+ "use strict";(()=>{window.createGA4Duplicator=function(g){class b{install(e){let r=window.fetch;window.fetch=function(n,i){let c=typeof n=="string"?n:n instanceof URL?n.toString():n.url,o=i&&i.method||n.method||"GET";if(e.isTargetUrl(c)){let s=(o||"GET").toUpperCase(),a=Promise.resolve(void 0);if(s==="POST"){if(i&&Object.prototype.hasOwnProperty.call(i,"body"))a=Promise.resolve(i.body);else if(typeof Request!="undefined"&&n instanceof Request)try{a=n.clone().blob().catch(()=>{})}catch(l){a=Promise.resolve(void 0)}}let u=r.apply(this,arguments),h=e.buildDuplicateUrl(c);return s==="GET"?r(h,{method:"GET",keepalive:!0}).catch(l=>{e.debug&&console.error("gtm interceptor: error duplicating GET fetch:",l)}):s==="POST"&&a.then(l=>{r(h,{method:"POST",body:l,keepalive:!0}).catch(f=>{e.debug&&console.error("gtm interceptor: error duplicating POST fetch:",f)})}),u}return r.apply(this,arguments)}}}class S{install(e){let r=XMLHttpRequest.prototype.open,n=XMLHttpRequest.prototype.send;XMLHttpRequest.prototype.open=function(i,c){return this._requestMethod=i,this._requestUrl=c,r.apply(this,arguments)},XMLHttpRequest.prototype.send=function(i){if(this._requestUrl&&e.isTargetUrl(this._requestUrl)){let c=n.apply(this,arguments);try{let o=(this._requestMethod||"GET").toUpperCase(),s=e.buildDuplicateUrl(this._requestUrl);o==="GET"?fetch(s,{method:"GET",keepalive:!0}).catch(a=>{e.debug&&console.error("gtm interceptor: error duplicating GET xhr:",a)}):o==="POST"&&fetch(s,{method:"POST",body:i,keepalive:!0}).catch(a=>{e.debug&&console.error("gtm interceptor: error duplicating POST xhr:",a)})}catch(o){e.debug&&console.error("gtm interceptor: xhr duplication failed:",o)}return c}return n.apply(this,arguments)}}}class U{install(e){if(!navigator.sendBeacon)return;let r=navigator.sendBeacon;navigator.sendBeacon=function(n,i){if(e.isTargetUrl(n)){let c=r.apply(this,arguments);try{r.call(navigator,e.buildDuplicateUrl(n),i)}catch(o){e.debug&&console.error("gtm interceptor: error duplicating sendBeacon:",o)}return c}return r.apply(this,arguments)}}}class L{install(e){try{let r=Object.getOwnPropertyDescriptor(HTMLScriptElement.prototype,"src"),n=r&&r.set,i=r&&r.get,c=HTMLScriptElement.prototype.setAttribute,o=s=>{try{if(!e.isTargetUrl(s))return;fetch(e.buildDuplicateUrl(s),{method:"GET",keepalive:!0}).catch(a=>{e.debug&&console.error("gtm interceptor: error duplicating script GET:",a)})}catch(a){}};if(n&&i){let s=n,a=i;Object.defineProperty(HTMLScriptElement.prototype,"src",{configurable:!0,enumerable:!0,get:function(){return a.call(this)},set:function(u){try{let h=this.__ga4LastSrcDuplicated;u&&u!==h&&(o(String(u)),this.__ga4LastSrcDuplicated=String(u));let l=this,f=function(){try{let p=l.src;p&&p!==l.__ga4LastSrcDuplicated&&(o(p),l.__ga4LastSrcDuplicated=p)}catch(p){}l.removeEventListener("load",f)};this.addEventListener("load",f)}catch(h){}s.call(this,u)}})}HTMLScriptElement.prototype.setAttribute=function(s,a){try{if(String(s).toLowerCase()==="src"){let u=String(a),h=this.__ga4LastSrcDuplicated;u&&u!==h&&(o(u),this.__ga4LastSrcDuplicated=u);let l=this,f=function(){try{let p=l.src;p&&p!==l.__ga4LastSrcDuplicated&&(o(p),l.__ga4LastSrcDuplicated=p)}catch(p){}l.removeEventListener("load",f)};this.addEventListener("load",f)}}catch(u){}return c.apply(this,arguments)}}catch(r){}}}if(window.__ga4DuplicatorInitialized){g.debug&&console.warn("GA4 Duplicator: already initialized.");return}let d=[];if(g.destinations&&Array.isArray(g.destinations))for(let t=0;t<g.destinations.length;t++)d.push(g.destinations[t]);if(g.server_container_url&&d.push({measurement_id:"*",server_container_url:g.server_container_url}),d.length===0){console.error("GA4 Duplicator: either server_container_url or destinations array is required");return}function y(t){return t=String(t||""),t=t.replace(/\/+$/,""),t===""?"/":t}function D(t,e){if(!t||t==="*")return!0;try{let r=t.replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*");return new RegExp("^"+r+"$","i").test(e)}catch(r){return t.toLowerCase()===e.toLowerCase()}}function w(t){try{let e=new URL(t,location.href);return e.searchParams.get("tid")||e.searchParams.get("id")||""}catch(e){let r=t.match(/[?&](?:tid|id)=([^&?#]+)/);return r?decodeURIComponent(r[1]):""}}function T(t){for(let e=0;e<d.length;e++)if(D(d[e].measurement_id,t))return d[e];return null}function m(t){let e=String(t.server_container_url||"").trim(),r=new URL(e,location.href);return r.search="",r.hash="",r}function I(t){if(!t||typeof t!="string")return!1;try{let e=new URL(t,location.href);for(let s=0;s<d.length;s++){let a=m(d[s]);if(e.origin===a.origin&&y(e.pathname)===y(a.pathname))return!1}let r=e.searchParams,n=r.has("gtm"),i=r.has("tag_exp"),c=r.get("tid")||r.get("id")||"",o=/^G-[A-Z0-9]+$/i.test(c);return n&&i&&o}catch(e){if(typeof t=="string"){for(let c=0;c<d.length;c++)try{let o=m(d[c]),s=o.origin+o.pathname;if(t.indexOf(s)!==-1)return!1}catch(o){}let r=t.indexOf("gtm=")!==-1,n=t.indexOf("tag_exp=")!==-1,i=t.match(/[?&](?:tid|id)=G-[A-Za-z0-9]+/);return!!(r&&n&&i)}return!1}}function G(t){let e=w(t),r=T(e);if(!r)return"";let n=m(r);try{let i=new URL(t,location.href);n.search=i.search}catch(i){}return n.toString()}let R={debug:!!g.debug,isTargetUrl:I,buildDuplicateUrl:G},_=[new b,new S,new U,new L];for(let t=0;t<_.length;t++)try{_[t].install(R)}catch(e){g.debug&&console.error("GA4 Duplicator: failed to install interceptor",e)}window.__ga4DuplicatorInitialized=!0};})();
3
+ //# sourceMappingURL=gd.min.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/ga4-duplicator.ts"],
4
+ "sourcesContent": ["/**\n * GA4 Duplicator - Intercepts GA4 collect calls and sends duplicates to D8A.\n */\n\ninterface GA4Destination {\n measurement_id: string;\n server_container_url: string;\n}\n\ninterface GA4DuplicatorOptions {\n server_container_url?: string;\n destinations?: GA4Destination[];\n debug?: boolean;\n}\n\n(window as any).createGA4Duplicator = function (options: GA4DuplicatorOptions) {\n /**\n * Shared logic and configuration for interceptors.\n */\n interface InterceptorContext {\n debug: boolean;\n isTargetUrl(url: string | null | undefined): boolean;\n buildDuplicateUrl(url: string): string;\n }\n\n /**\n * Base interface for monkey-patching implementations.\n */\n interface NetworkInterceptor {\n install(context: InterceptorContext): void;\n }\n\n class FetchInterceptor implements NetworkInterceptor {\n install(ctx: InterceptorContext): void {\n const originalFetch = window.fetch;\n window.fetch = function (\n this: any,\n resource: RequestInfo | URL,\n config?: RequestInit,\n ): Promise<Response> {\n const requestUrl =\n typeof resource === \"string\"\n ? resource\n : resource instanceof URL\n ? resource.toString()\n : (resource as any).url;\n const method = (config && config.method) || (resource as any).method || \"GET\";\n\n if (ctx.isTargetUrl(requestUrl)) {\n const upperMethod = (method || \"GET\").toUpperCase();\n\n // If we need the body from a Request, clone BEFORE calling the original fetch\n let prepareBodyPromise: Promise<any> = Promise.resolve(undefined);\n if (upperMethod === \"POST\") {\n if (config && Object.prototype.hasOwnProperty.call(config, \"body\")) {\n prepareBodyPromise = Promise.resolve(config.body);\n } else if (typeof Request !== \"undefined\" && resource instanceof Request) {\n try {\n const clonedReq = resource.clone();\n prepareBodyPromise = clonedReq.blob().catch(() => undefined);\n } catch {\n prepareBodyPromise = Promise.resolve(undefined);\n }\n }\n }\n\n // First send original request to Google Analytics\n const originalPromise = originalFetch.apply(this, arguments as any);\n\n // Then send duplicate\n const duplicateUrl = ctx.buildDuplicateUrl(requestUrl);\n\n if (upperMethod === \"GET\") {\n originalFetch(duplicateUrl, { method: \"GET\", keepalive: true }).catch((error) => {\n if (ctx.debug) console.error(\"gtm interceptor: error duplicating GET fetch:\", error);\n });\n } else if (upperMethod === \"POST\") {\n prepareBodyPromise.then((dupBody) => {\n originalFetch(duplicateUrl, { method: \"POST\", body: dupBody, keepalive: true }).catch(\n (error) => {\n if (ctx.debug)\n console.error(\"gtm interceptor: error duplicating POST fetch:\", error);\n },\n );\n });\n }\n\n return originalPromise;\n }\n\n return originalFetch.apply(this, arguments as any);\n };\n }\n }\n\n class XhrInterceptor implements NetworkInterceptor {\n install(ctx: InterceptorContext): void {\n const originalXHROpen = XMLHttpRequest.prototype.open;\n const originalXHRSend = XMLHttpRequest.prototype.send;\n\n XMLHttpRequest.prototype.open = function (this: any, method: string, url: string | URL) {\n this._requestMethod = method;\n this._requestUrl = url;\n return originalXHROpen.apply(this, arguments as any);\n };\n\n XMLHttpRequest.prototype.send = function (\n this: any,\n body?: Document | XMLHttpRequestBodyInit | null,\n ) {\n if (this._requestUrl && ctx.isTargetUrl(this._requestUrl)) {\n // First send original request to Google Analytics\n const originalResult = originalXHRSend.apply(this, arguments as any);\n\n // Then send duplicate to our endpoint mimicking method and payload\n try {\n const method = (this._requestMethod || \"GET\").toUpperCase();\n const duplicateUrl = ctx.buildDuplicateUrl(this._requestUrl);\n\n if (method === \"GET\") {\n fetch(duplicateUrl, { method: \"GET\", keepalive: true }).catch((error) => {\n if (ctx.debug) console.error(\"gtm interceptor: error duplicating GET xhr:\", error);\n });\n } else if (method === \"POST\") {\n fetch(duplicateUrl, { method: \"POST\", body: body as any, keepalive: true }).catch(\n (error) => {\n if (ctx.debug)\n console.error(\"gtm interceptor: error duplicating POST xhr:\", error);\n },\n );\n }\n } catch (dupErr) {\n if (ctx.debug) console.error(\"gtm interceptor: xhr duplication failed:\", dupErr);\n }\n return originalResult;\n }\n return originalXHRSend.apply(this, arguments as any);\n };\n }\n }\n\n class BeaconInterceptor implements NetworkInterceptor {\n install(ctx: InterceptorContext): void {\n if (!navigator.sendBeacon) return;\n\n const originalSendBeacon = navigator.sendBeacon;\n navigator.sendBeacon = function (\n this: any,\n url: string | URL,\n data?: BodyInit | null,\n ): boolean {\n if (ctx.isTargetUrl(url as string)) {\n const originalResult = originalSendBeacon.apply(this, arguments as any);\n try {\n originalSendBeacon.call(navigator, ctx.buildDuplicateUrl(url as string), data);\n } catch (e) {\n if (ctx.debug) console.error(\"gtm interceptor: error duplicating sendBeacon:\", e);\n }\n return originalResult;\n }\n return originalSendBeacon.apply(this, arguments as any);\n };\n }\n }\n\n class ScriptInterceptor implements NetworkInterceptor {\n install(ctx: InterceptorContext): void {\n try {\n const scriptSrcDescriptor = Object.getOwnPropertyDescriptor(\n HTMLScriptElement.prototype,\n \"src\",\n );\n const originalScriptSrcSetter = scriptSrcDescriptor && scriptSrcDescriptor.set;\n const originalScriptSrcGetter = scriptSrcDescriptor && scriptSrcDescriptor.get;\n const originalScriptSetAttribute = HTMLScriptElement.prototype.setAttribute;\n\n const duplicateIfGA4Url = (urlString: string) => {\n try {\n if (!ctx.isTargetUrl(urlString)) return;\n fetch(ctx.buildDuplicateUrl(urlString), { method: \"GET\", keepalive: true }).catch(\n (error) => {\n if (ctx.debug)\n console.error(\"gtm interceptor: error duplicating script GET:\", error);\n },\n );\n } catch {\n // Intentionally empty\n }\n };\n\n if (originalScriptSrcSetter && originalScriptSrcGetter) {\n const setter = originalScriptSrcSetter;\n const getter = originalScriptSrcGetter;\n Object.defineProperty(HTMLScriptElement.prototype, \"src\", {\n configurable: true,\n enumerable: true,\n get: function (this: any) {\n return getter.call(this);\n },\n set: function (this: any, value: string) {\n try {\n const last = this.__ga4LastSrcDuplicated;\n if (value && value !== last) {\n duplicateIfGA4Url(String(value));\n this.__ga4LastSrcDuplicated = String(value);\n }\n const self = this;\n const onloadOnce = function () {\n try {\n const finalUrl = self.src;\n if (finalUrl && finalUrl !== self.__ga4LastSrcDuplicated) {\n duplicateIfGA4Url(finalUrl);\n self.__ga4LastSrcDuplicated = finalUrl;\n }\n } catch {}\n self.removeEventListener(\"load\", onloadOnce);\n };\n this.addEventListener(\"load\", onloadOnce);\n } catch {}\n setter.call(this, value);\n },\n });\n }\n\n HTMLScriptElement.prototype.setAttribute = function (\n this: any,\n name: string,\n value: string,\n ) {\n try {\n if (String(name).toLowerCase() === \"src\") {\n const v = String(value);\n const last = this.__ga4LastSrcDuplicated;\n if (v && v !== last) {\n duplicateIfGA4Url(v);\n this.__ga4LastSrcDuplicated = v;\n }\n const selfAttr = this;\n const onloadOnceAttr = function () {\n try {\n const finalUrlAttr = selfAttr.src;\n if (finalUrlAttr && finalUrlAttr !== selfAttr.__ga4LastSrcDuplicated) {\n duplicateIfGA4Url(finalUrlAttr);\n selfAttr.__ga4LastSrcDuplicated = finalUrlAttr;\n }\n } catch {}\n selfAttr.removeEventListener(\"load\", onloadOnceAttr);\n };\n this.addEventListener(\"load\", onloadOnceAttr);\n }\n } catch {\n // Intentionally empty\n }\n return originalScriptSetAttribute.apply(this, arguments as any);\n };\n } catch {}\n }\n }\n\n if ((window as any).__ga4DuplicatorInitialized) {\n if (options.debug) console.warn(\"GA4 Duplicator: already initialized.\");\n return;\n }\n\n const destinations: GA4Destination[] = [];\n if (options.destinations && Array.isArray(options.destinations)) {\n for (let i = 0; i < options.destinations.length; i++) {\n destinations.push(options.destinations[i]);\n }\n }\n\n if (options.server_container_url) {\n destinations.push({\n measurement_id: \"*\",\n server_container_url: options.server_container_url,\n });\n }\n\n if (destinations.length === 0) {\n console.error(\"GA4 Duplicator: either server_container_url or destinations array is required\");\n return;\n }\n\n function normalizePath(p: string): string {\n p = String(p || \"\");\n p = p.replace(/\\/+$/, \"\");\n return p === \"\" ? \"/\" : p;\n }\n\n function matchesId(pattern: string, id: string): boolean {\n if (!pattern || pattern === \"*\") return true;\n try {\n const regexStr = pattern.replace(/[.+^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/\\*/g, \".*\");\n return new RegExp(\"^\" + regexStr + \"$\", \"i\").test(id);\n } catch {\n return pattern.toLowerCase() === id.toLowerCase();\n }\n }\n\n function getMeasurementId(url: string): string {\n try {\n const parsed = new URL(url, location.href);\n return parsed.searchParams.get(\"tid\") || parsed.searchParams.get(\"id\") || \"\";\n } catch {\n const match = url.match(/[?&](?:tid|id)=([^&?#]+)/);\n return match ? decodeURIComponent(match[1]) : \"\";\n }\n }\n\n function getDestinationForId(id: string): GA4Destination | null {\n for (let i = 0; i < destinations.length; i++) {\n if (matchesId(destinations[i].measurement_id, id)) {\n return destinations[i];\n }\n }\n return null;\n }\n\n function getDuplicateEndpointUrl(dest: GA4Destination): URL {\n const trackingURL = String(dest.server_container_url || \"\").trim();\n const u = new URL(trackingURL, location.href);\n u.search = \"\";\n u.hash = \"\";\n return u;\n }\n\n function isTargetUrl(url: string | null | undefined): boolean {\n if (!url || typeof url !== \"string\") return false;\n try {\n const parsed = new URL(url, location.href);\n\n for (let i = 0; i < destinations.length; i++) {\n const duplicateTarget = getDuplicateEndpointUrl(destinations[i]);\n if (\n parsed.origin === duplicateTarget.origin &&\n normalizePath(parsed.pathname) === normalizePath(duplicateTarget.pathname)\n ) {\n return false;\n }\n }\n\n const params = parsed.searchParams;\n const hasGtm = params.has(\"gtm\");\n const hasTagExp = params.has(\"tag_exp\");\n const measurementId = params.get(\"tid\") || params.get(\"id\") || \"\";\n const isMeasurementIdGA4 = /^G-[A-Z0-9]+$/i.test(measurementId);\n\n return hasGtm && hasTagExp && isMeasurementIdGA4;\n } catch {\n if (typeof url === \"string\") {\n for (let j = 0; j < destinations.length; j++) {\n try {\n const target = getDuplicateEndpointUrl(destinations[j]);\n const targetNoQuery = target.origin + target.pathname;\n if (url.indexOf(targetNoQuery) !== -1) return false;\n } catch {\n // Intentionally empty\n }\n }\n\n const hasGtmFallback = url.indexOf(\"gtm=\") !== -1;\n const hasTagExpFallback = url.indexOf(\"tag_exp=\") !== -1;\n const idMatch = url.match(/[?&](?:tid|id)=G-[A-Za-z0-9]+/);\n return !!(hasGtmFallback && hasTagExpFallback && idMatch);\n }\n return false;\n }\n }\n\n function buildDuplicateUrl(originalUrl: string): string {\n const id = getMeasurementId(originalUrl);\n const dest = getDestinationForId(id);\n if (!dest) return \"\";\n\n const dst = getDuplicateEndpointUrl(dest);\n try {\n const src = new URL(originalUrl, location.href);\n dst.search = src.search;\n } catch {}\n return dst.toString();\n }\n\n const context: InterceptorContext = {\n debug: !!options.debug,\n isTargetUrl,\n buildDuplicateUrl,\n };\n\n const interceptors: NetworkInterceptor[] = [\n new FetchInterceptor(),\n new XhrInterceptor(),\n new BeaconInterceptor(),\n new ScriptInterceptor(),\n ];\n\n for (let i = 0; i < interceptors.length; i++) {\n try {\n interceptors[i].install(context);\n } catch (e) {\n if (options.debug) console.error(\"GA4 Duplicator: failed to install interceptor\", e);\n }\n }\n\n (window as any).__ga4DuplicatorInitialized = true;\n};\n"],
5
+ "mappings": "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",
6
+ "names": ["options", "FetchInterceptor", "ctx", "originalFetch", "resource", "config", "requestUrl", "method", "upperMethod", "prepareBodyPromise", "e", "originalPromise", "duplicateUrl", "error", "dupBody", "XhrInterceptor", "originalXHROpen", "originalXHRSend", "url", "body", "originalResult", "dupErr", "BeaconInterceptor", "originalSendBeacon", "data", "ScriptInterceptor", "scriptSrcDescriptor", "originalScriptSrcSetter", "originalScriptSrcGetter", "originalScriptSetAttribute", "duplicateIfGA4Url", "urlString", "setter", "getter", "value", "last", "self", "onloadOnce", "finalUrl", "name", "v", "selfAttr", "onloadOnceAttr", "finalUrlAttr", "destinations", "i", "normalizePath", "p", "matchesId", "pattern", "id", "regexStr", "getMeasurementId", "parsed", "match", "getDestinationForId", "getDuplicateEndpointUrl", "dest", "trackingURL", "u", "isTargetUrl", "duplicateTarget", "params", "hasGtm", "hasTagExp", "measurementId", "isMeasurementIdGA4", "j", "target", "targetNoQuery", "hasGtmFallback", "hasTagExpFallback", "idMatch", "buildDuplicateUrl", "originalUrl", "dst", "src", "context", "interceptors"]
7
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@d8a-tech/ga4-duplicator",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "GA4 network request duplicator (browser inline script) - TypeScript source + minified dist build.",
6
+ "license": "MIT",
7
+ "publishConfig": {
8
+ "access": "public"
9
+ },
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/d8a-tech/d8a.git",
13
+ "directory": "js/ga4-duplicator"
14
+ },
15
+ "bugs": {
16
+ "url": "https://github.com/d8a-tech/d8a/issues"
17
+ },
18
+ "homepage": "https://d8a.tech",
19
+ "files": [
20
+ "dist/",
21
+ "README.md",
22
+ "LICENSE",
23
+ "src.hash"
24
+ ],
25
+ "scripts": {
26
+ "prepack": "npm run build",
27
+ "build": "node scripts/build.mjs",
28
+ "hash": "node scripts/hash.mjs",
29
+ "typecheck": "tsc -p . --noEmit",
30
+ "lint": "eslint .",
31
+ "lint:fix": "eslint . --fix",
32
+ "format": "prettier . --check",
33
+ "format:fix": "prettier . --write",
34
+ "test": "vitest run src/*.test.ts",
35
+ "test:e2e": "npm run build && playwright test"
36
+ },
37
+ "devDependencies": {
38
+ "@eslint/js": "^9.36.0",
39
+ "@playwright/test": "^1.57.0",
40
+ "@types/node": "^20.0.0",
41
+ "@typescript-eslint/eslint-plugin": "^8.52.0",
42
+ "@typescript-eslint/parser": "^8.52.0",
43
+ "esbuild": "^0.25.9",
44
+ "eslint-config-prettier": "^10.1.8",
45
+ "globals": "^17.0.0",
46
+ "jsdom": "^26.1.0",
47
+ "prettier": "^3.6.2",
48
+ "terser": "^5.36.0",
49
+ "tsx": "^4.19.0",
50
+ "typescript": "~5.6.2",
51
+ "vitest": "^3.2.4"
52
+ }
53
+ }
package/src.hash ADDED
@@ -0,0 +1 @@
1
+ b864d43d6f15b9f68f156d17e02996f9b61cf35127cfd03b8d79c9c5e501c80c -