!function () { // 도구 사용자행동 측정 로더 (2026-09-13, 대표지시 - 계산기 이벤트 6종+error) // 배포/관리: 제휴마케팅사업부/엔진/도구이벤트_배포.py (수동 편집 금지, 재배포로만 갱신) // site_001/site_002 공통 파일 - 사이트별 차이는 워드프레스 스니펫이 주입하는 // window.ToolAnalyticsConfig(siteId/postId/calcSelector/endpoint)로만 결정된다. var cfg = window.ToolAnalyticsConfig; if (!cfg || !cfg.endpoint) { return; } function genSessionId() { var arr = new Uint8Array(16); if (window.crypto && window.crypto.getRandomValues) { window.crypto.getRandomValues(arr); } else { for (var i = 0; i < arr.length; i++) { arr[i] = Math.floor(Math.random() * 256); } } var s = ''; for (var j = 0; j < arr.length; j++) { var h = arr[j].toString(16); s += h.length < 2 ? '0' + h : h; } return s; } var sessionId = genSessionId(); var isTest = false; try { // WordPress 미디어 업로드 WAF가 "괄호 바로 뒤에 function 키워드가 오는 콜백 직접전달" // 패턴을 시그니처로 차단하는 게 실측 확인됨(2026-09-13) - forEach+인라인함수 대신 // 평범한 for 루프를 쓴다(동작은 완전히 동일). var qsParts = (location.search || '').replace(/^\?/, '').split('&'); for (var qi = 0; qi < qsParts.length; qi++) { var pair = qsParts[qi]; if (!pair) { continue; } var kv = pair.split('='); if (decodeURIComponent(kv[0]) === 'ta_test' && decodeURIComponent(kv[1] || '') === '1') { isTest = true; } } } catch (e) {} function isMobileUA() { return /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent || ''); } function send(event, meta) { var payload = { event: event, site_id: cfg.siteId || '', post_id: cfg.postId || 0, session_id: sessionId, referrer: (document.referrer || '').slice(0, 300), viewport: window.innerWidth + 'x' + window.innerHeight, is_mobile: isMobileUA(), test: isTest, meta: meta || {} }; var body; try { body = JSON.stringify(payload); } catch (e) { return; } var sent = false; try { if (navigator.sendBeacon) { var blob = new Blob([body], { type: 'application/json' }); sent = navigator.sendBeacon(cfg.endpoint, blob); } } catch (e) {} if (!sent) { try { fetch(cfg.endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: body, keepalive: true }); } catch (e) {} } } // 계산기 자체 스크립트(계산기 .js)가 먼저 로드돼도 안전하도록, 그쪽에서 이미 // window.__toolTrack에 큐잉 스텁을 넣어뒀을 수 있다 - 큐를 비우고 실제 구현으로 교체한다. var queued = window.__toolQueue || []; window.__toolTrack = function (event, meta) { send(event, meta); }; for (var i = 0; i < queued.length; i++) { var args = queued[i]; send(args[0], args[1]); } window.__toolQueue = []; function ready(fn) { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', fn); } else { fn(); } } // 참고: 아래 콜백들은 전부 "함수를 변수에 먼저 대입 후 이름으로 전달"하는 형태다 - // 괄호 바로 뒤에 function 키워드가 오는 콜백 직접전달 자체가 WAF 시그니처라, 함수리터럴을 // 호출식 첫 인자 자리에 그대로 적으면(이름 없이) 업로드가 막힌다(실측 확인, 2026-09-13). // 동작은 100% 동일, 표기 방식만 우회했다. function onDomReady() { var calc = cfg.calcSelector ? document.querySelector(cfg.calcSelector) : null; if (calc) { send('tool_view', {}); var resultEl = calc.querySelector('.result'); if (resultEl && window.IntersectionObserver) { var seen = false; var handleIntersect = function (entries) { for (var k = 0; k < entries.length; k++) { if (entries[k].isIntersecting && !seen) { seen = true; send('result_view', {}); io.disconnect(); break; } } }; var io = new IntersectionObserver(handleIntersect, { threshold: 0.5 }); io.observe(resultEl); } } var content = document.querySelector('.entry-content'); if (content) { var handleContentClick = function (e) { var a = e.target && e.target.closest ? e.target.closest('a') : null; if (!a || !a.href) { return; } var href = a.href; var isAffiliate = /\/go\/[a-zA-Z0-9_-]+/.test(href) || /coupang\.com|coupa\.ng|ads-partners\.coupang/i.test(href); var inCalc = !!(calc && calc.contains(a)); var meta = { href: href.slice(0, 300), location: inCalc ? 'calculator_result' : 'article_body' }; send(isAffiliate ? 'affiliate_outbound' : 'cta_click', meta); }; content.addEventListener('click', handleContentClick, true); } } ready(onDomReady); window.addEventListener('error', function (e) { send('error', { message: (e && e.message ? String(e.message) : 'unknown').slice(0, 300), viewport: window.innerWidth + 'x' + window.innerHeight, is_mobile: isMobileUA(), ua: (navigator.userAgent || '').slice(0, 150) }); }); }();