1/*! 2 * Courstore Landing Analytics beacon (пеÑвоÑÑоÑоннÑÑ Ð°Ð½Ð°Ð»Ð¸Ñика лендинга). 3 * СамодоÑÑаÑоÑнÑй, без внеÑÐ½Ð¸Ñ Ð»Ð¸Ð±. ÐодклÑÑаÑÑ <script src=".../landing-beacon.js" defer>. 4 * 5 * ЧÑо делаеÑ: 6 * - visitor_id: поÑÑоÑннÑй UUID в localStorage (cs_vid), не ÑбÑаÑÑваеÑÑÑ; 7 * - session_id: UUID в localStorage (cs_sid) + 30-мин ÑаймаÑÑ Ð±ÐµÐ·Ð´ÐµÐ¹ÑÑвиÑ; 8 * - на загÑÑзке ÑÑÑаниÑÑ ÑлÑÑ pageview на /v1/landing/track (endpoint по Ñ Ð¾ÑÑÑ); 9 * - CourstoreAnalytics.visitorId()/sessionId() â геÑÑеÑÑ Ð´Ð»Ñ quiz-POST; 10 * - CourstoreAnalytics.lead(extra) â опÑионалÑное ÑобÑÑие конвеÑÑии. 11 * 12 * ÐÐÐÐÐÐÐÐ: beacon ÑабоÑÐ°ÐµÑ Ð½Ð° ÐСÐÐ¥ Ñ Ð¾ÑÑÐ°Ñ (в оÑлиÑие Ð¾Ñ GA/GTM-гаÑда). 13 * ТеÑÑ-Ñ Ð¾ÑÑÑ ÑÑ Ð¾Ð´ÑÑ Ð² devapi (ÑеÑÑ-ÐÐ), боевÑе â в api (бой-ÐÐ). ÐзолиÑованно, Ñак и надо. 14 */ 15(function () { 16 "use strict"; 17 18 var SESSION_TTL_MS = 30 * 60 * 1000; // 30 минÑÑ Ð±ÐµÐ·Ð´ÐµÐ¹ÑÑÐ²Ð¸Ñ 19 var VKEY = "cs_vid"; // persistent visitor_id (localStorage) 20 var SKEY = "cs_sid"; // session_id + last_activity (localStorage, CRS-3322) 21 var UKEY = "cs_utm"; // ÐÐСÐÐÐÐÐРизвеÑÑное каÑание (localStorage â пеÑеживаÑÑ Ð²ÐºÐ»Ð°Ð´ÐºÐ¸/ÑеÑÑиÑ, 22 // но пеÑезапиÑÑваеÑÑÑ Ð½Ð¾Ð²Ñм кликом â неÑмоÑÑÑ Ð½Ð° Ð¸Ð¼Ñ Ð¿ÐµÑеменной ниже, 23 // ÑÑо ÐÐ first-touch; Ñм. FKEY) 24 // CRS-3666: ÐÐÐ ÐÐРкаÑание Ñ Ñекламой â пиÑеÑÑÑ Ð ÐÐÐÐ ÐÐÐÐ Ð ÐÐ (или еÑли 25 // пÑедÑдÑÑÐ°Ñ Ð·Ð°Ð¿Ð¸ÑÑ Ð¿ÑоÑÑÑ Ð»Ð° â ÑÑаÑÑе 90 дней) и живÑÑ 90 дней. Ðез него 26 // fbclid ÑеÑÑлÑÑ: акÑÐ¸Ð²Ð½Ð°Ñ UKEY-меÑка вÑÑе пеÑезаÑиÑаеÑÑÑ ÐºÐ°Ð¶Ð´Ñм новÑм 27 // кликом, а квиз мог оÑкÑÑÑÑÑÑ ÑпÑÑÑÑ Ð´Ð½Ð¸/недели на «ÑиÑÑом» URL без 28 // единого паÑамеÑÑа ÑÐµÐºÐ»Ð°Ð¼Ñ â Ñогда единÑÑвеннÑм иÑÑоÑником аÑÑибÑÑии 29 // оÑÑаÑÑÑÑ Ð¸Ð¼ÐµÐ½Ð½Ð¾ ÑÑо Ñамое пеÑвое каÑание. 30 var FKEY = "cs_ft"; 31 var FIRST_TOUCH_MAX_AGE_MS = 90 * 24 * 60 * 60 * 1000; // 90 дней 32 var LKEY = "cs_lang"; // вÑбÑаннÑй ÑзÑк лендинга (localStorage + cookie), site-wide 33 var LANG_MAX_AGE = 60 * 60 * 24 * 365; // 1 год Ð´Ð»Ñ cookie ÑзÑка 34 35 // ÐлÑÑи ÑÑекинга: utm_* + клик-id ÑекламнÑÑ ÑиÑÑем. 36 var TRACK_KEYS = [ 37 "utm_source", "utm_medium", "utm_campaign", "utm_content", "utm_term", 38 "gclid", "fbclid", "yclid", "ttclid" 39 ]; 40 41 // API-Ñ Ð¾ÑÑ: testapp/test/localhost в hostname â devapi, инаÑе пÑод api. 42 function apiBase() { 43 var h = (location.hostname || "").toLowerCase(); 44 if (/testapp|(^|\.)test|localhost|127\.0\.0\.1/.test(h)) { 45 return "https://devapi.courstore.com"; 46 } 47 return "https://api.courstore.com"; 48 } 49 50 function uuid() { 51 try { 52 if (window.crypto && crypto.randomUUID) return crypto.randomUUID(); 53 } catch (e) {} 54 return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { 55 var r = (Math.random() * 16) | 0; 56 var v = c === "x" ? r : (r & 0x3) | 0x8; 57 return v.toString(16); 58 }); 59 } 60 61 // visitor_id â поÑÑоÑннÑй, живÑÑ Ð² localStorage. 62 function getVisitorId() { 63 try { 64 var v = localStorage.getItem(VKEY); 65 if (!v) { v = uuid(); localStorage.setItem(VKEY, v); } 66 return v; 67 } catch (e) { return uuid(); } 68 } 69 70 // session_id â в localStorage; пÑодлеваеÑÑÑ Ð¿Ñи акÑивноÑÑи, 71 // пеÑеÑоздаÑÑÑÑ Ð¿Ð¾Ñле 30 мин бездейÑÑÐ²Ð¸Ñ (по last_activity ts). 72 // 73 // CRS-3322: ÑанÑÑе лежал в sessionStorage, а он изолиÑован по вкладке. 74 // In-app бÑаÑÐ·ÐµÑ Facebook/Instagram оÑкÑÑÐ²Ð°ÐµÑ ÐºÐ°Ð¶Ð´Ñй пеÑÐµÑ Ð¾Ð´ новÑм webview â 75 // один и ÑÐ¾Ñ Ð¶Ðµ Ñеловек давал новÑÑ Â«ÑеÑÑиÑ» на каждÑй Ð·Ð°Ñ Ð¾Ð´ (замеÑено: 76 // 25 ÑеÑÑий одного visitor_id за 3 минÑÑÑ, 18% вÑÐµÑ ÑеÑÑий â Ñакие двойники). 77 // Ðаодно наÑинал ÑабоÑаÑÑ 30-минÑÑнÑй TTL: он ÑиÑал sessionStorage, коÑоÑого 78 // в новой вкладке пÑоÑÑо неÑ, поÑÑÐ¾Ð¼Ñ Ð½Ð¸ÐºÐ¾Ð³Ð´Ð° ниÑего не пÑодлевал. 79 function readSession() { 80 var raw = null; 81 try { raw = localStorage.getItem(SKEY); } catch (e) {}
82 // ÐбÑаÑÐ½Ð°Ñ ÑовмеÑÑимоÑÑÑ: ÑеÑÑиÑ, наÑаÑÐ°Ñ Ð¿ÑоÑлой веÑÑией beacon, Ð»ÐµÐ¶Ð¸Ñ 83 // в sessionStorage. ÐÐ¾Ð´Ñ Ð²Ð°ÑÑваем еÑ, ÑÑÐ¾Ð±Ñ Ð²Ð¸Ð·Ð¸Ñ Ð½Ðµ ÑазÑÑвалÑÑ Ð½Ð° вÑкаÑе. 84 if (!raw) { try { raw = sessionStorage.getItem(SKEY); } catch (e) {} } 85 try { var o = JSON.parse(raw || "null"); return (o && o.id) ? o : null; } catch (e) { return null; } 86 } 87 function getSessionId() { 88 var now = Date.now(), s = readSession(); 89 if (!s || !s.id || (now - s.t) > SESSION_TTL_MS) { 90 s = { id: uuid(), t: now }; 91 } else { 92 s.t = now; // пÑодлеваем last_activity 93 } 94 var body = JSON.stringify(s); 95 // localStorage Ð¼Ð¾Ð¶ÐµÑ Ð±ÑÑÑ Ð½ÐµÐ´Ð¾ÑÑÑпен (пÑиваÑнÑй Ñежим, оÑклÑÑÑннÑе кÑки) â 96 // Ñогда дегÑадиÑÑем до пÑежнего поведениÑ, а не ÑеÑÑем session_id вовÑе. 97 try { localStorage.setItem(SKEY, body); } 98 catch (e) { try { sessionStorage.setItem(SKEY, body); } catch (e2) {} } 99 return s.id; 100 } 101 102 function params() { 103 try { return (new URL(location.href)).searchParams; } catch (e) { return null; } 104 } 105 function qp(p, k) { return (p && p.get(k)) || null; } 106 107 // ââ ТÑекинг-меÑки (LAST-touch: пÑиоÑиÑÐµÑ â ÑекÑÑий URL) âââââââââââââââââ 108 // ÐладелеÑ: ÐСÐÐÐРбÑаÑÑ Ð°ÐºÑÑалÑнÑÑ UTM из ÑекÑÑего URL. ÐÑли в URL еÑÑÑ Ð¼ÐµÑки â 109 // они «иÑÑина» (last-touch): иÑполÑзÑÑÑÑÑ Ð² ÑÑÑÐ»ÐºÐ°Ñ /лиде РпеÑезапиÑÑваÑÑ Ñ ÑанилиÑе. 110 // ÐÑли в URL меÑок Ð½ÐµÑ â ÐРподÑÑавлÑÑÑ ÑÑаÑÑе залипÑие (не ÑаÑим stale в ÑÑÑлки/лид). 111 // Ð¥ÑанилиÑе оÑÑаÑÑÑÑ Ð»Ð¸ÑÑ ÐºÐ°Ðº «поÑледнее извеÑÑное каÑание» Ð´Ð»Ñ Ñела pageview. 112 113 // ÐÑоÑиÑаÑÑ ÑÑекинг-клÑÑи из ÑекÑÑего URL â обÑÐµÐºÑ (ÑолÑко непÑÑÑÑе). 114 function urlTracking() { 115 var out = {}, p = params(); 116 if (!p) return out; 117 for (var i = 0; i < TRACK_KEYS.length; i++) { 118 var v = p.get(TRACK_KEYS[i]); 119 if (v != null && v !== "") out[TRACK_KEYS[i]] = v; 120 } 121 return out; 122 } 123 124 function isEmptyObj(o) { 125 for (var k in o) if (o.hasOwnProperty(k)) return false; 126 return true; 127 } 128 129 // last-touch: еÑли в ÑекÑÑем URL еÑÑÑ Ð¼ÐµÑки â ÐÐÐ ÐÐÐÐÐСÐТЬ ими Ñ ÑанилиÑе 130 // (акÑÑалÑÐ½Ð°Ñ ÐºÐ°Ð¼Ð¿Ð°Ð½Ð¸Ñ Ð¿Ð¾Ð±ÐµÐ¶Ð´Ð°ÐµÑ ÑÑаÑÑÑ). ÐÑли меÑок в URL Ð½ÐµÑ â Ñ ÑанилиÑе не ÑÑогаем. 131 function captureLastTouch() { 132 try { 133 var cur = urlTracking(); 134 if (!isEmptyObj(cur)) { 135 localStorage.setItem(UKEY, JSON.stringify(cur)); 136 try { sessionStorage.removeItem(UKEY); } catch (e) {} // ÑиÑÑим ÑÑаÑÑй sessionStorage-клÑÑ 137 } 138 } catch (e) {} 139 } 140 141 // ââ ÐÐÐ ÐÐРкаÑание (CRS-3666) â 90 дней, пиÑеÑÑÑ Ð¾Ð´Ð¸Ð½ Ñаз âââââââââââââââ 142 // fb.1.<ts_ms>.<fbclid> â ÑÐ¾Ñ Ð¶Ðµ ÑоÑÐ¼Ð°Ñ cookie _fbc, коÑоÑÑй ждÑÑ Meta CAPI 143 // (ÑеÑÐ²ÐµÑ ÑÑÑÐ¾Ð¸Ñ ÐµÐ³Ð¾ Ñак же в MetaCapiPayload.fbcFromClickId). 144 function buildFbc(fbclid, tsMs) { 145 return fbclid ? ("fb.1." + tsMs + "." + fbclid) : null; 146 } 147 function readFirstTouch() { 148 try { 149 var raw = localStorage.getItem(FKEY); 150 if (!raw) return null; 151 var o = JSON.parse(raw); 152 if (!o || !o.ts) return null; 153 if ((Date.now() - o.ts) > FIRST_TOUCH_MAX_AGE_MS) return null; // пÑоÑÑÑ Ð»Ð¾ 154 return o; 155 } catch (e) { return null; } 156 } 157 // ÐиÑем ТÐÐЬÐРеÑли еÑÑ Ð½ÐµÑ Ð²Ð°Ð»Ð¸Ð´Ð½Ð¾Ð¹ запиÑи (Ð½ÐµÑ Ð¸Ð»Ð¸ пÑоÑÑÑ Ð»Ð°) Рв ÑекÑÑем 158 // URL ÑеалÑно еÑÑÑ Ñ Ð¾ÑÑ Ð¾Ð´Ð½Ð° меÑка â инаÑе пеÑвÑм «каÑанием» ÑÑÐ°Ð½ÐµÑ ÑлÑÑайнÑй
159 // пÑÑмой Ð·Ð°Ñ Ð¾Ð´ без ÑекламÑ, и наÑÑоÑÑий клик по обÑÑÐ²Ð»ÐµÐ½Ð¸Ñ ÑÑÑ Ð¿ÑÑÑÑÑ 160 // заглÑÑÐºÑ Ñже не пеÑезапиÑÐµÑ (ÑÑо и бÑло Ð±Ñ Ð½ÐµÐ¿ÑавилÑно â 90 дней иÑÑина одна). 161 function captureFirstTouch() { 162 try { 163 if (readFirstTouch()) return; 164 var cur = urlTracking(); 165 if (isEmptyObj(cur)) return; 166 var now = Date.now(); 167 var rec = { 168 ts: now, 169 utm_source: cur.utm_source || null, 170 utm_medium: cur.utm_medium || null, 171 utm_campaign: cur.utm_campaign || null, 172 utm_content: cur.utm_content || null, 173 utm_term: cur.utm_term || null, 174 fbclid: cur.fbclid || null, 175 fbc: buildFbc(cur.fbclid, now) 176 }; 177 localStorage.setItem(FKEY, JSON.stringify(rec)); 178 } catch (e) {} 179 } 180 // ÐÑблиÑнÑй вид (Ð´Ð»Ñ beacon-Ñела и quiz-POST): first_touch_at как ISO-8601. 181 function firstTouchPublic() { 182 var f = readFirstTouch(); 183 if (!f) return {}; 184 return { 185 utm_source: f.utm_source || null, 186 utm_medium: f.utm_medium || null, 187 utm_campaign: f.utm_campaign || null, 188 utm_content: f.utm_content || null, 189 utm_term: f.utm_term || null, 190 fbclid: f.fbclid || null, 191 fbc: f.fbc || null, 192 first_touch_at: f.ts ? new Date(f.ts).toISOString() : null 193 }; 194 } 195 196 // Ð¡Ð¾Ñ ÑанÑннÑе меÑки (обÑекÑ; {} еÑли ниÑего неÑ). Fallback на ÑÑаÑÑй sessionStorage-клÑÑ. 197 function storedParams() { 198 try { 199 var s = localStorage.getItem(UKEY) || sessionStorage.getItem(UKEY); 200 if (s) { var o = JSON.parse(s); if (o && typeof o === "object") return o; } 201 } catch (e) {} 202 return {}; 203 } 204 205 // ÐÐТУÐÐЬÐЫРмеÑки Ð´Ð»Ñ ÑÑÑлок/лида: ТÐÐЬÐРиз ÑекÑÑего URL (last-touch). 206 // Ðа «ÑиÑÑом» URL (без utm_) â {} , ÑÑаÑÑе залипÑие ÐРподÑÑавлÑем. 207 function activeTracking() { 208 return urlTracking(); 209 } 210 211 // ÐÐ»Ñ Ñела pageview: акÑÑалÑнÑе из URL, инаÑе поÑледнее извеÑÑное каÑание (Ñ ÑанилиÑе). 212 function trackingForBeacon() { 213 var cur = urlTracking(); 214 return isEmptyObj(cur) ? storedParams() : cur; 215 } 216 217 // ââ ЯзÑк лендинга (site-wide пеÑÑиÑÑенÑноÑÑÑ) âââââââââââââââââââââââââââ 218 // Ð¥Ñанение: localStorage (cs_lang) + cookie (cs_lang) â ÑÑÐ¾Ð±Ñ ÑиÑалоÑÑ Ð¸ на 219 // дÑÑÐ³Ð¸Ñ ÑÑÑаниÑÐ°Ñ , и пÑи SSR. ÐÑиоÑиÑÐµÑ ÑÑениÑ: ?lang= â cookie â localStorage. 220 function readCookie(name) { 221 try { 222 var m = document.cookie.match(new RegExp("(?:^|; )" + name + "=([^;]*)")); 223 return m ? decodeURIComponent(m[1]) : null; 224 } catch (e) { return null; } 225 } 226 function writeCookie(name, val) { 227 try { 228 document.cookie = name + "=" + encodeURIComponent(val) + 229 "; path=/; max-age=" + LANG_MAX_AGE + "; SameSite=Lax"; 230 } catch (e) {} 231 } 232 function normLang(l) { 233 l = (l || "").toLowerCase().trim(); 234 return /^(ru|kk|en|uz)$/.test(l) ? l : null; 235 } 236 // Ð¡Ð¾Ñ ÑанÑннÑй ÑзÑк (без ?lang=): cookie â localStorage. 237 function savedLang() { 238 var l = normLang(readCookie(LKEY)); 239 if (l) return l; 240 try { l = normLang(localStorage.getItem(LKEY)); } catch (e) {} 241 return l || null; 242 } 243 // ЯзÑк из ÐУТР(пÑеÑикÑÐ½Ð°Ñ ÑÑ ÐµÐ¼Ð°): маÑÐºÐµÑ __CS_PATH_LANG, инаÑе пеÑвÑй ÑÐµÐ³Ð¼ÐµÐ½Ñ 244 // пÑÑи /<lang>/⦠. ÐÑо главнÑй иÑÑоÑник в полноÑÑÑÑ-пÑеÑикÑной ÑÑ ÐµÐ¼Ðµ. 245 function pathLang() { 246 try { 247 var pl = normLang(window.__CS_PATH_LANG); 248 if (pl) return pl; 249 } catch (e) {} 250 var m = (location.pathname || "").match(/\/(ru|kk|en|uz)(?:\/|$)/i); 251 return m ? normLang(m[1]) : null; 252 } 253 // ТекÑÑий ÑзÑк ÑÑÑаниÑÑ: ÐУТЬ /<lang>/ â ?lang= (back-compat) â ÑÐ¾Ñ ÑанÑннÑй. 254 // РпÑеÑикÑной ÑÑ ÐµÐ¼Ðµ ÑзÑк неÑÑÑ Ñам пÑÑÑ, поÑÑÐ¾Ð¼Ñ Ð¾Ð½ в пÑиоÑиÑеÑе. 255 function currentLang() { 256 var l = pathLang(); 257 if (l) return l; 258 var p = params(); 259 l = p ? normLang(p.get("lang")) : null;
260 return l || savedLang(); 261 } 262 // Ð¡Ð¾Ñ ÑаниÑÑ Ð²ÑбÑаннÑй ÑзÑк (localStorage + cookie). 263 function persistLang(l) { 264 l = normLang(l); if (!l) return; 265 try { localStorage.setItem(LKEY, l); } catch (e) {} 266 writeCookie(LKEY, l); 267 } 268 269 // ÐÑблиÑнÑй API ÑзÑка (иÑполÑзÑеÑÑÑ ÑÑÑаниÑами лендинга Ð´Ð»Ñ ÑÑÐµÐ½Ð¸Ñ Ð½Ð° load). 270 window.CourstoreLang = { 271 get: currentLang, // ÐУТЬ /<lang>/ â ?lang= â cookie â localStorage â null 272 path: pathLang, // ÑзÑк из пÑÑи /<lang>/ (или __CS_PATH_LANG) â null 273 saved: savedLang, // cookie â localStorage â null (без ÑÑÑÑа URL/пÑÑи) 274 save: persistLang, // запиÑаÑÑ Ð²ÑбÑаннÑй ÑзÑк в cookie+localStorage 275 norm: normLang 276 }; 277 278 // ââ ÐÑеÑикÑÐ½Ð°Ñ Ð¿ÐµÑелинковка внÑÑÑÐµÐ½Ð½Ð¸Ñ ÑÑÑлок âââââââââââââââââââââââââââ 279 // ÐолноÑÑÑÑ-пÑеÑикÑÐ½Ð°Ñ i18n: внÑÑÑеннÑÑ nav/footer/CTA-ÑÑÑлка должна неÑÑи 280 // ТÐÐУЩÐÐ ÑзÑковой пÑеÑÐ¸ÐºÑ Ð¿ÑÑи (/<lang>/â¦), а не голÑй пÑÑÑ Ð¸ не ?lang=. 281 // langifyPath(pathname, lang): веÑнÑÑÑ pathname Ñ Ð¿ÐµÑвÑм ÑегменÑом = /<lang>/. 282 // УÑиÑÑÐ²Ð°ÐµÑ Â«Ð±Ð°Ð·Ñ» Ð´ÐµÐ¿Ð»Ð¾Ñ (â¦/landing/, â¦/pro/): ÑзÑк вÑÑавлÑеÑÑÑ ÐÐСÐРбазÑ, 283 // опÑеделÑемой по ÑекÑÑÐµÐ¼Ñ Ð¿ÑÑи ÑÑÑаниÑÑ (ÑодиÑÐµÐ»Ñ ÑзÑкового ÑегменÑа). 284 function deployBase() { 285 // Ðаза = ÑекÑÑий пÑÑÑ Ð´Ð¾ ÑзÑкового ÑегменÑа. Ðа /landing/kk/prices/ база = /landing/. 286 // ÐÑли Ð¼Ñ Ð½Ð° голом пÑÑи (без /<lang>/) â база = каÑалог ÑекÑÑей ÑÑÑаниÑÑ. 287 var pn = location.pathname || "/"; 288 var m = pn.match(/^(.*?\/)(?:ru|kk|en|uz)(?:\/|$)/i); 289 if (m) return m[1]; 290 // голÑй пÑÑÑ: до поÑледнего '/' 291 return pn.replace(/[^\/]*$/, ""); 292 } 293 function langifyPath(pathname, lang, base) { 294 // ÐбÑолÑÑнÑй pathname â ÑÐ¾Ñ Ð¶Ðµ пÑÑÑ Ð¿Ð¾Ð´ /<lang>/. Снимаем ÑÑÑеÑÑвÑÑÑий ÑзÑковой 295 // ÑÐµÐ³Ð¼ÐµÐ½Ñ (еÑли еÑÑÑ), заÑем вÑÑавлÑем нÑжнÑй ÑÑÐ°Ð·Ñ Ð¿Ð¾Ñле Ð±Ð°Ð·Ñ Ð´ÐµÐ¿Ð»Ð¾Ñ. 296 var rest = pathname; 297 // оÑÑезаÑÑ Ð±Ð°Ð·Ñ (еÑли пÑÑÑ Ð½Ð°ÑинаеÑÑÑ Ñ Ð½ÐµÑ) 298 if (base && pathname.toLowerCase().indexOf(base.toLowerCase()) === 0) { 299 rest = pathname.slice(base.length); 300 } else { 301 base = "/"; 302 rest = pathname.replace(/^\//, ""); 303 } 304 // ÑнÑÑÑ Ð²ÐµÐ´ÑÑий ÑзÑковой ÑÐµÐ³Ð¼ÐµÐ½Ñ Ð¸Ð· оÑÑаÑка 305 rest = rest.replace(/^(ru|kk|en|uz)(\/|$)/i, function (m, l, sl) { return sl === "/" ? "" : ""; }); 306 return base + lang + "/" + rest; 307 } 308 // ÐеÑелинковаÑÑ ÐСРвнÑÑÑенние ÑÑÑлки под ÑзÑк `lang` + акÑÑалÑнÑÑ UTM. 309 // ÐдемпоÑенÑно: absolute-path Ñ ÑеÑÑ Ñ Ñже веÑнÑм пÑеÑикÑом оÑÑаÑÑÑÑ ÐºÐ°Ðº еÑÑÑ. 310 var NO_LANG_PATHS = /(^|\/)mobile-app(\/|$)/i; 311 function relinkAll(lang) { 312 lang = normLang(lang) || pathLang(); if (!lang) return; 313 var host = (location.hostname || "").toLowerCase(); 314 var base = deployBase(); 315 // ÐкÑÑалÑнÑе (last-touch) UTM-паÑамеÑÑÑ Ð¸Ð· ÑекÑÑего URL â в query. 316 var utm = activeTracking(), utmParts = []; 317 for (var uk in utm) if (utm.hasOwnProperty(uk)) { 318 utmParts.push(encodeURIComponent(uk) + "=" + encodeURIComponent(utm[uk])); 319 } 320 var links = document.querySelectorAll("a[href]"); 321 for (var i = 0; i < links.length; i++) { 322 var a = links[i], href = a.getAttribute("href"); 323 if (!href) continue; 324 var low = href.trim().toLowerCase(); 325 if (low.charAt(0) === "#") continue; // in-page ÑкоÑÑ 326 if (/^(mailto:|tel:|javascript:)/.test(low)) continue; // не-навигаÑÐ¸Ñ 327 var abs;
328 try { abs = new URL(href, location.href); } catch (e) { continue; } 329 if (abs.hostname.toLowerCase() !== host) continue; // внеÑний домен â не ÑÑогаем 330 // ТолÑко ÑÑÑаниÑÑ Ð¿Ð¾Ð´ базой Ð´ÐµÐ¿Ð»Ð¾Ñ (nav/footer/CTA лендинга). РеÑÑÑÑÑ 331 // (shared/*.css, /og/*, favicon, .png/.js/.css) â не ÑзÑковÑе, пÑопÑÑкаем. 332 if (/\.(css|js|png|jpg|jpeg|svg|webp|ico|gif|woff2?|mp4|webm|pdf)$/i.test(abs.pathname)) continue; 333 // Standalone-лендинги живÑÑ Ð²Ð½Ðµ ÑзÑковой ÑÑ ÐµÐ¼Ñ (/mobile-app/ не Ð¸Ð¼ÐµÐµÑ /ru/-веÑÑии). 334 if (NO_LANG_PATHS.test(abs.pathname)) continue; 335 // ÐÑÑавлÑем ÑзÑковой пÑеÑÐ¸ÐºÑ Ð² пÑÑÑ. 336 var newPath = langifyPath(abs.pathname, lang, base); 337 // ЧиÑÑим legacy ?lang= из query; наноÑим акÑÑалÑнÑÑ UTM (пеÑезапиÑÑваем ÑÑаÑÑÑ). 338 abs.searchParams.delete("lang"); 339 for (var t = 0; t < TRACK_KEYS.length; t++) abs.searchParams.delete(TRACK_KEYS[t]); 340 var qs = abs.searchParams.toString(); 341 if (utmParts.length) qs = (qs ? qs + "&" : "") + utmParts.join("&"); 342 a.setAttribute("href", newPath + (qs ? "?" + qs : "") + (abs.hash || "")); 343 } 344 } 345 346 // ÐеÑÑиÑÑ ÑзÑка пÑи лÑбом пеÑеклÑÑении: делегиÑованнÑй клик по [data-lang]. 347 // (СÑÑаниÑнÑй switcher Ñам ведÑÑ Ð½Ð° /<lang>/-пÑÑÑ ÑеÑез csLangHref; здеÑÑ ÑолÑко 348 // ÑÐ¾Ñ ÑанÑем вÑÐ±Ð¾Ñ Ð¸ на вÑÑкий ÑлÑÑай пеÑелинковÑваем видимÑе ÑÑÑлки под новÑй ÑзÑк.) 349 document.addEventListener("click", function (e) { 350 var a = e.target && e.target.closest ? e.target.closest("[data-lang]") : null; 351 if (a) { 352 var l = normLang(a.getAttribute("data-lang")); 353 if (l) { persistLang(l); relinkAll(l); } 354 } 355 }, true); 356 357 // СÑÑÐ°Ñ Ð¾Ð²ÐºÐ°: еÑли ÑÑÑаниÑа менÑÐµÑ <html lang> Ñвоим applyLang â ÑÐ¾Ñ ÑаниÑÑ ÐµÐ³Ð¾. 358 try { 359 var htmlEl = document.documentElement; 360 new MutationObserver(function () { 361 var l = normLang(htmlEl.getAttribute("lang")); 362 if (l && l !== savedLang()) persistLang(l); 363 }).observe(htmlEl, { attributes: true, attributeFilter: ["lang"] }); 364 } catch (e) {} 365 366 // ÐекоÑаÑÐ¸Ñ Ð²Ð½ÑÑÑÐµÐ½Ð½Ð¸Ñ ÑÑÑлок: ÑзÑковой ÐÐ ÐФÐÐС пÑÑи (/<lang>/â¦) + ÐÐТУÐÐЬÐÐЯ UTM. 367 // ÐаменÑÐµÑ ÑÑаÑÑÑ query-based (?lang=) ÑÑ ÐµÐ¼Ñ: ÑзÑк ÑепеÑÑ Ð½ÐµÑÑÑ Ð¿ÑÑÑ. 368 function decorateLinks() { 369 var lang = currentLang(); // ÑзÑк из пÑÑи (в пÑиоÑиÑеÑе) â ⦠370 if (!lang) return; // Ð½ÐµÑ ÑзÑкового конÑекÑÑа â ниÑего не пÑеÑикÑим 371 relinkAll(lang); 372 } 373 374 // lang: ?lang= â ÑÐ¾Ñ ÑанÑннÑй (cookie/localStorage) â <html lang> 375 function getLang() { 376 var l = currentLang(); 377 if (!l) l = document.documentElement.getAttribute("lang") || ""; 378 return (l || "").toLowerCase().slice(0, 16); 379 } 380 381 function deviceHint() { 382 var w = window.innerWidth || (screen && screen.width) || 0; 383 if (w > 0 && w < 768) return "mobile"; 384 if (w >= 768 && w < 1024) return "tablet"; 385 return "desktop"; 386 } 387 388 function send(eventType, extra) { 389 // Тело pageview неÑÑÑ ÐÐÐ ÐÐÐÐСÐТÐÐЬÐЫРмеÑки (stored), ÑолбÑк â ÑекÑÑий URL. 390 var tp = trackingForBeacon(); 391 var payload = { 392 event_type: eventType, 393 visitor_id: getVisitorId(), 394 session_id: getSessionId(), 395 page_path: location.pathname, 396 page_url: location.href, 397 referrer: document.referrer || null, 398 utm_source: tp.utm_source || null, 399 utm_medium: tp.utm_medium || null, 400 utm_campaign: tp.utm_campaign || null, 401 utm_content: tp.utm_content || null, 402 utm_term: tp.utm_term || null, 403 lang: getLang(), 404 screen_w: window.innerWidth || 0, 405 device_hint: deviceHint() 406 }; 407 // CRS-3666: пеÑвое каÑание Ñ Ñекламой â ÑвнÑми полÑми, ÑÑÐ¾Ð±Ñ Ð¾Ð
407½Ð¾ пиÑалоÑÑ 408 // в wp_landing_sessions/wp_landing_events незавиÑимо Ð¾Ñ Ñого, неÑÑÑ Ð»Ð¸ 409 // ТÐÐУЩÐÐ URL fbclid (обÑÑно Ñже не неÑÑÑ â Ñеклама бÑла кликнÑÑа ÑанÑÑе). 410 var ft = firstTouchPublic(); 411 if (ft.fbclid) payload.fbclid = ft.fbclid; 412 if (ft.fbc) payload.fbc = ft.fbc; 413 if (ft.first_touch_at) payload.first_touch_at = ft.first_touch_at; 414 if (extra) for (var k in extra) if (extra.hasOwnProperty(k)) payload[k] = extra[k]; 415 416 var url = apiBase() + "/v1/landing/track"; 417 var body = JSON.stringify(payload); 418 // ÐналиÑика никогда не ÑонÑÐµÑ ÑÑÑаниÑÑ â лÑбÑе оÑибки глÑÑим. 419 try { 420 if (navigator.sendBeacon) { 421 navigator.sendBeacon(url, new Blob([body], { type: "text/plain;charset=UTF-8" })); 422 return; 423 } 424 } catch (e) {} 425 try { 426 fetch(url, { 427 method: "POST", 428 headers: { "Content-Type": "text/plain;charset=UTF-8" }, 429 body: body, 430 keepalive: true, 431 mode: "cors" 432 }).catch(function () {}); 433 } catch (e) {} 434 } 435 436 // ÐÑблиÑнÑй API: геÑÑеÑÑ id'Ñников Ð´Ð»Ñ quiz-POST + опÑионалÑное lead-ÑобÑÑие. 437 window.CourstoreAnalytics = { 438 visitorId: getVisitorId, 439 sessionId: getSessionId, 440 trackingParams: activeTracking, // ÐÐТУÐÐЬÐЫРмеÑки из ÑекÑÑего URL (last-touch); {} на ÑиÑÑом URL 441 // CRS-3666: ÐÐÐ ÐÐРкаÑание Ñ Ñекламой (90 дней, пиÑеÑÑÑ Ð¾Ð´Ð¸Ð½ Ñаз) â 442 // {utm_source, utm_medium, utm_campaign, utm_content, utm_term, fbclid, fbc, 443 // first_touch_at} или {} еÑли еÑÑ Ð½Ðµ заÑикÑиÑовано/пÑоÑÑÑ Ð»Ð¾. ÐÑÑоÑник 444 // аÑÑибÑÑии Ð´Ð»Ñ ÑоÑÐ¼Ñ ÐºÐ²Ð¸Ð·Ð°, не завиÑÐ¸Ñ Ð¾Ñ Ñого, ÑÑо неÑÑÑ ÑекÑÑий URL. 445 firstTouch: firstTouchPublic, 446 lead: function (extra) { send("lead", extra || {}); }, 447 // УнивеÑÑалÑнÑй ÑÑек пÑоизволÑного ÑобÑÑÐ¸Ñ (promo_impression/close/click и пÑ.). 448 // ÐеÑÐ¸Ñ Ñем же beacon-каналом на /v1/landing/track Ñ event_type + доп.полÑ. 449 track: function (eventType, extra) { send(eventType, extra || {}); } 450 }; 451 452 // ÐоÑÑдок: ÑнаÑала last-touch capture, заÑем first-touch (еÑли еÑÑ Ð½Ðµ 453 // заÑикÑиÑовано) â оба ÑÐ¸Ð½Ñ Ñонно, до pageview и декоÑаÑии; заÑем â по 454 // гоÑовноÑÑи DOM â декоÑаÑÐ¸Ñ ÑÑÑлок и pageview. 455 captureLastTouch(); 456 captureFirstTouch(); 457 458 function onReady() { 459 // Ðа SEO-пÑеÑендеÑе (__CS_NO_REDIRECT) ÐРпÑеÑикÑим ÑÑÑлки: ÑÑÑаниÑа гÑÑзиÑÑÑ 460 // по голомÑ/виÑÑÑалÑÐ½Ð¾Ð¼Ñ URL, пÑеÑÐ¸ÐºÑ Ð¸Ð· location бÑл Ð±Ñ Ð½ÐµÐ²ÐµÑнÑм â оÑÑавлÑем 461 // иÑÑ Ð¾Ð´Ð½Ñе оÑноÑиÑелÑнÑе ÑÑÑлки, prerender Ñам пÑÐ°Ð²Ð¸Ñ Ð³Ð»ÑбинÑ, а на ÑеалÑной 462 // загÑÑзке beacon пеÑелинкÑÐµÑ Ð¿Ð¾ ÑакÑиÑеÑÐºÐ¾Ð¼Ñ /<lang>/-пÑÑи. 463 var skip = false; 464 try { skip = !!window.__CS_NO_REDIRECT; } catch (e) {} 465 if (!skip) decorateLinks(); // ÑÑÑлки полÑÑаÑÑ ÑзÑк-пÑеÑÐ¸ÐºÑ + акÑÑалÑнÑÑ UTM 466 send("pageview"); 467 } 468 if (document.readyState === "complete" || document.readyState === "interactive") { 469 setTimeout(onReady, 0); 470 } else { 471 window.addEventListener("DOMContentLoaded", onReady); 472 } 473})();
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.