1// 2// Clarity v1.18.1 (29.08.2024) 3// Part of Chatium Platform 4// https://gcext.ru 5// 6;(function () { 7 8 if (window.clarityScriptExecuted) { 9 return; 10 } 11 window.clarityScriptExecuted = true 12 13 function getCookie(name) { 14 const matches = document.cookie.match(new RegExp( 15 "(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)" 16 )); 17 return matches ? decodeURIComponent(matches[1]) : undefined; 18 } 19 20 const cookieUid = getCookie('x-chtm-uid'); 21 const cookieSid = getCookie('x-chtm-uid-sid'); 22 23 const [visitorId, visitId, sessionId] = findGetcourseFormsVisitorVisitSession() 24 25 const serverInitTime = 1790339473057; 26 let firstClarityTimestamp = serverInitTime; 27 const clientInitTime = Date.now(); 28 const clarityRunId = 3622652914; 29 30 function getCurrentTimestamp() { 31 return serverInitTime + (Date.now() - clientInitTime) 32 } 33 34 let queryUid = null 35 let parentClarityQueryData; 36 try { 37 const url = new URL(window.location.href) 38 if (url.searchParams.get('clrtQueryData')) { 39 const parsedParentClarityQueryData = JSON.parse(url.searchParams.get('clrtQueryData')) 40 if (parsedParentClarityQueryData && parsedParentClarityQueryData.uid && parsedParentClarityQueryData.sid) { 41 parentClarityQueryData = parsedParentClarityQueryData; 42 } 43 } 44 if (url.searchParams.get('clarity_uid')) { 45 queryUid = url.searchParams.get('clarity_uid') 46 removeQueryClarityUid(); 47 } else if (url.searchParams.get('loc')) { 48 const locUrl = new URL(url.searchParams.get('loc')) 49 if (locUrl.searchParams.get('clarity_uid')) { 50 queryUid = locUrl.searchParams.get('clarity_uid') 51 } 52 } 53 } catch (error) { 54 console.error(error) 55 } 56 57 const uid = queryUid || (parentClarityQueryData && parentClarityQueryData.uid) || cookieUid || "cy-ELAKEXR59ILnGq04zP_X7E1ymMva4"; 58 const sid = (parentClarityQueryData && parentClarityQueryData.sid) || cookieSid || "LUvRdJmhJW1c30y6RvKVdZAdQ5EuXFEh:1790339473056"; 59 60 const inferredUid = cookieUid || queryUid ? false : false; 61 const inferredSid = cookieSid ? false : false; 62 63 document.cookie = `x-chtm-uid=${uid}; max-age=31536000; path=/;`; 64 document.cookie = `x-chtm-uid-sid=${sid}; max-age=1800; path=/;`; 65 66 const query = {}; 67 const params = new URLSearchParams(location.search); 68 69 [ 70 'utm_funnel', 'utm_node', 'utm_node_from', 71 'utm_action', 'utm_action_params', 72 'utm_action_param1', 'utm_action_param2', 'utm_action_param3', 73 'utm_action_param1_float', 'utm_action_param2_float', 'utm_action_param3_float', 74 'utm_action_param1_int', 'utm_action_param2_int', 'utm_action_param3_int', 75 ].forEach(function (param) { 76 if (params.get(param)) query[param] = params.get(param); 77 }) 78 79 let referer = '' 80 try { 81 referer = document.referrer || sessionStorage.getItem('x-chtm-rfr') || ''; 82 sessionStorage.setItem('x-chtm-rfr-p', sessionStorage.getItem('x-chtm-rfr')); 83 sessionStorage.setItem('x-chtm-rfr', window.location.href); 84 } catch (_) {} 85 86 function enrichUrl(url) { 87 return url.startsWith('http://') || url.startsWith('https://') || url.startsWith('event://') 88 ? url 89 : 'event://custom/' + url 90 } 91 92 window.clrtIsReactive = false 93 function appendSocketStoreToBody() { 94 if (false || window.clrtReactivity === true) { 95 if (window.clrtIsReactive === false) { 96 window.clrtIsReactive = true 97 console.log('ð Clarity reactivity enabled') 98 99 const script = document.createElement('script'); 100 script.src = "https://arabic-calligraphy.ru/chtm/s/metric/socket-bundle.8dU6jrcTp8rM8.js"; 101 script.onload = function () { 102 const socketStore = new SocketStore({ baseURL: 'wss://app.gcext.su/' }); 103 socketStore.setToken(); 104 if (clrtUserSocketId) { 105 socketStore.subscribeToData(clrtUserSocketId).listen(function (params) { 106 console.log('ð Clarity user socket', params) 107 108 window.dispatchEvent(new CustomEvent('claritySocket', { detail: { ...params } } )); 109 window.postMessage(JSON.stringify({ source: 'clarity', action: 'claritySocket', params })); 110 }); 111 } 112 socketStore.subscribeToData(clrtSocketId).listen(function (params) { 113 console.log('ð Clarity uid socket', params) 114 115 window.dispatchEvent(new CustomEvent('claritySocket', { detail: { ...params } } )); 116 window.postMessage(JSON.stringify({ source: 'clarity', action: 'claritySocket', params })); 117 }); 118 } 119 document.body.appendChild(script) 120 } 121 } 122 } 123
124 var clarityInit = false 125 function clarityTrack(params) { 126 if (parentClarityQueryData && window !== window.top) { 127 return 128 } 129 130 try { 131 if (params.url && params.url.startsWith('event://')) { 132 params.referer = document.location.href 133 } 134 } catch (e) { 135 // ignore that error 136 } 137 138 139 function appendTrackerToBody() { 140 const img = document.createElement('img'); 141 img.style.position = 'absolute'; 142 img.style.left = '-1px'; 143 img.style.top = '-1px'; 144 img.style.width = '1px'; 145 img.style.height = '1px'; 146 img.src = getClarityImageUrl(typeof params === 'string' ? { url: enrichUrl(params) } : params); 147 img.onload = function () { 148 img.remove(); 149 }; 150 document.body.appendChild(img); 151 152 try { 153 if (clarityInit === false) { 154 window.dispatchEvent(new CustomEvent('clarityInit', { detail: { ...params } } )); 155 window.postMessage(JSON.stringify({ source: 'clarity', action: 'clarityInit', params })); 156 } 157 158 window.dispatchEvent(new CustomEvent('clarityTrack', { detail: { ...params, initial: clarityInit === false } } )); 159 window.postMessage(JSON.stringify({ source: 'clarity', action: 'clarityTrack', params: { ...params, initial: clarityInit === false } })); 160 } catch (error) { 161 console.error(error) 162 } 163 164 try { 165 if (clarityInit === false) { 166 appendSocketStoreToBody() 167 } 168 } catch (error) { 169 console.error(error) 170 } 171 172 clarityInit = true 173 } 174 175 if (document.body) { 176 appendTrackerToBody() 177 } else { 178 window.addEventListener("DOMContentLoaded", appendTrackerToBody, { once: true }) 179 } 180 } 181 182 function getClarityImageUrl(rewrite) { 183 var baseUrl = "https://arabic-calligraphy.ru/chtm/s/metric/clarity.gif" 184 var resultReferer = referer 185 var resultUrl = document.location.href 186 var resultDomain = document.location.hostname 187 188 try { 189 const params = new URLSearchParams(document.location.search) 190 if (params.get('loc') !== null) { 191 resultUrl = params.get('loc') 192 var url = new URL(resultUrl) 193 194 resultDomain = url.hostname 195 } 196 if (params.get('ref') !== null) { 197 resultReferer = params.get('ref') 198 } 199 } catch (error) { 200 console.error(error) 201 } 202 203 var params = { 204 c2: firstClarityTimestamp || getCurrentTimestamp(), 205 uid, 206 sid, 207 referer: resultReferer, 208 url: resultUrl, 209 domain: resultDomain, 210 title: document.title, 211 width: screen.width, 212 height: screen.height, 213 pr: window.devicePixelRatio, 214 iuid: inferredUid, 215 isid: inferredSid, 216 visitor: visitorId, 217 visit: visitId, 218 session: sessionId, 219 enc: window.chtmClarityEncoded || undefined, 220 ul: window.ULong || undefined, 221 ...query, 222 ...(rewrite || {}), 223 clrt_run_id: clarityRunId 224 } 225 firstClarityTimestamp = 0; 226 227 return baseUrl + '?' + Object.keys(params).map(function(param) { 228 return params[param] ? param + '=' + encodeURIComponent(params[param]) : '' 229 }).filter(Boolean).join('&'); 230 } 231 232 function exposeUidToGetcourseForms() { 233 try { 234 function hiddenUidInput() { 235 const input = document.createElement('input'); 236 input.setAttribute('type', 'hidden'); 237 input.setAttribute('name', 'formParams[clarity_uid]'); 238 input.setAttribute('value', uid); 239 input.setAttribute('style', 'display:none;position:absolute;width:0;max-width:0;height:0;max-height:0;left:-999999999px;pointer-events:none;vizability:hidden;') 240 return input; 241 } 242 243 244 Array 245 .from(document.forms) 246 .filter(form => form.getAttribute('action') && form.getAttribute('action').includes('/pl/')) 247 .forEach(form => form.appendChild(hiddenUidInput())); 248 249 if (window.location.href.startsWith('https://testchatium1.getcourse.ru/')) { 250 formObserver = new MutationObserver(function (records) {
251 Array.from(records).forEach(record => { 252 Array.from(record.addedNodes).forEach(record => { 253 if (record instanceof HTMLDivElement) { 254 const forms = record.querySelectorAll('form') 255 256 Array 257 .from(forms) 258 .filter(form => form.getAttribute('action') && form.getAttribute('action').includes('/pl/')) 259 .forEach(form => form.appendChild(hiddenUidInput())); 260 261 if (forms.length) { 262 formObserver.disconnect() 263 } 264 } 265 }) 266 }) 267 }) 268 269 observer = new MutationObserver(function (records) { 270 Array.from(records).forEach(record => { 271 Array.from(record.addedNodes).forEach(record => { 272 if (record instanceof HTMLDivElement) { 273 if (record.classList.contains('gc-modal')) { 274 formObserver.observe(record, { childList: true, subtree: true }) 275 } 276 } 277 }) 278 }) 279 }) 280 281 observer.observe(document.body, { childList: true }) 282 } 283 } catch (error) { 284 console.error(error); 285 } 286 } 287 288 function removeQueryClarityUid() { 289 // if no body yet - delay removing 290 if (!document.body || document.readyState === 'loading') { 291 document.addEventListener('DOMContentLoaded', removeQueryClarityUid, { once: true }); 292 return; 293 } 294 // if there is a gc form script on page left - delay removing 295 const existsGcIframeScripts = Array.from(document.querySelectorAll('script')).some(function(script) { 296 return typeof script.src === 'string' && script.src.includes('pl/lite/widget/script'); 297 }) 298 if (existsGcIframeScripts) { 299 setTimeout(removeQueryClarityUid, 1000); 300 return; 301 } 302 303 try { 304 const url = new URL(window.location.href); 305 url.searchParams?.delete('clarity_uid'); 306 if (window.history) { 307 window.history.replaceState(null, null, url.toString()) 308 } 309 } catch (error) { 310 console.error(error); 311 } 312 } 313 314 function replaceTelegramStartUid() { 315 if (uid) { 316 Array.from(document.querySelectorAll('a')).forEach(function (a) { 317 if (a.href && a.href.includes('{clarity_uid}')) { 318 a.href = a.href.replace('{clarity_uid}', 'uid-' + uid) 319 } 320 }) 321 } 322 } 323 324 function findGetcourseFormsVisitorVisitSession() { 325 try { 326 const forms = Array.from(document.forms).filter(form => form.getAttribute('action') && form.getAttribute('action').includes('gcVisitor')) 327 if (forms.length > 0) { 328 const action = forms[0].getAttribute('action') 329 const url = new URL(action) 330 331 let visitorId = undefined 332 let visitId = undefined 333 let sessionId = undefined 334 335 try { 336 const gcVisitorParam = url.searchParams.get('gcVisitor') 337 const gcVisitor = JSON.parse(gcVisitorParam) 338 if (gcVisitor.id) visitorId = gcVisitor.id 339 } catch (_) {} 340 341 try { 342 const gcVisitParam = url.searchParams.get('gcVisit') 343 const gcVisit = JSON.parse(gcVisitParam) 344 if (gcVisit.id) visitId = gcVisit.id 345 if (gcVisit.sid) sessionId = gcVisit.sid 346 } catch (_) {} 347 348 return [visitorId, visitId, sessionId] 349 } 350 } catch (error) { 351 console.error(error) 352 } 353 354 return [undefined, undefined, undefined] 355 } 356 357 /** user webinar **/ 358 function webinarTrack(webinarId) { 359 if ( 360 window.webinar === undefined || 361 window.webinar.status === undefined || 362 (window.webinar.status != 'opened' && window.webinar.status != 'started') 363 ) { 364 setTimeout(() => { 365 webinarTrack(webinarId) 366 }, 1000); // wait 1 second, to check status later 367 return; 368 } 369 const visitorId = window.webinar.visitorId 370 const launchId = window.webinar.launchId // maybe it's usefull 371 const userId = window.accountUserId && window.accountUserId > 0 ? window.accountUserId : '' 372 const fullUserId = userId && window.accountId ? window.accountId + ':' + userId : '' 373 374 let adminMode = false;
375 if (typeof isAdminView !== "undefined") { // global variable 376 adminMode = !!isAdminView 377 } 378 const viewerMode = adminMode ? 'admin' : 'user' 379 380 clarityTrack({ 381 url: "event://getcourse/webinar/visit?id=" + encodeURIComponent(webinarId), 382 visitor: visitorId, 383 userId: fullUserId, 384 action_param1: webinarId, 385 action_param2: viewerMode, 386 action_param1_int: launchId, 387 }) 388 } 389 390 window.chtmClarityTrack = clarityTrack; 391 window.rfnl = clarityTrack; 392 window.clrtUid = uid; 393 window.clrtSid = sid; 394 window.clrtQueryData = {uid: uid, sid: sid}; 395 window.clrtTrack = clarityTrack; 396 window.clrtTracked = false; 397 window.clrtMakeReactive = function () { 398 window.clrtReactivity = true; 399 appendSocketStoreToBody(); 400 } 401 window.clrtUrlToTelegramBot = function (name) { 402 return 'https://t.me/' + name + '?start=uid-' + uid; 403 } 404 window.clrtRedirectToTelegramBot = function (name, target, windowFeatures) { 405 window.open(window.clrtUrlToTelegramBot(name), target, windowFeatures) 406 } 407 408 window.startFunnel = function (sceneId) { 409 if (sceneId) { 410 clarityTrack({ 411 url: "event://refunnels/startScenario/" + sceneId, 412 action: "event_js" 413 }) 414 } 415 } 416 417 function trackBehaviour() { 418 if (parentClarityQueryData && window !== window.top) { 419 return; 420 } 421 422 function getTime() { 423 if (window.performance && typeof window.performance.now === 'function') { 424 return Math.floor(window.performance.now()) 425 } 426 427 return new Date().getTime() 428 } 429 430 const trackingStartedAt = getTime() 431 432 const browserSessionId = sessionStorage.getItem('clrt-v8-session-id') || "o4CZfF9FaavDzfi6W6jhJOC2zKG-W_CY"; 433 sessionStorage.setItem('clrt-v8-session-id', browserSessionId) 434 435 const browserSessionStartedAt = sessionStorage.getItem('clrt-v8-session-at') 436 ? parseInt(sessionStorage.getItem('clrt-v8-session-at')) 437 : serverInitTime; 438 sessionStorage.setItem('clrt-v8-session-at', browserSessionStartedAt) 439 440 let viewPercent = 0 441 let focused = document.hasFocus() 442 let loading = false 443 let unfocusedDuration = 0 444 let unfocusStartedAt = 0 445 let scrollDistance = 0 446 let mouseDistance = 0 447 let clickCounter = 0 448 let selectionLength = 0 449 let performanceReady = document.readyState === 'complete' 450 let performanceSent = false 451 452 window.addEventListener('load', function (event) { 453 performanceReady = true 454 }) 455 456 document.addEventListener('pointerdown', function (event) { 457 clickCounter++ 458 }) 459 460 document.addEventListener('pointerup', function (event) { 461 selectionLength = selectionLength + window.getSelection?.()?.toString()?.length 462 }) 463 464 let sx = null, sy = null 465 document.addEventListener('scroll', function (event) { 466 if (document.scrollingElement) { 467 if (sx !== null) { 468 scrollDistance = scrollDistance + Math.abs(document.scrollingElement.scrollLeft - sx) 469 } 470 471 if (sy !== null) { 472 scrollDistance = scrollDistance + Math.abs(document.scrollingElement.scrollTop - sy) 473 } 474 475 sx = document.scrollingElement.scrollLeft 476 sy = document.scrollingElement.scrollTop 477 } 478 }) 479 480 let px = null, py = null 481 document.addEventListener('mousemove', function (event) { 482 if (px !== null) { 483 const x = px - event.pageX; 484 const y = py - event.pageY; 485 486 mouseDistance = mouseDistance + Math.sqrt(x*x + y*y) 487 } 488 489 px = event.pageX 490 py = event.pageY 491 }) 492 493 window.addEventListener('focus', function () { 494 focused = true 495 if (unfocusStartedAt !== 0) { 496 unfocusedDuration = Math.max(0, getTime() - unfocusStartedAt) 497 if (getTime() - unfocusStartedAt < 0) { 498 console.info('Unfocus duration', getTime() - unfocusStartedAt, getTime(), unfocusStartedAt) 499 } 500 } 501 unfocusStartedAt = 0 502 }) 503 504 window.addEventListener('blur', function () { 505 focused = false 506 unfocusStartedAt = getTime() 507 }) 508 509 window.addEventListener("DOMContentLoaded", () => { 510 function documentScrollHandler() { 511 viewPercent = Math.max(viewPercent, getViewPercent()) 512 if (viewPercent === 100) { 513 document.removeEventListener("scroll", documentScrollHandler) 514 } 515 } 516 517 document.addEventListener("scroll", documentScrollHandler) 518 }, { once: true }) 519 520 function getViewPercent() { 521 return Math.round( 522 ( 523 document.scrollingElement.scrollHeight 524 ? Math.max(0, Math.min(1, (document.scrollingElement.clientHeight + document.scrollingElement.scrollTop) / document.scrollingElement.scrollHeight)) 525 : 0 526 ) * 100 527 ) 528 } 529 530 function getBehaviourQueryParams() { 531 let url = document.location.href 532
533 try { 534 const params = new URLSearchParams(document.location.search) 535 if (params.get('loc') !== null) { 536 url = params.get('loc') 537 } 538 } catch (error) { 539 console.error(error) 540 } 541 542 const searchParams = new URLSearchParams() 543 searchParams.set('v', 8) 544 searchParams.set('c', serverInitTime) 545 searchParams.set('uid', uid) 546 searchParams.set('sid', sid) 547 searchParams.set('url', url) 548 searchParams.set('browser_id', browserSessionId) 549 searchParams.set('browser_id_started_at', browserSessionStartedAt) 550 searchParams.set('view_total_duration', Math.max(0, getTime() - trackingStartedAt)) 551 searchParams.set('view_focused_duration', Math.max(0, getTime() - trackingStartedAt - unfocusedDuration)) 552 searchParams.set('mouse_distance', Math.round(mouseDistance)) 553 searchParams.set('scroll_distance', Math.round(scrollDistance)) 554 searchParams.set('click_counter', Math.round(clickCounter)) 555 searchParams.set('selection_length', Math.round(selectionLength)) 556 searchParams.set('clrt_run_id', clarityRunId); 557 if (window.UShort) { 558 searchParams.set('us', window.UShort) 559 } 560 561 if (performanceReady === true && performanceSent === false && window.performance) { 562 searchParams.set('performance', JSON.stringify({ 563 ns: performance.timing.navigationStart, 564 ues: performance.timing.unloadEventStart, 565 uee: performance.timing.unloadEventEnd, 566 rds: performance.timing.redirectStart, 567 rde: performance.timing.redirectEnd, 568 fs: performance.timing.fetchStart, 569 dls: performance.timing.domainLookupStart, 570 dle: performance.timing.domainLookupEnd, 571 cs: performance.timing.connectStart, 572 ce: performance.timing.connectEnd, 573 scs: performance.timing.secureConnectionStart, 574 rqs: performance.timing.requestStart, 575 rss: performance.timing.responseStart, 576 rse: performance.timing.responseEnd, 577 dl: performance.timing.domLoading, 578 di: performance.timing.domInteractive, 579 dcles: performance.timing.domContentLoadedEventStart, 580 dclee: performance.timing.domContentLoadedEventEnd, 581 dc: performance.timing.domComplete, 582 les: performance.timing.loadEventStart, 583 lee: performance.timing.loadEventEnd, 584 })) 585 586 performanceSent = true 587 } 588 589 return searchParams 590 } 591 592 document.addEventListener("visibilitychange", function sendBehaviourBeacon() { 593 if (document.visibilityState === "hidden") { 594 navigator.sendBeacon( 595 "https://arabic-calligraphy.ru/chtm/s/metric/behaviourBeacon" + '?' + getBehaviourQueryParams().toString() 596 ); 597 } 598 }); 599 600 function appendBehaviourImage() { 601 if (loading) { 602 return false 603 } 604 605 loading = true 606 607 function appendTrackerToBody() { 608 const img = document.createElement('img'); 609 img.style.position = 'absolute'; 610 img.style.left = '-1px'; 611 img.style.top = '-1px'; 612 img.style.width = '1px'; 613 img.style.height = '1px'; 614 img.src = "https://arabic-calligraphy.ru/chtm/s/metric/behaviour.gif" + '?' + getBehaviourQueryParams().toString(); 615 img.onload = function () { 616 img.remove(); 617 loading = false 618 }; 619 img.onerror = function () { 620 loading = false 621 } 622 document.body.appendChild(img); 623 } 624 625 if (document.body) { 626 appendTrackerToBody() 627 } else { 628 window.addEventListener("DOMContentLoaded", appendTrackerToBody, { once: true }) 629 } 630 } 631 632 let currentInterval = 10000; // 10 sec 633 function appendBehaviourImageInTimeout() { 634 if (focused) { 635 appendBehaviourImage(); 636 } 637 if (currentInterval < 60000) { 638 currentInterval += 10000; 639 } 640 setTimeout(appendBehaviourImageInTimeout, currentInterval); 641 } 642 643 setTimeout( 644 appendBehaviourImageInTimeout, 645 currentInterval 646 ); 647 648 appendBehaviourImage(); 649 } 650 651 function setup() { 652 if (window.clrtTracked === false) { 653 window.clrtTracked = true 654 655 if (window.gcUniqId !== undefined) { 656 try { 657 if (localStorage.getItem('visit')) { 658 clarityTrack(); 659 } else { 660 const start = Date.now() 661 const handler = setInterval(checkVisit, 500); 662 663 function checkVisit() { 664 if (localStorage.getItem('visit') || Date.now() - start > 1000 * 10) { 665 clearInterval(handler); 666 clarityTrack(); 667 } 668 } 669 } 670 } catch (_) { 671 clarityTrack(); 672 } 673 } else { 674 clarityTrack(); 675 } 676 } 677 678 let webinarId = 0 679 if (window.location.pathname.indexOf('pl/webinar/show') !== -1) { 680 const url = new URL(window.location.href) 681 webinarId = url.searchParams.get('id'); 682 } 683 684 if (webinarId) { 685 setTimeout(() => { 686 webinarTrack(webinarId) 687 }, 1000); // wait 1 second to be sure that user hasn't closed the page 688 } 689 } 690 691 if (document.readyState === 'loading') { 692 document.addEventListener('DOMContentLoaded', function () { 693 setup() 694 }, { once: true }); 695 } else { 696 setup() 697 } 698 699 exposeUidToGetcourseForms(); 700 replaceTelegramStartUid(); 701 702 try { 703 trackBehaviour(); 704 } catch (error) { 705 console.error('trackBehaviour', error) 706 } 707})();
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.