1// ÐпÑеделÑем ÑекÑÑий Ñ Ð¾ÑÑ 2const currentHost = window.location.host; 3 4// ФÑнкÑÐ¸Ñ Ð´Ð»Ñ Ð¿Ð¾Ð»ÑÑÐµÐ½Ð¸Ñ Ð´Ð¾Ð¼ÐµÐ½Ð° вÑоÑого ÑÑÐ¾Ð²Ð½Ñ 5function getSecondLevelDomain() { 6 const hostname = window.location.hostname; 7 8 // ÐбÑабоÑка локалÑнÑÑ Ñ Ð¾ÑÑов и IP-адÑеÑов 9 if (hostname === 'localhost' || /^\d+\.\d+\.\d+\.\d+$/.test(hostname)) { 10 return hostname; 11 } 12 13 const parts = hostname.split('.'); 14 15 // ÐÑли ÑÑо домен вÑоÑого ÑÑÐ¾Ð²Ð½Ñ (напÑимеÑ, example.com) 16 if (parts.length === 2) { 17 return hostname; 18 } 19 20 // ÐÐ»Ñ Ð´Ð¾Ð¼ÐµÐ½Ð¾Ð² ÑÑеÑÑего ÑÑÐ¾Ð²Ð½Ñ Ð¸ вÑÑе (напÑимеÑ, sub.example.com) 21 // ÐеÑем поÑледние 2 ÑаÑÑи 22 if (parts.length > 2) { 23 return parts.slice(-2).join('.'); 24 } 25 26 return hostname; 27} 28 29// ÐÐ¼Ñ ÐºÑк (обновленнÑе названиÑ) 30const COOKIE_REFER = 'qform_segment_refer'; 31const COOKIE_ENTRY_POINT = 'qform_segment_entry_point'; 32const COOKIE_SEGMENT = 'qform_segment_type'; 33 34// ÐолÑÑаем домен Ð´Ð»Ñ ÐºÑк 35const COOKIE_DOMAIN = getSecondLevelDomain(); 36 37// URL Ð´Ð»Ñ Viki API 38const VIKI_API_URL = 'https://viki.seotask.ru/qform/segment'; 39 40// УÑилиÑÑ Ð´Ð»Ñ ÑабоÑÑ Ñ ÐºÑками 41const CookieUtils = { 42 // ÐолÑÑиÑÑ ÐºÑÐºÑ Ð¿Ð¾ имени 43 get(name) { 44 const matches = document.cookie.match(new RegExp( 45 "(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)" 46 )); 47 return matches ? decodeURIComponent(matches[1]) : undefined; 48 }, 49 50 // УÑÑановиÑÑ ÐºÑÐºÑ 51 set(name, value, options = {}) { 52 const defaultOptions = { 53 path: '/', 54 maxAge: 30 * 24 * 60 * 60, // 30 дней 55 sameSite: 'Lax', 56 domain: COOKIE_DOMAIN 57 }; 58 59 const opts = { ...defaultOptions, ...options }; 60 61 let cookieString = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`; 62 63 if (opts.maxAge) { 64 cookieString += `; max-age=${opts.maxAge}`; 65 } 66 67 if (opts.path) { 68 cookieString += `; path=${opts.path}`; 69 } 70 71 // ÐобавлÑем домен ÑолÑко еÑли ÑÑо не localhost или IP-адÑÐµÑ 72 if (opts.domain && opts.domain !== 'localhost' && !opts.domain.startsWith('127.0.0.1') && !opts.domain.startsWith('192.168.')) { 73 cookieString += `; domain=.${opts.domain}`; 74 } 75 76 if (opts.secure) { 77 cookieString += '; secure'; 78 } 79 80 if (opts.sameSite) { 81 cookieString += `; samesite=${opts.sameSite}`; 82 } 83 84 document.cookie = cookieString; 85 }, 86 87 // УдалиÑÑ ÐºÑÐºÑ 88 remove(name) { 89 this.set(name, '', { maxAge: -1 }); 90 }, 91 92 // ÐÑовеÑиÑÑ, ÑÑÑеÑÑвÑÐµÑ Ð»Ð¸ кÑка 93 exists(name) { 94 return this.get(name) !== undefined; 95 } 96}; 97 98// ÐÑовеÑÑем, ÑвлÑеÑÑÑ Ð»Ð¸ ÑеÑÐµÑ Ð²Ð½ÐµÑним 99function isExternalReferrer(referrer) { 100 if (!referrer || referrer === '') { 101 return false; 102 } 103 104 try { 105 const referrerHost = new URL(referrer).host; 106 return referrerHost !== currentHost; 107 } catch (e) { 108 return false; 109 } 110} 111 112// ÐоÑмализоваÑÑ Ð¼Ð°ÑÑив ÑегменÑов: ÑбÑаÑÑ Ð¿ÑÑÑÑе знаÑÐµÐ½Ð¸Ñ Ð¸ дÑбли, 113// ÑÐ¾Ñ Ñанив иÑÑ Ð¾Ð´Ð½Ñй поÑÑдок Viki. 114function normalizeSegments(segments) { 115 const sourceSegments = Array.isArray(segments) 116 ? segments 117 : (typeof segments === 'string' ? [segments] : []); 118 119 const uniqueSegments = new Set(); 120 121 return sourceSegments.reduce((result, segment) => { 122 if (typeof segment !== 'string') { 123 return result; 124 } 125 126 const normalizedSegment = segment.trim(); 127 if (!normalizedSegment || uniqueSegments.has(normalizedSegment)) { 128 return result; 129 } 130 131 uniqueSegments.add(normalizedSegment); 132 result.push(normalizedSegment); 133 134 return result; 135 }, []); 136} 137 138// ÐолÑÑиÑÑ Ð¼Ð°ÑÑив ÑегменÑов из кÑки. СÑаÑое одиноÑное знаÑение 139// поддеÑживаеÑÑÑ ÑолÑко Ð´Ð»Ñ Ð±ÐµÑÑовной мигÑаÑии на JSON-маÑÑив. 140function getSegmentsFromCookie() { 141 const value = CookieUtils.get(COOKIE_SEGMENT); 142 if (!value) { 143 return []; 144 } 145 146 try { 147 return normalizeSegments(JSON.parse(value)); 148 } catch (e) { 149 return normalizeSegments([value]); 150 } 151} 152 153function saveSegments(segments) { 154 const normalizedSegments = normalizeSegments(segments); 155 CookieUtils.set(COOKIE_SEGMENT, JSON.stringify(normalizedSegments)); 156 updateRegLinksWithSegments(normalizedSegments); 157 return normalizedSegments; 158} 159 160// ÐеÑед добавлением акÑÑалÑнÑÑ segments ÑдалиÑÑ Ð¿Ñежние segment/segments. 161function updateRegLinksWithSegments(segments = getSegmentsFromCookie()) { 162 const normalizedSegments = normalizeSegments(segments); 163 164 const links = document.querySelectorAll('a.reglink'); 165 if (!links || links.length === 0) return; 166 167 links.forEach((link) => { 168 const href = link.getAttribute('href'); 169 if (!href) return; 170 171 try { 172 const url = new URL(href, window.location.origin); 173 url.searchParams.delete('segment'); 174 url.searchParams.delete('segments'); 175
176 normalizedSegments.forEach((segment) => { 177 url.searchParams.append('segment', segment); 178 }); 179 180 // еÑли ÑÑÑлка оÑноÑиÑелÑÐ½Ð°Ñ â ÑÐ¾Ñ ÑанÑем оÑноÑиÑелÑноÑÑÑ 181 const isRelative = !/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(href); 182 link.setAttribute('href', isRelative ? (url.pathname + url.search + url.hash) : url.toString()); 183 } catch (e) { 184 // некоÑÑекÑнÑй href â пÑопÑÑкаем 185 } 186 }); 187} 188 189// ÐоддеÑживаем ÑÑаÑÑй segment и новÑй segments. ÐнаÑение каждого Ð¿Ð¾Ð»Ñ 190// Ð¼Ð¾Ð¶ÐµÑ Ð±ÑÑÑ ÑÑÑокой или маÑÑивом; пÑи налиÑии segments оно пÑиоÑиÑеÑно. 191function getSegmentsFromVikiResponse(data) { 192 if (!data || typeof data !== 'object') { 193 return []; 194 } 195 196 const sourceSegments = Object.prototype.hasOwnProperty.call(data, 'segments') 197 ? data.segments 198 : data.segment; 199 200 return normalizeSegments(sourceSegments); 201} 202 203// ÐÑе ÑпоÑÐ¾Ð±Ñ Ð¿Ð¾Ð»ÑÑÐµÐ½Ð¸Ñ Ð¾ÑвеÑа Viki иÑполÑзÑÑÑ Ð¾Ð´Ð¸Ð½ и ÑÐ¾Ñ Ð¶Ðµ обÑабоÑÑик. 204function handleVikiResponse(data, source = 'Viki API') { 205 const segments = saveSegments(getSegmentsFromVikiResponse(data)); 206 207 console.log('Segments saved to cookie via ' + source + ':', segments); 208 209 if (data && typeof data === 'object' && !Array.isArray(data)) { 210 return { ...data, segments }; 211 } 212 213 return { segments }; 214} 215 216// ÐбÑабоÑка ÑеÑеÑов 217function processReferrer() { 218 const currentReferrer = document.referrer; 219 220 if (!isExternalReferrer(currentReferrer)) { 221 return; 222 } 223 224 let referrers = []; 225 226 // ÐолÑÑаем ÑÑÑеÑÑвÑÑÑие ÑеÑеÑÑ Ð¸Ð· кÑки 227 const existingReferrersCookie = CookieUtils.get(COOKIE_REFER); 228 if (existingReferrersCookie) { 229 try { 230 referrers = JSON.parse(existingReferrersCookie); 231 // ÐÑли ÑÑо маÑÑив, ÑÐ¾Ñ ÑанÑем ÑолÑко пеÑвÑй ÑÐ»ÐµÐ¼ÐµÐ½Ñ Ð¸ добавлÑем новÑй 232 if (Array.isArray(referrers) && referrers.length > 0) { 233 // Ð¡Ð¾Ñ ÑанÑем пеÑвÑй ÑеÑÐµÑ 234 const firstReferrer = referrers[0]; 235 referrers = [firstReferrer, currentReferrer]; 236 } else { 237 // ÐÑли ÑÑо-Ñо поÑло не Ñак, наÑинаем заново 238 referrers = [currentReferrer]; 239 } 240 } catch (e) { 241 referrers = [currentReferrer]; 242 } 243 } else { 244 // ÐеÑвÑй Ð·Ð°Ñ Ð¾Ð´ 245 referrers = [currentReferrer]; 246 } 247 248 // Ð¡Ð¾Ñ ÑанÑем в кÑÐºÑ 249 CookieUtils.set(COOKIE_REFER, JSON.stringify(referrers)); 250} 251 252// ÐбÑабоÑка ÑоÑек Ð²Ñ Ð¾Ð´Ð° 253function processEntryPoint() { 254 const currentReferrer = document.referrer; 255 256 // ÐÑовеÑÑем, еÑÑÑ Ð»Ð¸ пеÑÐµÑ Ð¾Ð´ Ñ Ð²Ð½ÐµÑнего иÑÑоÑника 257 if (!isExternalReferrer(currentReferrer)) { 258 return false; 259 } 260 261 // ÐолÑÑаем ÑекÑÑий URL без паÑамеÑÑов и Ñ Ð¾ÑÑа (ÑолÑко пÑÑÑ) 262 const currentUrl = window.location.pathname; // ÐапÑимеÑ: "/forms" 263 264 let entryPoints = []; 265 266 // ÐолÑÑаем ÑÑÑеÑÑвÑÑÑие ÑоÑки Ð²Ñ Ð¾Ð´Ð° из кÑки 267 const existingEntryPointsCookie = CookieUtils.get(COOKIE_ENTRY_POINT); 268 if (existingEntryPointsCookie) { 269 try { 270 entryPoints = JSON.parse(existingEntryPointsCookie); 271 if (Array.isArray(entryPoints) && entryPoints.length > 0) { 272 // Ð¡Ð¾Ñ ÑанÑем пеÑвÑÑ ÑоÑÐºÑ Ð²Ñ Ð¾Ð´Ð° 273 const firstEntryPoint = entryPoints[0]; 274 entryPoints = [firstEntryPoint, currentUrl]; 275 } else { 276 entryPoints = [currentUrl]; 277 } 278 } catch (e) { 279 entryPoints = [currentUrl]; 280 } 281 } else { 282 // ÐеÑÐ²Ð°Ñ ÑоÑка Ð²Ñ Ð¾Ð´Ð° 283 entryPoints = [currentUrl]; 284 } 285 286 // Ð¡Ð¾Ñ ÑанÑем в кÑÐºÑ 287 CookieUtils.set(COOKIE_ENTRY_POINT, JSON.stringify(entryPoints)); 288 289 return true; 290} 291 292// ÐÑпÑавка запÑоÑа к Viki API ÑеÑез CORS proxy или напÑÑмÑÑ 293async function sendToVikiApi() { 294 try { 295 // ÐолÑÑаем ÑекÑÑÑÑ ÑоÑÐºÑ Ð²Ñ Ð¾Ð´Ð° ÐÐ ÐУÐÐ (Ñже в ÑоÑмаÑе без Ñ Ð¾ÑÑа) 296 const entryPointsCookie = CookieUtils.get(COOKIE_ENTRY_POINT); 297 let entryPoint = ''; 298 299 if (entryPointsCookie) { 300 try { 301 const entryPoints = JSON.parse(entryPointsCookie); 302 if (Array.isArray(entryPoints) && entryPoints.length > 0) { 303 // ÐеÑем пеÑвÑÑ ÑоÑÐºÑ Ð²Ñ Ð¾Ð´Ð° (Ñже без Ñ Ð¾ÑÑа, напÑÐ¸Ð¼ÐµÑ "/forms") 304 entryPoint = entryPoints[0]; 305 } 306 } catch (e) { 307 console.error('Error parsing entry points:', e); 308 } 309 } 310 311 // ÐобавлÑем паÑамеÑÑÑ: ÑоÑÐºÑ Ð²Ñ Ð¾Ð´Ð° (без Ñ Ð¾ÑÑа) и домен 312 const params = new URLSearchParams(); 313 314 if (entryPoint) { 315 // entryPoint Ñже должен бÑÑÑ Ð² ÑоÑмаÑе без Ñ Ð¾ÑÑа, напÑÐ¸Ð¼ÐµÑ "/forms" 316 params.append('entry_point', entryPoint); 317 } 318 319 // ÐобавлÑем домен ÑекÑÑего ÑайÑа 320 params.append('domain', window.location.hostname); 321 322 const urlWithParams = `${VIKI_API_URL}?${params.toString()}`; 323 324 console.log('Sending request to Viki API:', urlWithParams); 325 326 // СÑÑаÑÐµÐ³Ð¸Ñ 1: ÐÑÑмой fetch Ñ mode 'cors' (еÑли ÑеÑÐ²ÐµÑ Ð¿Ð¾Ð´Ð´ÐµÑÐ¶Ð¸Ð²Ð°ÐµÑ CORS) 327 try { 328 const response = await fetch(urlWithParams, { 329 method: 'GET', 330 mode: 'cors', 331 headers: { 332 'Content-Type': 'application/json', 333 'Accept': 'application/json', 334 }, 335 credentials: 'omit', 336 }); 337 338 if (!response.ok) { 339 throw new Error(`HTTP error! status: ${response.status}`); 340 } 341 342 const data = await response.json(); 343 return handleVikiResponse(data, 'direct fetch'); 344 345 } catch (fetchError) { 346 console.log('Direct fetch failed, trying JSONP method:', fetchError.message); 347 348 // СÑÑаÑÐµÐ³Ð¸Ñ 2: JSONP (еÑли ÑеÑÐ²ÐµÑ Ð¿Ð¾Ð´Ð´ÐµÑживаеÑ) 349 const jsonpData = await new Promise((resolve) => { 350 const callbackName = 'vikiCallback_' + Date.now(); 351 352 const script = document.createElement('script'); 353 script.src = `${urlWithParams}&callback=${callbackName}`; 354 355 window[callbackName] = function(data) { 356 document.head.removeChild(script); 357 delete window[callbackName]; 358 359 resolve(handleVikiResponse(data, 'JSONP')); 360 }; 361 362 script.onerror = function() { 363 document.head.removeChild(script); 364 delete window[callbackName]; 365 console.error('JSONP request failed'); 366 resolve(null); 367 }; 368 369 document.head.appendChild(script); 370 371 setTimeout(() => { 372 if (window[callbackName]) { 373 document.head.removeChild(script); 374 delete window[callbackName]; 375 console.log('JSONP request timeout'); 376 resolve(null); 377 } 378 }, 5000); 379 }); 380 381 if (jsonpData) { 382 return jsonpData; 383 } 384 385 console.log('JSONP failed, trying proxy method'); 386 return sendToVikiApiViaProxy(); 387 } 388 389 } catch (error) { 390 console.error('Error sending request to Viki API:', error); 391 return null; 392 } 393} 394 395// ÐлÑÑеÑнаÑивнÑй меÑод: оÑпÑавка ÑеÑез proxy на Ñом же домене 396async function sendToVikiApiViaProxy() { 397 try { 398 // ÐолÑÑаем ÑекÑÑÑÑ ÑоÑÐºÑ Ð²Ñ Ð¾Ð´Ð° 399 const entryPointsCookie = CookieUtils.get(COOKIE_ENTRY_POINT); 400 let entryPoint = ''; 401 402 if (entryPointsCookie) { 403 try { 404 const entryPoints = JSON.parse(entryPointsCookie); 405 if (Array.isArray(entryPoints) && entryPoints.length > 0) { 406 entryPoint = entryPoints[0]; 407 } 408 } catch (e) { 409 console.error('Error parsing entry points:', e); 410 } 411 } 412 413 // ÐÑпÑавлÑем запÑÐ¾Ñ Ð½Ð° Ñвой proxy endpoint (нÑжно наÑÑÑоиÑÑ Ð½Ð° бÑкенде) 414 const proxyUrl = '/api/viki-proxy'; 415 416 const response = await fetch(proxyUrl, { 417 method: 'POST', 418 headers: { 419 'Content-Type': 'application/json', 420 }, 421 body: JSON.stringify({ 422 entry_point: entryPoint, 423 domain: window.location.hostname 424 }), 425 }); 426 427 if (!response.ok) { 428 throw new Error(`HTTP error! status: ${response.status}`); 429 } 430 431 const data = await response.json(); 432 return handleVikiResponse(data, 'proxy'); 433 434 } catch (error) { 435 console.error('Error sending request via proxy:', error); 436 return null; 437 } 438} 439 440// ÐÑÐ½Ð¾Ð²Ð½Ð°Ñ ÑÑнкÑÐ¸Ñ Ð¸Ð½Ð¸ÑиализаÑии 441function initTracking() { 442 // ÐÑовеÑÑем, доÑÑÑпен ли доÑÑÑп к кÑкам 443 if (!navigator.cookieEnabled) { 444 console.warn('Cookies are disabled. Tracking may not work properly.'); 445 return; 446 } 447 448 // ÐбÑабаÑÑваем ÑеÑÐµÑ 449 processReferrer(); 450 451 // ÐбÑабаÑÑваем ÑоÑÐºÑ Ð²Ñ Ð¾Ð´Ð° (вÑегда, еÑли еÑÑÑ Ð²Ð½ÐµÑний ÑеÑеÑ) 452 processEntryPoint(); 453 454 // ÐÑли кÑка Ñже еÑÑÑ, ноÑмализÑем и ÑÐ¾Ñ ÑанÑем ее как JSON-маÑÑив. 455 const hasSegmentCookie = CookieUtils.exists(COOKIE_SEGMENT); 456 const storedSegments = getSegmentsFromCookie(); 457 458 if (hasSegmentCookie) { 459 // Ð¡Ð¾Ñ Ñанение cookie одновÑеменно обновлÑÐµÑ ÑÑÑлки. 460 saveSegments(storedSegments); 461 } else { 462 // Ðаже без cookie оÑиÑаем ÑÑаÑÑе segment/segments из ÑазмеÑки. 463 updateRegLinksWithSegments([]); 464 } 465 466 // ÐÑпÑавлÑем запÑÐ¾Ñ Ðº Viki API еÑли Ð½ÐµÑ ÐºÑки ÑегменÑа 467 if (!hasSegmentCookie) { 468 sendToVikiApi().then(data => { 469 if (data) { 470 const event = new CustomEvent('segmentDataReceived', { detail: data }); 471 window.dispatchEvent(event); 472 } 473 }).catch(error => { 474 console.error('Failed to get segment data:', error); 475 }); 476 } 477} 478 479// ÐÑовеÑÑем, ÑÑо Ð¼Ñ Ð² бÑаÑзеÑе 480if (typeof window !== 'undefined' && typeof document !== 'undefined') { 481 // ÐниÑиализиÑÑем оÑÑлеживание пÑи загÑÑзке ÑÑÑаниÑÑ 482 if (document.readyState === 'loading') { 483 document.addEventListener('DOMContentLoaded', initTracking); 484 } else { 485 initTracking(); 486 } 487} 488
489// ÐкÑпоÑÑиÑÑем ÑÑнкÑии Ð´Ð»Ñ Ð¸ÑполÑÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð² дÑÑÐ³Ð¸Ñ Ð¼Ð¾Ð´ÑлÑÑ (еÑли нÑжно) 490if (typeof module !== 'undefined' && module.exports) { 491 module.exports = { 492 CookieUtils, 493 isExternalReferrer, 494 processReferrer, 495 processEntryPoint, 496 sendToVikiApi, 497 sendToVikiApiViaProxy, 498 initTracking, 499 normalizeSegments, 500 getSegmentsFromVikiResponse, 501 getSegmentsFromCookie, 502 saveSegments, 503 handleVikiResponse, 504 updateRegLinksWithSegments, 505 COOKIE_DOMAIN, 506 COOKIE_REFER, 507 COOKIE_ENTRY_POINT, 508 COOKIE_SEGMENT, 509 VIKI_API_URL 510 }; 511}
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.