PageSourceSearch

https://arabic-calligraphy.ru/chtm/s/metric/clarity.js

js arabic-calligraphy.ru collected 2026-09-25 12:31:13 UTC 23,124 bytes, 707 lines download raw bytes

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.