PageSourceSearch

https://abidpro.org/wp-content/plugins/authorize-net-payments-for-…/frontend/anet-sentry-browser.js?ver=1.0.5

js abidpro.org collected 2026-10-09 22:41:24 UTC 19,252 bytes, 464 lines download raw bytes

1/**
2 * Sentry Browser Integration for Authorize.net Payments
3 * 
4 * Captures JavaScript errors, payment form failures, and network issues
5 * Sends them to Sentry for monitoring
6 */
7(function () {
8    'use strict';
9    // Sentry Browser Helper - matches server-side implementation
10    window.AnetSentryBrowser = {
11        initialized: false,
12        dsn: null,
13        environment: 'production',
14        breadcrumbs: [],
15        maxBreadcrumbs: 50,
16        /**
17         * Initialize Sentry Browser SDK
18         */
19        init: function (config) {
20            if (this.initialized) return;
21            this.dsn = config.dsn || null;
22            this.environment = config.environment || 'production';
23            if (!this.dsn) {
24                console.warn('Sentry DSN not configured - error tracking disabled');
25                return;
26            }
27            this.initialized = true;
28        },
29        /**
30         * Check if Sentry is available
31         */
32        isAvailable: function () {
33            return this.initialized && this.dsn;
34        },
35        /**
36         * Capture JavaScript exception
37         */
38        captureException: function (error, context) {
39            if (!this.isAvailable()) return false;
40            try {
41                var eventData = this.buildEventData('error', error.message || error.toString(), error, context);
42                return this.sendToSentry(eventData);
43            } catch (e) {
44                console.error('Failed to capture exception:', e);
45                return false;
46            }
47        },
48        /**
49         * Capture error message
50         */
51        captureMessage: function (message, level, context) {
52            if (!this.isAvailable()) return false;
53            try {
54                var eventData = this.buildEventData(level || 'error', message, null, context);
55                return this.sendToSentry(eventData);
56            } catch (e) {
57                console.error('Failed to capture message:', e);
58                return false;
59            }
60        },
61        /**
62         * Build Sentry event data
63         */
64        buildEventData: function (level, message, error, context) {
65            context = context || {};
66            var event = {
67                event_id: this.generateEventId(),
68                timestamp: Math.floor(Date.now() / 1000),
69                level: level,
70                message: message,
71                platform: 'javascript',
72                sdk: {
73                    name: 'anet-sentry-browser',
74                    version: '1.0.0'
75                },
76                tags: {
77                    plugin: 'authorize-net-payments',
78                    environment: this.environment,
79                    browser: this.getBrowserInfo(),
80                    url: window.location.href,
81                    page_type: this.getPageType()
82                },
83                contexts: {
84                    browser: {
85                        name: navigator.userAgent,
86                        version: navigator.appVersion
87                    },
88                    page: {
89                        url: window.location.href,
90                        referrer: document.referrer
91                    }
92                },
93                extra: {
94                    error_message: message
95                }
96            };
97            // Add exception details with stack trace
98            if (error && error.stack) {
99                event.exception = {
100                    values: [{
101                        type: error.name || 'Error',
102                        value: error.message || message,
103                        stacktrace: {
104                            frames: this.parseStackTrace(error.stack)
105                        }
106                    }]
107                };
108            }
109            // Add Custome context tags 
110            if (context.payment_type) event.tags.payment_type = context.payment_type;
111            if (context.error_type) event.tags.error_type = context.error_type;
112            if (context.order_id) event.tags.order_id = context.order_id;
113            if (context.gateway_id) event.tags.gateway_id = context.gateway_id;
114            if (context.extra) {
115                for (var key in context.extra) {
116                    if (context.extra.hasOwnProperty(key)) {
117                        event.extra[key] = context.extra[key];
118                    }
119                }
120            }
121            // Add breadcrumbs trail
122            if (this.breadcrumbs.length > 0) {
123                event.breadcrumbs = {
124                    values: this.breadcrumbs.slice()
125                };
126            }
127            return event;
128        },
129        /**
130         * Add breadcrumb to track events leading to errors
131         */
132        addBreadcrumb: function (message, category, level, data) {
133            category = category || 'default';
134            level = level || 'info';
135            // Limit breadcrumbs to prevent memory issues
136            if (this.breadcrumbs.length >= this.maxBreadcrumbs) {
137                this.breadcrumbs.shift(); // Remove oldest
138            }
139            var breadcrumb = {
140                timestamp: Math.floor(Date.now() / 1000),
141                message: message,
142                category: category,
143                level: level
144            };
145            if (data) {
146                breadcrumb.data = data;
147            }
148            this.breadcrumbs.push(breadcrumb);
149        },
150        /**
151         * Clear all breadcrumbs
152         */
153        clearBreadcrumbs: function () {
154            this.breadcrumbs = [];
155        },
156        /**
157         * Get all breadcrumbs
158         */
159        getBreadcrumbs: function () {
160            return this.breadcrumbs;
161        },
162        /**
163         * Send event to Sentry via HTTP
164         * Updated to work with Visa Sentry using query parameters and envelope format
165         */
166        sendToSentry: function (eventData) {
167            if (!this.dsn) return false;
168            var dsnParts = this.parseDSN(this.dsn);
169            if (!dsnParts) {
170                console.error('Invalid Sentry DSN');
171                return false;
172            }
173            // Build envelope format (required by modern Sentry)
174            var timestamp = new Date().toISOString();
175            var envelope_header = JSON.stringify({
176                event_id: eventData.event_id,
177                sent_at: timestamp,
178                sdk: {
179                    name: 'sentry.javascript.react',
180                    version: '10.36.0'
181                }
182            });
183            var item_header = JSON.stringify({
184                type: 'event'
185            });
186            var item_payload = JSON.stringify(eventData);
187            // Combine into envelope format (newline-delimited JSON)
188            var envelopeBody = envelope_header + '\n' + item_header + '\n' + item_payload;
189            // Build endpoint with query parameters (Visa format)
190            var endpoint = `https://${dsnParts.host}${dsnParts.path}/api/${dsnParts.projectId}/envelope/`;
191            var queryParams = `?sentry_version=7&sentry_key=${encodeURIComponent(dsnParts.publicKey)}&sentry_client=${encodeURIComponent('sentry.javascript.react/10.36.0')}`;
192            var fullUrl = endpoint + queryParams;
193            // Use XMLHttpRequest for better browser compatibility
194            var xhr = new XMLHttpRequest();
195            xhr.open('POST', fullUrl, true);
196            xhr.setRequestHeader('Content-Type', 'text/plain;charset=UTF-8');
197            xhr.setRequestHeader('Accept', '*/*');
198            try {
199                xhr.send(envelopeBody);
200                return eventData.event_id;
201            } catch (e) {
202                console.error('Failed to send to Sentry:', e);
203                return false;
204            }
205        },
206        /**
207         * Parse DSN to extract components
208         * Updated to handle Visa DSN format with path prefix
209         */
210        parseDSN: function (dsn) {
211            // Match: https://PUBLIC_KEY@HOST/PATH/PROJECT_ID
212            // Example: https://[email protected]/sentry/5
213            var match = dsn.match(/^https?:\/\/([a-f0-9]+)@([^\/]+)(\/[^\/]+)*\/(\d+)\/?$/i);
214            if (!match) return null;
215            return {
216                publicKey: match[1],
217                host: match[2],
218                path: match[3] || '', // e.g., '/sentry' or empty string
219                projectId: match[4]
220            };
221        },
222        generateEventId: function () {
223            return 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'.replace(/x/g, function () {
224                return Math.floor(Math.random() * 16).toString(16);
225            });
226        },
227        getBrowserInfo: function () {
228            var ua = navigator.userAgent;
229            if (ua.indexOf('Chrome') > -1) return 'Chrome';
230            if (ua.indexOf('Firefox') > -1) return 'Firefox';
231            if (ua.indexOf('Safari') > -1) return 'Safari';
232            if (ua.indexOf('Edge') > -1) return 'Edge';
233            if (ua.indexOf('MSIE') > -1 || ua.indexOf('Trident') > -1) return 'IE';
234            return 'Unknown';
235        },
236        getPageType: function () {
237            var classList = document.body.classList;
238            if (classList.contains('woocommerce-checkout')) return 'checkout';
239            if (classList.contains('woocommerce-order-pay')) return 'order-pay';
240            if (classList.contains('woocommerce-account')) return 'my-account';
241            return 'unknown';
242        },
243        parseStackTrace: function (stack) {
244            var frames = [];
245            var lines = stack.split('\n');
246            for (var i = 0; i < lines.length; i++) {
247                var line = lines[i].trim();
248                if (!line) continue;
249                var match = line.match(/at\s+(.+?)\s+\((.+?):(\d+):(\d+)\)/) ||
250                    line.match(/(.+?)@(.+?):(\d+):(\d+)/);
251                if (match) {
252                    frames.push({
253                        filename: match[2],
254                        function: match[1],
255                        lineno: parseInt(match[3]),
256                        colno: parseInt(match[4])
257                    });
258                }
259            }
260            return frames.reverse();
261        }
262    };
263    // Global error handler
264    window.addEventListener('error', function (event) {
265        if (window.AnetSentryBrowser && window.AnetSentryBrowser.isAvailable()) {
266            window.AnetSentryBrowser.captureException(event.error || new Error(event.message), {
267                error_type: 'unhandled_error',
268                extra: {
269                    filename: event.filename,
270                    lineno: event.lineno,
271                    colno: event.colno
272                }
273            });
274        }
275    });
276    // Promise rejection handler
277    window.addEventListener('unhandledrejection', function (event) {
278        if (window.AnetSentryBrowser && window.AnetSentryBrowser.isAvailable()) {
279            window.AnetSentryBrowser.captureException(
280                event.reason || new Error('Unhandled Promise Rejection'),
281                {
282                    error_type: 'unhandled_rejection',
283                    extra: {
284                        promise: event.promise
285                    }
286                }
287            );
288        }
289    });
290    // Automatic breadcrumb tracking for user interactions
291    if (window.AnetSentryBrowser) {
292        // Track page load
293        window.addEventListener('load', function () {
294            if (window.AnetSentryBrowser.isAvailable()) {
295                window.AnetSentryBrowser.addBreadcrumb(
296                    'Page loaded: ' + window.location.pathname,
297                    'navigation',
298                    'info',
299                    {
300                        url: window.location.href,
301                        referrer: document.referrer
302                    }
303                );
304            }
305        });
306        // Track clicks on important elements
307        document.addEventListener('click', function (event) {
308            if (!window.AnetSentryBrowser || !window.AnetSentryBrowser.isAvailable()) return;
309            var target = event.target;
310            var elementInfo = target.tagName.toLowerCase();
311            // Add ID or class if available
312            if (target.id) elementInfo += '#' + target.id;
313            else if (target.className && typeof target.className === 'string') elementInfo += '.' + target.className.split(' ')[0];
314            // Track button clicks
315            if (target.tagName === 'BUTTON' || target.tagName === 'A' || target.type === 'submit') {
316                window.AnetSentryBrowser.addBreadcrumb(
317                    'Clicked: ' + elementInfo,
318                    'ui.click',
319                    'info',
320                    {
321                        element: elementInfo,
322                        text: target.textContent ? target.textContent.substring(0, 50) : ''
323                    }
324                );
325            }
326        });
327        // Track form submissions
328        document.addEventListener('submit', function (event) {
329            if (!window.AnetSentryBrowser || !window.AnetSentryBrowser.isAvailable()) return;
330            var form = event.target;
331            var formInfo = 'form';
332            if (form.id) formInfo += '#' + form.id;
333            else if (form.className) formInfo += '.' + form.className.split(' ')[0];
334            window.AnetSentryBrowser.addBreadcrumb(
335                'Form submitted: ' + formInfo,
336                'ui.submit',
337                'info',
338                {
339                    form: formInfo,
340                    action: form.action || 'none'
341                }
342            );
343        });
344        // Track AJAX/fetch requests
345        if (window.XMLHttpRequest) {
346            var originalOpen = XMLHttpRequest.prototype.open;
347            var originalSend = XMLHttpRequest.prototype.send;
348            XMLHttpRequest.prototype.open = function (method, url) {
349                this._sentryMethod = method;
350                this._sentryUrl = url;
351                return originalOpen.apply(this, arguments);
352            };
353            XMLHttpRequest.prototype.send = function () {
354                var xhr = this;
355                if (window.AnetSentryBrowser && window.AnetSentryBrowser.isAvailable()) {
356                    window.AnetSentryBrowser.addBreadcrumb(
357                        xhr._sentryMethod + ' request to ' + xhr._sentryUrl,
358                        'http',
359                        'info',
360                        {
361                            method: xhr._sentryMethod,
362                            url: xhr._sentryUrl
363                        }
364                    );
365                    // Track response
366                    xhr.addEventListener('load', function () {
367                        if (window.AnetSentryBrowser && window.AnetSentryBrowser.isAvailable()) {
368                            window.AnetSentryBrowser.addBreadcrumb(
369                                'HTTP ' + xhr.status + ' response',
370                                'http',
371                                xhr.status >= 400 ? 'error' : 'info',
372                                {
373                                    status: xhr.status,
374                                    url: xhr._sentryUrl
375                                }
376                            );
377                        }
378                    });
379                }
380                return originalSend.apply(this, arguments);
381            };
382        }
383    }
384    // Auto-initialize if configuration is available
385    if (typeof anetSentryConfig !== 'undefined' && anetSentryConfig.enabled) {
386        window.AnetSentryBrowser.init({
387            dsn: anetSentryConfig.dsn,
388            environment: anetSentryConfig.environment
389        });
390    }
391})();
392/**
393 *  Standard Sentry SDK  compatible with code expecting standard Sentry SDK.
394 */
395(function () {
396    'use strict';
397    // Create global Sentry object if it doesn't exist.
398    if (typeof window.Sentry === 'undefined') {
399        window.Sentry = {
400            init: function (config) {
401                if (window.AnetSentryBrowser) {
402                    window.AnetSentryBrowser.init(config);
403                }
404            },
405            captureException: function (error, context) {
406                if (window.AnetSentryBrowser) {
407                    return window.AnetSentryBrowser.captureException(error, context);
408                }
409                console.warn('Sentry: AnetSentryBrowser not available');
410                return null;
411            },
412            captureMessage: function (message, level) {
413                if (window.AnetSentryBrowser) {
414                    // Convert Sentry severity levels to our format
415                    var sentryLevel = level;
416                    if (typeof level === 'object' && level.level) {
417                        sentryLevel = level.level;
418                    }
419                    return window.AnetSentryBrowser.captureMessage(message, sentryLevel);
420                }
421                console.warn('Sentry: AnetSentryBrowser not available');
422                return null;
423            },
424            // Add breadcrumb.
425            addBreadcrumb: function (breadcrumb) {
426                if (window.AnetSentryBrowser) {
427                    var message = breadcrumb.message || '';
428                    var category = breadcrumb.category || 'default';
429                    var level = breadcrumb.level || 'info';
430                    var data = breadcrumb.data || null;
431                    window.AnetSentryBrowser.addBreadcrumb(message, category, level, data);
432                }
433            },
434            setUser: function (user) {
435                // Store user context for future events.
436                if (window.AnetSentryBrowser) {
437                    window.AnetSentryBrowser.userContext = user;
438                }
439            },
440            // Set tags.
441            setTag: function (key, value) {
442                if (window.AnetSentryBrowser) {
443                    if (!window.AnetSentryBrowser.customTags) {
444                        window.AnetSentryBrowser.customTags = {};
445                    }
446                    window.AnetSentryBrowser.customTags[key] = value;
447                }
448            },
449            // Set context.
450            setContext: function (name, context) {
451                if (window.AnetSentryBrowser) {
452                    if (!window.AnetSentryBrowser.customContexts) {
453                        window.AnetSentryBrowser.customContexts = {};
454                    }
455                    window.AnetSentryBrowser.customContexts[name] = context;
456                }
457            },
458            // Check if Sentry is available.
459            isInitialized: function () {
460                return window.AnetSentryBrowser && window.AnetSentryBrowser.isAvailable();
461            }
462        };
463    }
464})();

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.