PageSourceSearch

https://botseller.ru/api/v1/webform/pixel.js?customer_id=282

js botseller.ru collected 2026-10-06 06:59:38 UTC 9,825 bytes, 216 lines download raw bytes

1(function(){
2  var BS_CUSTOMER_ID = 282;
3  var BS_COOKIE_DOMAIN_OVERRIDE = null;
4  var BS_API_URL = "https://botseller.ru";
5
6  function autoDetectCookieDomain() {
7    // Для apex domain ('botseller.ai' напрямую) — не ставим domain
8    // (browser автоматически шарит cookie на поддомены если их нет).
9    var host = window.location.hostname;
10    if (!host || host === 'localhost' || /^\d{1,3}(\.\d{1,3}){3}$/.test(host)) {
11      return null; // localhost/IP — без явного domain
12    }
13    // Простая эвристика: последние 2 сегмента (botseller.ai / example.com).
14    // Для .co.uk и подобных eTLD клиент должен передать cookie_domain явно.
15    var parts = host.split('.');
16    if (parts.length < 2) return null;
17    return '.' + parts.slice(-2).join('.');
18  }
19
20  var BS_COOKIE_DOMAIN = BS_COOKIE_DOMAIN_OVERRIDE || autoDetectCookieDomain();
21
22  function getCookie(name) {
23    var m = document.cookie.match(new RegExp('(?:^|; )' + name.replace(/[-.+*]/g, '\\$&') + '=([^;]*)'));
24    return m ? decodeURIComponent(m[1]) : null;
25  }
26  function setCookie(name, value, days) {
27    var d = new Date();
28    d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
29    var domainPart = BS_COOKIE_DOMAIN ? (';domain=' + BS_COOKIE_DOMAIN) : '';
30    document.cookie = name + '=' + encodeURIComponent(value)
31      + ';expires=' + d.toUTCString()
32      + ';path=/;SameSite=Lax' + domainPart;
33  }
34  function uuidv4() {
35    if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
36      return crypto.randomUUID();
37    }
38    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
39      var r = Math.random() * 16 | 0;
40      var v = c === 'x' ? r : (r & 0x3 | 0x8);
41      return v.toString(16);
42    });
43  }
44  function getUrlParam(name) {
45    var m = window.location.search.match(new RegExp('[?&]' + name + '=([^&]+)'));
46    return m ? decodeURIComponent(m[1]) : null;
47  }
48
49  // Click id рекламных систем и поисковиков. Часто это ЕДИНСТВЕННЫЙ признак
50  // источника: переход из мобильного приложения Яндекса или из мессенджера
51  // приходит без referrer, но ysclid в адресе остаётся.
52  function collectClickIds() {
53    return {
54      ysclid: getUrlParam('ysclid'),
55      yclid:  getUrlParam('yclid'),
56      gclid:  getUrlParam('gclid'),
57      gbraid: getUrlParam('gbraid'),
58      wbraid: getUrlParam('wbraid'),
59      fbclid: getUrlParam('fbclid'),
60      ttclid: getUrlParam('ttclid')
61    };
62  }
63
64  // Воркспейс — это уже продукт, а не маркетинговый трафик. Первое касание
65  // должно указывать на публичную страницу, иначе источником регистрации
66  // становится сам кабинет.
67  function isWorkspaceHost() {
68    return /^workspace\./.test(window.location.hostname);
69  }
70
71  // 1. Anonymous visitor ID — sticky 365 дней, генерится один раз.
72  if (!getCookie('_bs_anon_id')) {
73    setCookie('_bs_anon_id', uuidv4(), 365);
74  }
75
76  // 2. First touch snapshot — sticky 90 дней. Записываем ТОЛЬКО если
77  //    cookie ещё нет: при втором визите с другим UTM сохраняем первое касание.
78  if (!getCookie('_bs_first_touch') && !isWorkspaceHost()) {
79    var firstTouch = {
80      utm: {
81        source:   getUrlParam('utm_source'),
82        medium:   getUrlParam('utm_medium'),
83        campaign: getUrlParam('utm_campaign'),
84        content:  getUrlParam('utm_content'),
85        term:     getUrlParam('utm_term')
86      },
87      click_ids:   collectClickIds(),
88      partner_ref: getUrlParam('ref'),
89      referrer:    document.referrer || null,
90      landing_url: window.location.href,
91      captured_at: new Date().toISOString()
92    };
93    try {
94      setCookie('_bs_first_touch', JSON.stringify(firstTouch), 90);
95    } catch (e) { /* JSON.stringify failed — silently skip */ }
96  }
97
98  // 3. Meta click id → _fbc (формат fb.1.{ts_ms}.{fbclid}, 90 дней).
99  //    Conversions API матчит по _fbc сильнее всего; Safari ITP режет
100  //    JS-cookie до 24ч, поэтому перезаписываем при КАЖДОМ заходе с fbclid
101  //    (новый клик = новая атрибуция), а снапшот атрибуции забирает значение
102  //    в момент заявки/регистрации.
103  var fbclid = getUrlParam('fbclid');
104  if (fbclid && fbclid.length <= 512) {
105    setCookie('_fbc', 'fb.1.' + Date.now() + '.' + fbclid, 90);
106  }
107
108  // Hook для расширений: window.__bsTracker.customerId доступен снаружи.
109  window.__bsTracker = window.__bsTracker || {};
110  window.__bsTracker.customerId = BS_CUSTOMER_ID;
111  window.__bsTracker.anonId = getCookie('_bs_anon_id');
112
113  // Phase 1D-2 + fix/pixel-spa-routing: page_view tracking с поддержкой
114  // SPA-роутинга (Next.js App Router, Nextra, любой React/Vue SPA).
115  //
116  // Современные сайты меняют URL через history.pushState БЕЗ полной
117  // перезагрузки. pixel.js в обычном script-tag загружается ОДИН раз
118  // и не знает про последующие client-side навигации. Без этого fix'а
119  // на /blog/article-X (после клика по Link на /blog) pageview НЕ
120  // отправлялся → таб «Маршрут» показывал только первую страницу.
121  //
122  // Решение — патчим history.pushState/replaceState и слушаем popstate.
123  // На каждое изменение URL вызываем sendPageView() с dedupe-проверкой.
124  if (BS_API_URL) {
125    var sentPageViews = {};  // map url->timestamp — dedupe в RAM
126
127    function sendPageView() {
128      var anonId = getCookie('_bs_anon_id');
129      if (!anonId) return;
130      try {
131        var currentUrl = window.location.href;
132        var nowMs = Date.now();
133
134        // Dedupe (fix/touchpoint-hardening): не шлём page_view если
135        // на ТОТ ЖЕ URL уже был fetch за последние 30 сек. Защита от
136        // F5-spam, hot-reload, browser back/forward на одну страницу.
137        // In-memory map быстрее sessionStorage и не падает в private mode.
138        if ((nowMs - (sentPageViews[currentUrl] || 0)) < 30000) {
139          return;
140        }
141        sentPageViews[currentUrl] = nowMs;
142
143        var firstTouchRaw = getCookie('_bs_first_touch');
144        var firstTouch = null;
145        if (firstTouchRaw) {
146          try { firstTouch = JSON.parse(firstTouchRaw); } catch (e) {}
147        }
148        var payload = {
149          customer_id: BS_CUSTOMER_ID,
150          contact_identifier: anonId,
151          contact_identifier_type: 'anon',
152          event_type: 'page_view',
153          attribution_data: {
154            utm: {
155              source:   getUrlParam('utm_source'),
156              medium:   getUrlParam('utm_medium'),
157              campaign: getUrlParam('utm_campaign'),
158              content:  getUrlParam('utm_content'),
159              term:     getUrlParam('utm_term')
160            },
161            click_ids:   collectClickIds(),
162            partner_ref: getUrlParam('ref'),
163            first_touch: firstTouch,
164            context: {
165              landing_url: currentUrl,
166              page_path:   window.location.pathname,
167              page_title:  document.title || null,
168              referrer:    document.referrer || null
169            }
170          }
171        };
172        fetch(BS_API_URL + '/api/v1/touchpoints/track', {
173          method: 'POST',
174          headers: { 'Content-Type': 'application/json' },
175          body: JSON.stringify(payload),
176          keepalive: true,
177          credentials: 'omit',
178          mode: 'cors'
179        }).catch(function() { /* fire-and-forget */ });
180      } catch (e) { /* skip on any error */ }
181    }
182
183    // SPA-навигация: даём React/Next.js 100ms на обновление document.title
184    // и DOM ПОСЛЕ pushState — иначе title в attribution будет от прошлой
185    // страницы (UI «Маршрут» покажет неправильный заголовок).
186    function delayedPageView() {
187      setTimeout(sendPageView, 100);
188    }
189
190    // 1. Initial pageview — сразу при загрузке script'а
191    sendPageView();
192
193    // 2. Patch history.pushState — Next.js Router / React Router / Vue Router
194    //    делают именно это при <Link> кликах. Сохраняем оригинал и
195    //    вызываем его после нашего хука.
196    var originalPushState = history.pushState;
197    history.pushState = function() {
198      var result = originalPushState.apply(this, arguments);
199      delayedPageView();
200      return result;
201    };
202
203    // 3. Patch history.replaceState — некоторые роутеры используют его
204    //    для query-параметров. Тоже отслеживаем.
205    var originalReplaceState = history.replaceState;
206    history.replaceState = function() {
207      var result = originalReplaceState.apply(this, arguments);
208      delayedPageView();
209      return result;
210    };
211
212    // 4. popstate event — browser back/forward кнопки.
213    //    pushState/replaceState его НЕ генерят, поэтому слушаем отдельно.
214    window.addEventListener('popstate', delayedPageView);
215  }
216})();

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.