PageSourceSearch

https://www.bigdropinc.com/wp-content/plugins/getautoseo-ai-conten…/assets/js/conversion-tracker.js?ver=1.3.95

js bigdropinc.com collected 2026-10-02 03:40:57 UTC 16,020 bytes, 431 lines download raw bytes

1/**
2 * AutoSEO Conversion Tracker
3 *
4 * Intercepts Google Ads (gtag / dataLayer) and Facebook Pixel (fbq)
5 * conversion events and beacons them to the local WP REST proxy,
6 * which forwards to the AutoSEO API server-side.
7 *
8 * SAFETY INVARIANT: The original gtag/fbq/dataLayer.push calls are
9 * NEVER wrapped in try/catch -- they execute exactly as they would
10 * without this script. Only our own tracking logic is guarded.
11 */
12(function () {
13    'use strict';
14
15    var currentScript = document.currentScript || {};
16    var config = window.autoseoConversionTracker || {};
17    if (!config.endpoint && currentScript.getAttribute) {
18        config.endpoint = currentScript.getAttribute('data-endpoint') || '';
19        config.token = currentScript.getAttribute('data-token') || '';
20        config.pageviewEndpoint = currentScript.getAttribute('data-pageview-endpoint') || '';
21        config.articleId = currentScript.getAttribute('data-article-id') || '';
22    }
23    if (!config.endpoint) return;
24
25    var CONVERSION_EVENTS_GTAG = ['conversion', 'purchase'];
26    var CONVERSION_EVENTS_FBQ = ['Purchase', 'Subscribe', 'CompleteRegistration', 'Lead', 'StartTrial'];
27    var DATALAYER_EVENTS = ['purchase', 'conversion'];
28    var ATTRIBUTION_WINDOW_MS = 30 * 24 * 60 * 60 * 1000;
29    var VISITOR_STORAGE_KEY = 'autoseo_visitor_id';
30    var ATTRIBUTION_STORAGE_KEY = 'autoseo_last_article_attribution';
31
32    var queue = [];
33    var flushTimer = null;
34    var seen = {};
35    var visitorId = getVisitorId();
36    var currentArticleView = null;
37
38    captureInboundAttribution();
39    recordCurrentArticleView();
40
41    function dedupeKey(source, type, value) {
42        return source + '|' + type + '|' + (value || '') + '|' + Math.floor(Date.now() / 2000);
43    }
44
45    function enqueue(source, type, params) {
46        var key = dedupeKey(source, type, params && params.value);
47        if (seen[key]) return;
48        seen[key] = true;
49
50        var val = params && params.value ? parseFloat(params.value) : null;
51        if (val !== null && (isNaN(val) || !isFinite(val))) val = null;
52        var attribution = getStoredAttribution();
53
54        var event = {
55            event_source: source,
56            event_type: String(type).substring(0, 100),
57            event_value: val,
58            event_currency: params && (params.currency || params.Currency) || null,
59            page_url: (location.pathname + location.search).substring(0, 500),
60            referrer_url: document.referrer ? document.referrer.substring(0, 500) : null,
61            visitor_id: visitorId,
62            timestamp: new Date().toISOString()
63        };
64
65        if (attribution) {
66            event.attributed_article_id = attribution.article_id;
67            event.article_viewed_at = attribution.viewed_at;
68            event.article_attribution_source = attribution.source || null;
69        }
70
71        queue.push(event);
72
73        if (!flushTimer) {
74            flushTimer = setTimeout(flush, 1500);
75        }
76    }
77
78    function storageGet(key) {
79        try {
80            return localStorage.getItem(key);
81        } catch (e) {
82            return null;
83        }
84    }
85
86    function storageSet(key, value) {
87        try {
88            localStorage.setItem(key, value);
89        } catch (e) {}
90    }
91
92    function getVisitorId() {
93        var inbound = getQueryParam('autoseo_vid');
94        if (isValidVisitorId(inbound)) {
95            storageSet(VISITOR_STORAGE_KEY, inbound);
96            return inbound;
97        }
98
99        var existing = storageGet(VISITOR_STORAGE_KEY);
100        if (isValidVisitorId(existing)) return existing;
101
102        var generated = 'as_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 12);
103        storageSet(VISITOR_STORAGE_KEY, generated);
104        return generated;
105    }
106
107    function isValidVisitorId(value) {
108        return typeof value === 'string' && value.length > 0 && value.length <= 128 && /^[A-Za-z0-9._:-]+$/.test(value);
109    }
110
111    function getQueryParam(name) {
112        try {
113            return new URLSearchParams(window.location.search).get(name);
114        } catch (e) {
115            return null;
116        }
117    }
118
119    function captureInboundAttribution() {
120        var articleId = parseInt(getQueryParam('autoseo_aid'), 10);
121        if (!articleId || articleId < 1) return;
122
123        var viewedAt = getQueryParam('autoseo_avt') || new Date().toISOString();
124        var viewedTime = Date.parse(viewedAt);
125        if (!viewedTime || Date.now() - viewedTime > ATTRIBUTION_WINDOW_MS) return;
126
127        storeAttribution({
128            article_id: articleId,
129            viewed_at: new Date(viewedTime).toISOString(),
130            source: (getQueryParam('autoseo_src') || 'hosted_blog_link').substring(0, 50)
131        });
132    }
133
134    function recordCurrentArticleView() {
135        var articleId = parseInt(config.articleId, 10);
136        if (!articleId || articleId < 1) return;
137
138        var viewedAt = new Date().toISOString();
139        currentArticleView = {
140            articleId: articleId,
141            viewedAt: viewedAt,
142            startedAt: Date.now(),
143            pageviewSent: false,
144            timeOnPageSent: false
145        };
146        storeAttribution({
147            article_id: articleId,
148            viewed_at: viewedAt,
149            source: 'wordpress'
150        });
151        sendArticlePageview(articleId, viewedAt);
152    }
153
154    function storeAttribution(attribution) {
155        storageSet(ATTRIBUTION_STORAGE_KEY, JSON.stringify(attribution));
156    }
157
158    function getStoredAttribution() {
159        try {
160            var raw = storageGet(ATTRIBUTION_STORAGE_KEY);
161            if (!raw) return null;
162            var attribution = JSON.parse(raw);
163            var articleId = parseInt(attribution.article_id, 10);
164            var viewedTime = Date.parse(attribution.viewed_at);
165            if (!articleId || !viewedTime || Date.now() - viewedTime > ATTRIBUTION_WINDOW_MS) return null;
166            return {
167                article_id: articleId,
168                viewed_at: new Date(viewedTime).toISOString(),
169                source: attribution.source ? String(attribution.source).substring(0, 50) : null
170            };
171        } catch (e) {
172            return null;
173        }
174    }
175
176    function sendArticlePageview(articleId, viewedAt) {
177        if (!config.pageviewEndpoint) return;
178
179        var sessionKey = 'autoseo_pv_sent_' + articleId + '_' + location.pathname;
180        try {
181            if (sessionStorage.getItem(sessionKey)) return;
182            sessionStorage.setItem(sessionKey, '1');
183        } catch (e) {}
184
185        var payload = JSON.stringify({
186            article_id: articleId,
187            visitor_id: visitorId,
188            event_type: 'pageview',
189            page_url: (location.pathname + location.search).substring(0, 500),
190            referrer_url: document.referrer ? document.referrer.substring(0, 500) : null,
191            timestamp: viewedAt
192        });
193
194        try {
195            if (navigator.sendBeacon) {
196                var blob = new Blob([payload], { type: 'application/json' });
197                if (navigator.sendBeacon(config.pageviewEndpoint, blob)) {
198                    markArticlePageviewSent(articleId);
199                    return;
200                }
201            }
202            if (window.fetch) {
203                fetch(config.pageviewEndpoint, {
204                    method: 'POST',
205                    body: payload,
206                    headers: { 'Content-Type': 'application/json' },
207                    keepalive: true,
208                    credentials: 'same-origin'
209                }).then(function (response) {
210                    if (response.ok) markArticlePageviewSent(articleId);
211                }).catch(function () {});
212            }
213        } catch (e) {}
214    }
215
216    function markArticlePageviewSent(articleId) {
217        if (!currentArticleView || currentArticleView.articleId !== articleId) return;
218        currentArticleView.pageviewSent = true;
219    }
220
221    function sendArticleTimeOnPage() {
222        if (!currentArticleView || !currentArticleView.pageviewSent || currentArticleView.timeOnPageSent) return;
223        if (!config.pageviewEndpoint) return;
224
225        var durationSeconds = Math.round((Date.now() - currentArticleView.startedAt) / 1000);
226        if (durationSeconds < 3) return;
227        if (durationSeconds > 3600) durationSeconds = 3600;
228
229        currentArticleView.timeOnPageSent = true;
230        var payload = JSON.stringify({
231            article_id: currentArticleView.articleId,
232            visitor_id: visitorId,
233            event_type: 'time_on_page',
234            duration_seconds: durationSeconds,
235            page_url: (location.pathname + location.search).substring(0, 500),
236            referrer_url: document.referrer ? document.referrer.substring(0, 500) : null,
237            timestamp: currentArticleView.viewedAt
238        });
239
240        try {
241            if (navigator.sendBeacon) {
242                var blob = new Blob([payload], { type: 'application/json' });
243                if (navigator.sendBeacon(config.pageviewEndpoint, blob)) return;
244            }
245            if (window.fetch) {
246                fetch(config.pageviewEndpoint, {
247                    method: 'POST',
248                    body: payload,
249                    headers: { 'Content-Type': 'application/json' },
250                    keepalive: true,
251                    credentials: 'same-origin'
252                }).catch(function () {});
253            }
254        } catch (e) {}
255    }
256
257    function flush() {
258        flushTimer = null;
259        if (!queue.length) return;
260
261        var events = queue.splice(0, 20);
262        var payload = JSON.stringify({
263            token: config.token || '',
264            events: events
265        });
266
267        try {
268            if (navigator.sendBeacon) {
269                var blob = new Blob([payload], { type: 'application/json' });
270                if (navigator.sendBeacon(config.endpoint, blob)) {
271                    return;
272                }
273            }
274            if (window.fetch) {
275                fetch(config.endpoint, {
276                    method: 'POST',
277                    body: payload,
278                    headers: { 'Content-Type': 'application/json' },
279                    keepalive: true,
280                    credentials: 'same-origin'
281                }).catch(function () {});
282                return;
283            }
284            var xhr = new XMLHttpRequest();
285            xhr.open('POST', config.endpoint, true);
286            xhr.setRequestHeader('Content-Type', 'application/json');
287            xhr.send(payload);
288        } catch (e) {}
289    }
290
291    // --- gtag() interception ---
292    function wrapGtag() {
293        if (!window.gtag || window.__autoseo_gtag_wrapped) return;
294        if (typeof window.gtag !== 'function') return;
295        var original = window.gtag;
296        window.__autoseo_gtag_wrapped = true;
297        window.gtag = function () {
298            // Original runs UNGUARDED -- behaves exactly as if we weren't here
299            var result = original.apply(this, arguments);
300            // Our tracking is guarded -- failures are silent and harmless
301            try {
302                if (arguments[0] === 'event' && CONVERSION_EVENTS_GTAG.indexOf(arguments[1]) !== -1) {
303                    enqueue('google_ads', arguments[1], arguments[2] || {});
304                }
305            } catch (e) {}
306            return result;
307        };
308    }
309
310    // --- dataLayer.push() interception ---
311    var wrappedDlInstance = null;
312    function wrapDataLayer() {
313        var dl = window.dataLayer;
314        if (!dl || !Array.isArray(dl)) return;
315        if (dl === wrappedDlInstance) return;
316        wrappedDlInstance = dl;
317        var originalPush = dl.push;
318        dl.push = function () {
319            // Original runs UNGUARDED
320            var result = originalPush.apply(dl, arguments);
321            // Our tracking is guarded
322            try {
323                for (var i = 0; i < arguments.length; i++) {
324                    var obj = arguments[i];
325                    if (obj && typeof obj === 'object') {
326                        var evt = obj.event || (obj[0] === 'event' ? obj[1] : null);
327                        if (evt && DATALAYER_EVENTS.indexOf(evt.toLowerCase ? evt.toLowerCase() : evt) !== -1) {
328                            var ecom = obj.ecommerce || obj;
329                            enqueue('dataLayer', evt, {
330                                value: ecom.value || null,
331                                currency: ecom.currency || null
332                            });
333                        }
334                    }
335                }
336            } catch (e) {}
337            return result;
338        };
339    }
340
341    // --- fbq() interception ---
342    // Keep window.fbq itself intact. The standard Meta bootstrap stub closes over
343    // its original function object; replacing window.fbq before fbevents.js adds
344    // callMethod can leave real pixel calls stuck in the old queue.
345    function wrapFbq() {
346        if (!window.fbq || window.__autoseo_fbq_wrapped) return;
347        if (typeof window.fbq !== 'function') return;
348        if (typeof window.fbq.callMethod !== 'function') return;
349
350        var originalCallMethod = window.fbq.callMethod;
351        window.__autoseo_fbq_wrapped = true;
352        window.fbq.callMethod = function () {
353            // Original runs UNGUARDED
354            var result = originalCallMethod.apply(this, arguments);
355            // Our tracking is guarded
356            try {
357                if (arguments[0] === 'track' && CONVERSION_EVENTS_FBQ.indexOf(arguments[1]) !== -1) {
358                    enqueue('facebook', arguments[1], arguments[2] || {});
359                }
360            } catch (e) {}
361            return result;
362        };
363    }
364
365    // --- Safe wrapper: if wrapping itself fails, the original global is untouched ---
366    function safeWrap(fn) {
367        try { fn(); } catch (e) {}
368    }
369
370    function tryWrapAll() {
371        safeWrap(wrapGtag);
372        safeWrap(wrapDataLayer);
373        safeWrap(wrapFbq);
374    }
375
376    // Wrap immediately if already loaded
377    tryWrapAll();
378
379    // Use defineProperty to catch late-loading scripts that create gtag/fbq/dataLayer
380    function watchProperty(name, wrapFn) {
381        if (window[name]) return;
382        var value;
383        var insideSetter = false;
384        try {
385            Object.defineProperty(window, name, {
386                configurable: true,
387                get: function () { return value; },
388                set: function (v) {
389                    if (insideSetter) { value = v; return; }
390                    insideSetter = true;
391                    value = v;
392                    // Restore normal property FIRST, before calling wrapFn.
393                    // Even if wrapFn throws, the property is restored and usable.
394                    try {
395                        Object.defineProperty(window, name, {
396                            configurable: true,
397                            writable: true,
398                            value: v
399                        });
400                    } catch (e) {
401                        // defineProperty restore failed; value is still readable
402                        // via the getter. Don't try window[name]=v (re-enters setter).
403                    }
404                    insideSetter = false;
405                    safeWrap(wrapFn);
406                }
407            });
408        } catch (e) {
409            // defineProperty on window not supported -- poll fallback handles it
410        }
411    }
412
413    watchProperty('gtag', wrapGtag);
414    watchProperty('fbq', wrapFbq);
415    watchProperty('dataLayer', wrapDataLayer);
416
417    // Fallback poll for environments where defineProperty on window fails
418    var pollCount = 0;
419    var pollInterval = setInterval(function () {
420        tryWrapAll();
421        pollCount++;
422        if (pollCount > 20) clearInterval(pollInterval);
423    }, 500);
424
425    // Flush on page unload
426    try {
427        window.addEventListener('pagehide', sendArticleTimeOnPage);
428        window.addEventListener('pagehide', flush);
429        window.addEventListener('beforeunload', flush);
430    } catch (e) {}
431})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.