PageSourceSearch

https://www.alphuel.com/replay-tracker.js

js alphuel.com collected 2026-10-04 03:10:42 UTC 9,547 bytes, 225 lines download raw bytes

1// replay-tracker.js — Hardened v1.1.1
2// All tracking routed through server-side WP REST proxy (API key never exposed to browser)
3
4(function() {
5    'use strict';
6
7    function initReplayTracker() {
8        var config = window.ReplayObj;
9        if (!config || !config.proxyUrl) return;
10
11        var PROXY_URL = config.proxyUrl;
12
13        // Use PostHog session ID if available, otherwise generate local fallback
14        var sessionId = sessionStorage.getItem('replay_session');
15        var phSessionId = window.posthog && typeof window.posthog.get_session_id === 'function' ? window.posthog.get_session_id() : null;
16        if (phSessionId) {
17            sessionId = phSessionId;
18            sessionStorage.setItem('replay_session', sessionId);
19        } else if (!sessionId) {
20            if (typeof crypto !== 'undefined' && crypto.randomUUID) {
21                sessionId = 'sess_' + crypto.randomUUID();
22            } else if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
23                var arr = new Uint8Array(16);
24                crypto.getRandomValues(arr);
25                sessionId = 'sess_' + Array.from(arr, function(b) {
26                    return b.toString(16).padStart(2, '0');
27                }).join('');
28            } else {
29                // Last-resort fallback for very old browsers
30                sessionId = 'sess_' + Date.now().toString(36) + Math.random().toString(36).substr(2, 12);
31            }
32            sessionStorage.setItem('replay_session', sessionId);
33        }
34
35        var visitorInfo = {
36            sessionId: sessionId,
37            url: window.location.href,
38            userAgent: navigator.userAgent,
39            screenWidth: window.innerWidth,
40            screenHeight: window.innerHeight,
41            language: navigator.language,
42            timestamp: new Date().toISOString(),
43            referrer: document.referrer || ''
44        };
45
46        // Helper: route all requests through the WP REST proxy (no API key in browser)
47        function proxyFetch(endpoint, body) {
48            return fetch(PROXY_URL + '/track/' + endpoint, {
49                method: 'POST',
50                headers: { 'Content-Type': 'application/json' },
51                body: JSON.stringify(body)
52            });
53        }
54
55        // 1. Geo lookup + initial visitor ping
56        fetch('https://get.geojs.io/v1/ip/geo.json')
57            .then(function(res) { return res.json(); })
58            .then(function(geo) {
59                visitorInfo.country = geo.country;
60                visitorInfo.state = geo.region;
61                visitorInfo.city = geo.city;
62                visitorInfo.latitude = geo.latitude ? parseFloat(geo.latitude) : null;
63                visitorInfo.longitude = geo.longitude ? parseFloat(geo.longitude) : null;
64                sendInitialPing();
65            })
66            .catch(function() { sendInitialPing(); });
67
68        function sendInitialPing() {
69            proxyFetch('visitor', visitorInfo)
70                .then(function(res) { return res.json(); })
71                .then(function() {
72                    var currentUrl = window.location.href.toLowerCase();
73                    var eventType = 'page_view';
74                    if (currentUrl.indexOf('/checkout') !== -1) {
75                        eventType = 'checkout_view';
76                    } else if (currentUrl.indexOf('/cart') !== -1) {
77                        eventType = 'cart_view';
78                    }
79                    proxyFetch('event', {
80                        sessionId: sessionId,
81                        event_type: eventType,
82                        url: window.location.href,
83                        timestamp: new Date().toISOString()
84                    });
85                })
86                .catch(function() {});
87        }
88
89        // 3. WooCommerce add-to-cart click tracking
90        document.addEventListener('click', function(e) {
91            var target = e.target;
92            if (target.classList && target.classList.contains('add_to_cart_button')) {
93                proxyFetch('event', {
94                    sessionId: sessionId,
95                    event_type: 'client_add_to_cart',
96                    timestamp: new Date().toISOString()
97                });
98            }
99        });
100
101        // 4. WooCommerce purchase & order tracking
102        function extractWooCommerceTotal() {
103            var selectors = [
104                '.woocommerce-order-overview__total .woocommerce-Price-amount',
105                '.woocommerce-order-overview__total strong',
106                '.woocommerce-checkout-review-order-table .order-total .woocommerce-Price-amount',
107                '.order_details tfoot .woocommerce-Price-amount',
108                '.woocommerce-Price-amount bdi',
109                '.woocommerce-Price-amount'
110            ];
111
112            for (var i = 0; i < selectors.length; i++) {
113                var element = document.querySelector(selectors[i]);
114                if (element) {
115                    var text = element.innerText || element.textContent || '';
116                    var cleaned = text.replace(/[^\d.,]/g, '');
117                    if (cleaned.indexOf(',') !== -1 && cleaned.indexOf('.') !== -1) {
118                        cleaned = cleaned.replace(/,/g, '');
119                    } else if (cleaned.indexOf(',') !== -1 && cleaned.indexOf('.') === -1) {
120                        var parts = cleaned.split(',');
121                        if (parts[1] && parts[1].length === 2) {
122                            cleaned = cleaned.replace(',', '.');
123                        } else {
124                            cleaned = cleaned.replace(/,/g, '');
125                        }
126                    }
127                    var price = parseFloat(cleaned);
128                    if (!isNaN(price) && price > 0) { return price; }
129                }
130            }
131            return null;
132        }
133
134        function extractWooCommerceOrderId() {
135            var orderEl = document.querySelector('.woocommerce-order-overview__order strong');
136            if (orderEl) {
137                var orderId = orderEl.textContent.trim();
138                if (orderId) return orderId;
139            }
140            var pathMatch = window.location.pathname.match(/\/order-received\/(\d+)/);
141            if (pathMatch && pathMatch[1]) return pathMatch[1];
142            var params = new URLSearchParams(window.location.search);
143            var orderParam = params.get('order-received');
144            if (orderParam) return orderParam;
145            return 'unknown_order';
146        }
147
148        var isOrderReceivedPage = window.location.pathname.indexOf('/order-received') !== -1 ||
149                                   window.location.search.indexOf('order-received') !== -1 ||
150                                   document.querySelector('.woocommerce-order-overview') !== null;
151
152        if (isOrderReceivedPage) {
153            var orderId = extractWooCommerceOrderId();
154            var trackingKey = 'tracked_order_' + orderId;
155
156            if (!localStorage.getItem(trackingKey)) {
157                var total = extractWooCommerceTotal() || 0;
158                localStorage.setItem(trackingKey, 'true');
159
160                proxyFetch('event', {
161                    sessionId: sessionId,
162                    event_type: 'purchase',
163                    url: window.location.href,
164                    timestamp: new Date().toISOString(),
165                    order_id: orderId,
166                    total: total,
167                    price: total
168                });
169            }
170        }
171
172        // 5. Heartbeat to keep session alive
173        setInterval(function() {
174            if (document.visibilityState === 'visible') {
175                proxyFetch('visitor', { sessionId: sessionId }).catch(function() {});
176            }
177        }, 60000);
178
179        // 6. Page leave
180        window.addEventListener('beforeunload', function() {
181            var payload = JSON.stringify({
182                sessionId: sessionId,
183                event_type: 'page_leave',
184                url: window.location.href,
185                timestamp: new Date().toISOString()
186            });
187
188            if (navigator.sendBeacon) {
189                var blob = new Blob([payload], { type: 'application/json' });
190                navigator.sendBeacon(PROXY_URL + '/track/event', blob);
191            } else {
192                fetch(PROXY_URL + '/track/event', {
193                    method: 'POST',
194                    keepalive: true,
195                    headers: { 'Content-Type': 'application/json' },
196                    body: payload
197                }).catch(function() {});
198            }
199        });
200    }
201
202    // A robust bootstrapper that waits for PostHog to be fully ready before starting
203    var bootstrapper = function() {
204        var attempts = 0;
205        var tryInit = function() {
206            var hasPostHog = window.posthog && typeof window.posthog.get_session_id === 'function' && window.posthog.get_session_id();
207            if (window.ReplayObj && hasPostHog) {
208                initReplayTracker();
209            } else if (attempts < 40) { // Wait up to 10 seconds (40 * 250ms)
210                attempts++;
211                setTimeout(tryInit, 250);
212            } else if (window.ReplayObj) {
213                // Fallback to local session ID generation if PostHog fails to load (e.g. adblocker)
214                initReplayTracker();
215            }
216        };
217        tryInit();
218    };
219
220    if (document.readyState === 'loading') {
221        document.addEventListener('DOMContentLoaded', bootstrapper);
222    } else {
223        bootstrapper();
224    }
225})();

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.