PageSourceSearch

https://balance2.pravoved.ru/js/pravoved-analytics.js

js pravoved.ru collected 2026-10-01 11:27:39 UTC 9,925 bytes, 171 lines download raw bytes

1/* PORTAL-10730: single browser runtime, shared by modern and legacy clients. */
2(function (global) {
3    'use strict';
4    if (global.PravovedAnalytics) return;
5    var prefix = 'pravoved_analytics_';
6    var uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
7    var memory = {};
8    var events = ('page_view cta_impression cta_click form_open form_interaction_start question_submitted ' +
9        'registration_shown registration_started registration_completed tariffs_shown tariff_selected ' +
10        'checkout_shown payment_started chat_milestone').split(' ');
11    var chatStages = ('chat_started clarification_requested clarification_received city_requested city_received ' +
12        'phone_requested phone_received consultation_preview_shown tariff_offer_shown payment_link_shown ' +
13        'full_consultation_shown consultation_ready').split(' ');
14    var stringFields = ('page_type surface component placement element_id form_id form_step stage status reason_code ' +
15        'split_version assignment_id scenario tariff_code').split(' ');
16    var numberFields = ['question_id', 'lead_id', 'order_id', 'element_version', 'interaction_no'];
17    function uuid() {
18        try {
19            if (global.crypto && global.crypto.randomUUID) return global.crypto.randomUUID();
20            if (!global.crypto || !global.crypto.getRandomValues) return null;
21            var bytes = global.crypto.getRandomValues(new Uint8Array(16));
22            bytes[6] = (bytes[6] & 15) | 64;
23            bytes[8] = (bytes[8] & 63) | 128;
24            var hex = Array.prototype.map.call(bytes, function (b) { return ('0' + b.toString(16)).slice(-2); }).join('');
25            return hex.slice(0, 8) + '-' + hex.slice(8, 12) + '-' + hex.slice(12, 16) + '-' + hex.slice(16, 20) + '-' + hex.slice(20);
26        } catch (error) { return null; }
27    }
28    function read(kind, key) {
29        try { return global[kind].getItem(prefix + key); } catch (error) { return null; }
30    }
31    function write(kind, key, value) {
32        memory[key] = value;
33        try { global[kind].setItem(prefix + key, value); } catch (error) { /* volatile fallback */ }
34    }
35    function identity(key, kind) {
36        var value = read(kind, key) || memory[key];
37        if (!uuidPattern.test(value || '')) {
38            value = uuid();
39            if (value) write(kind, key, value);
40        }
41        memory[key] = value;
42        return value;
43    }
44    function context() {
45        return { visitor_id: identity('visitor_id', 'localStorage'), session_id: identity('session_id', 'sessionStorage'), journey_id: identity('journey_id', 'sessionStorage') };
46    }
47    function renewJourney() {
48        var id = uuid();
49        if (id) { write('sessionStorage', 'journey_id', id); write('sessionStorage', 'event_index', 0); write('sessionStorage', 'completed_journey_id', ''); }
50        return id;
51    }
52    function markJourneyComplete() {
53        var id = context().journey_id;
54        if (id) write('sessionStorage', 'completed_journey_id', id);
55    }
56    function beginNewQuestion() {
57        var current = context().journey_id;
58        var completed = read('sessionStorage', 'completed_journey_id') || memory.completed_journey_id;
59        return current && completed === current ? renewJourney() : current;
60    }
61    function dimension(value) {
62        return typeof value === 'number' && isFinite(value) && value >= 0 && value <= 65535 ? Math.round(value) : null;
63    }
64    function endpoint() {
65        var config = global.PravovedAnalyticsConfig || {};
66        return typeof config.endpoint === 'string' && /^https:\/\//.test(config.endpoint) ? config.endpoint : '';
67    }
68    function send(body, url, attempt) {
69        var retried = false;
70        function retry() {
71            if (!retried && attempt < 2) {
72                retried = true;
73                try { global.setTimeout(function () { send(body, url, attempt + 1); }, 500 * Math.pow(2, attempt)); } catch (error) { /* best effort */ }
74            }
75        }
76        try {
77            if (global.fetch) {
78                global.fetch(url, { method: 'POST', mode: 'cors', credentials: 'omit', keepalive: true, headers: { 'Content-Type': 'application/json' }, body: body })
79                    .then(function (response) { if (response.status >= 500 || response.status === 429) retry(); }, retry);
80            } else if (global.XMLHttpRequest) {
81                var xhr = new global.XMLHttpRequest();
82                xhr.open('POST', url, true);
83                xhr.timeout = 2000;
84                xhr.setRequestHeader('Content-Type', 'application/json');
85                xhr.onerror = retry;
86                xhr.ontimeout = retry;
87                xhr.onload = function () { if (xhr.status >= 500 || xhr.status === 429) retry(); };
88                xhr.send(body);
89            }
90        } catch (error) { retry(); }
91    }
92    function captureSnapshot() {
93        var ids = context();
94        var eventId = uuid();
95        if (!eventId || !ids.visitor_id || !ids.journey_id || !ids.session_id) return null;
96        var index = Number(read('sessionStorage', 'event_index') || memory.event_index || 0);
97        if (!isFinite(index) || index < 0 || index >= 4294967295) index = 0;
98        write('sessionStorage', 'event_index', index + 1);
99        return {
100            ids: ids,
101            eventId: eventId,
102            index: index,
103            time: new Date().toISOString(),
104            pageUrl: String((global.location || {}).pathname || '').slice(0, 2048)
105        };
106    }
107    function captureEvent() {
108        try {
109            if (!endpoint()) return null;
110            var snapshot = captureSnapshot();
111            if (!snapshot) return null;
112            var sent = false;
113            return function (event, fields) {
114                if (sent) return null;
115                var id = track(event, fields, snapshot);
116                if (id) sent = true;
117                return id;
118            };
119        } catch (error) { return null; }
120    }
121    function track(event, fields, captured) {
122        try {
123            fields = fields || {};
124            var url = endpoint();
125            if (!url || events.indexOf(event) === -1 || fields.split_id !== 'PORTAL-10682' || ['nosplit', 'a', 'b', 'c'].indexOf(fields.split_variant) === -1) return null;
126            if (event === 'chat_milestone' && chatStages.indexOf(fields.stage) === -1) return null;
127            var snapshot = captured || captureSnapshot();
128            if (!snapshot) return null;
129            var ids = snapshot.ids, eventId = snapshot.eventId, index = snapshot.index;
130            var nav = global.navigator || {}, screen = global.screen || {};
131            var payload = {
132                schema_version: 1, event_version: 1, event_id: eventId, dedup_key: eventId, event: event, source: 'frontend',
133                event_time: snapshot.time, event_index: index, visitor_id: ids.visitor_id, journey_id: ids.journey_id, session_id: ids.session_id,
134                split_id: 'PORTAL-10682', split_variant: fields.split_variant,
135                traffic_class: ['human', 'test', 'bot'].indexOf(fields.traffic_class) !== -1 ? fields.traffic_class : 'human',
136                page_url: snapshot.pageUrl,
137                user_agent: String(nav.userAgent || '').slice(0, 1024), language: String(nav.language || '').slice(0, 32),
138                timezone_offset_minutes: -new Date().getTimezoneOffset(), screen_width: dimension(screen.width), screen_height: dimension(screen.height),
139                viewport_width: dimension(global.innerWidth), viewport_height: dimension(global.innerHeight),
140                device_type: global.innerWidth < 768 ? 'mobile' : global.innerWidth < 1024 ? 'tablet' : 'desktop',
141                experiments_json: '{}', properties_json: '{}'
142            };
143            var configuredUser = Number((global.PravovedAnalyticsConfig || {}).userId);
144            if (configuredUser > 0 && Math.floor(configuredUser) === configuredUser) payload.user_id = configuredUser;
145            try {
146                if (global.URL && global.document && global.document.referrer) payload.referrer_url = new global.URL(global.document.referrer).pathname.slice(0, 2048);
147                if (global.URL && global.location && global.location.href) {
148                    var acquisition = new global.URL(global.location.href).searchParams;
149                    ['utm_source', 'utm_medium', 'utm_campaign', 'utm_content', 'utm_term'].forEach(function (key) {
150                        var value = acquisition.get(key);
151                        if (value && /^[A-Za-z0-9_.-]{1,128}$/.test(value)) payload[key] = value;
152                    });
153                }
154            } catch (error) { /* omit malformed acquisition data */ }
155            stringFields.forEach(function (key) {
156                var value = fields[key];
157                if (typeof value === 'string' && /^[a-zA-Z0-9_.:/-]{1,128}$/.test(value)) payload[key] = value;
158            });
159            numberFields.forEach(function (key) {
160                var value = fields[key];
161                if (typeof value === 'number' && isFinite(value) && Math.floor(value) === value && value > 0 && value <= 9007199254740991) payload[key] = value;
162            });
163            if (event.indexOf('cta_') === 0 && (!payload.component || !payload.placement || !payload.element_id)) return null;
164            payload.element_version = payload.element_version || 1;
165            // Serialize exactly once: callers and transport retries cannot mutate the event.
166            send(JSON.stringify(payload), url, 0);
167            return eventId;
168        } catch (error) { return null; }
169    }
170    global.PravovedAnalytics = Object.freeze({ track: function (event, fields) { return track(event, fields); }, captureEvent: captureEvent, getContext: context, renewJourney: renewJourney, markJourneyComplete: markJourneyComplete, beginNewQuestion: beginNewQuestion });
171})(window);

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.