PageSourceSearch

https://jmsolution.com/app/Manage/js/inflowbeacon.js?vs=3d0b473968a0ec4ec41e3bf59df3aa51&u=jmsolutioncm.1

js jmsolution.com collected 2026-09-26 16:59:22 UTC 10,954 bytes, 216 lines download raw bytes

1/**
2 * 유입 광고매체(cafe_mkt) 비콘 발사
3 *
4 *  - 접속통계 V3(weblog/eclog 비콘) 종료 대응. 광고매체 결정/영속은 캐시되는 진입 페이지가 아니라
5 *    uncached 비콘(/exec/front/inflow/main, appInflowFrontExecMain)에서 수행한다.
6 *  - 모든 사용자 공통의 "정적 발사 스크립트"라 진입 페이지가 HRPCS 캐시되어도 오염되지 않는다.
7 *    아래 게이트는 전부 클라이언트 런타임 값(location/referrer/UA)만 보므로 이 전제를 깨지 않는다.
8 *  - 접속통계 수집기(weblog.js)와 무관한 별도 수집기다.
9 *
10 * @see https://jira.simplexi.com/browse/ECHOSTING-618344 (도입)
11 * @see https://jira.simplexi.com/browse/ECHOSTING-634694 (호출 최소화 게이트)
12 */
13(function () {
14    try {
15        // 중복 발사 가드 — 스크립트가 2회 이상 로드돼도 1회만 발사
16        if (window.__ecInflowBeaconFired === true) {
17            return;
18        }
19        window.__ecInflowBeaconFired = true;
20
21        // top-frame 한정 — iframe 내부(위젯/임베드)는 유입 랜딩이 아니다
22        if (window.self !== window.top) {
23            return;
24        }
25
26        /*
27         * 미리보기 제외 — 스킨 편집 미리보기는 실 유입이 아니다.
28         *
29         * PHP(setInflowBeaconJs)가 아니라 여기서 판정한다. 진입 페이지는 HRPCS 캐시 대상이고
30         * 최소한 메인 페이지는 쿼리스트링이 캐시 키에서 빠지므로(`/?cb=랜덤` 도 x-cache: HIT),
31         * PHP 에서 걸러내면 미리보기 응답이 공유 캐시를 채워 실사용자 전원이 비콘 없는 페이지를
32         * 받게 된다. JS 는 캐시되더라도 매 사용자 브라우저에서 실행되므로 그 오염이 없다.
33         *
34         * 편집기가 미리보기 URL 과 그 안의 링크에 PREVIEW_SDE=1 을 전파한다
35         * (app/Editor/Resource/js/View/Preview.js, appBoardUtilDataList.php).
36         * libRoute::isSkinPreview() 의 HTTP_EC_URI_SKIN_CODE 헤더 분기는 JS 가 볼 수 없어
37         * 커버하지 않는다 (기존 PHP 게이트도 PREVIEW_SDE 만 봤으므로 현행 대비 손실 없음).
38         */
39        if (/[?&]PREVIEW_SDE=1/i.test(location.search) === true) {
40            return;
41        }
42
43        /*
44         * 봇 제외 — 광고매체 attribution 에 봇 트래픽은 무의미하다.
45         *
46         * 봇은 referer 없이 URL 을 직접 찍으므로 아래 신호 게이트의 "내부 회유" 조건에 걸리지 않아
47         * 매 페이지 발사된다. 크롤러가 수천 페이지를 돌면 그대로 수천 호출이라 여기서 끊는다.
48         *
49         * [MUST] 목록은 lib/Inflow/libInflowLoadblock.php 의 $aBotUserAgent 와 항상 동일하게 유지한다.
50         *   순서까지 원본과 맞춰 줄 단위 대조가 되게 둔다. 한쪽만 고치면 판정이 어긋난다.
51         *   - 여기(JS)  : 호출 자체를 막는다. JS 를 실행하는 봇에만 걸린다.
52         *   - 서버(lib) : JS 우회(직접 POST)를 막는다. 최종 방어선.
53         */
54        var A_BOT = [
55            'X-SIMPLEXI-'
56            , 'Daum(oa)?\\/4\\.1'
57            , 'Baiduspider-render'
58            , 'AdsBot-Naver'
59            , 'AdsBot-Google'
60            , 'pyspider'
61            , 'wkhtmltoimage'
62            , 'PhantomJS'
63            , 'YandexBot'
64            , 'KISA Privacy Incident Response System'
65            , 'HubSpot Webcrawler'
66            , 'AmazonProductDiscovery'
67            , 'AmazonSellerInitiatedListing'
68            , 'HeadlessChrome'
69            , 'BingPreview'
70            , 'Bytespider'
71            , 'Googlebot'
72            , 'NaverBot'
73            , 'NHN Corp'
74            , 'msnbot'
75            , 'WebAuto'
76            , 'http:\\/\\/help\\.naver\\.com\\/robots\\/'
77            , 'Yeti'
78            , 'Slurp'
79            , 'bingbot'
80            , 'facebookexternalhit'
81            , 'DotBot'
82            , 'PetalBot'
83            , 'Adsbot'
84            , 'MojeekBot'
85            , 'applebot'
86        ];
87        var RE_BOT = new RegExp('(' + A_BOT.join('|') + ')', 'i');
88        if (RE_BOT.test(navigator.userAgent) === true) {
89            return;
90        }
91
92        /**
93         * 랜딩 URL 신호 — 서버 libInflowResolver::resolve() 가 rloc 에서 보는 파라미터
94         *   cafe_mkt(0순위) / utm_*(1순위+강제매핑) / remind_id·push_code(3순위)
95         *   / k_media(kakao_sa 강제매핑) / sbpm(naver_ks 세분화)
96         * 서버가 실제로 채택하지 않는 조합(utm_source 없는 utm_medium 등)도 포함한다.
97         * 과발사는 현행과 같고 과차단만이 회귀이므로, 판정은 항상 발사 쪽으로 기운다.
98         */
99        var RE_LOC_SIGNAL = /[?&](cafe_mkt|utm_source|utm_medium|utm_campaign|utm_content|remind_id|push_code|k_media|sbpm)=/i;
100
101        /** cafe24plus 앱(2순위). UA 초기화 케이스의 쿠키 폴백은 HttpOnly 라 클라가 못 읽는다 — 아래 주석 참고 */
102        var RE_UA_SIGNAL = /Cafe24Plus/i;
103
104        /**
105         * referer 가 자기 도메인인지 (= 사이트 내부 회유)
106         * 빈 referer(직접 진입 / referrer-policy 차단)는 내부로 단정하지 않는다 → 발사한다.
107         *
108         * @param {string} sRef document.referrer
109         * @return {boolean} 내부 회유면 true
110         */
111        function isInternalReferrer(sRef) {
112            if (!sRef) {
113                return false;
114            }
115            try {
116                var oAnchor = document.createElement('a');
117                oAnchor.href = sRef;
118                return (oAnchor.hostname === location.hostname);
119            } catch (e) {
120                return false;
121            }
122        }
123
124        var sRloc = location.href;
125        var sRref = document.referrer;
126
127        /*
128         * 신호 게이트 — "서버에 물어볼 이유가 있는가" 를 판정한다.
129         *
130         *   미발사 = (판정 재료 없음) AND (내부 회유)
131         *            = ① ② 둘 다 없음   +   ③ referer 가 자기 도메인
132         *
133         *   ① 랜딩 URL 신호   ② cafe24plus 앱 UA   ③ 내부 회유 여부
134         *
135         * weblog 과 목적이 달라서 가능한 최적화다.
136         *   weblog : 페이지뷰 집계가 목적이라 매 페이지 발사가 필요하다.
137         *   inflow : 랜딩 1회 attribution 이 목적이라, 신호 없는 내부 회유는 서버가 100% 빈손이다.
138         *
139         * [referer 의 신호는 보지 않는다]
140         *   서버는 rref 에서도 강제매핑(k_media / dcollection / youtube / chatgpt / claude / gemini)을
141         *   판정하지만, 그 경로가 유일한 기회가 되는 건 "랜딩에서 비콘을 못 쐈을 때" 뿐이다.
142         *     - 랜딩 페이지 미커버 : setInflowBeaconJs 가 Front Hybrid 훅이라 전 프론트 페이지를 커버한다.
143         *     - 랜딩 비콘 실패     : 실패한 사용자는 대개 즉시 이탈이라 주문까지 가지 않아 attribution 이 무의미하다.
144         *   referer 가 외부인 경우는 ③ 이 이미 발사시키므로, referer 신호 검사는 랜딩 직후 첫 클릭을
145         *   중복 호출하게 만들 뿐이었다. (ECHOSTING-634694)
146         *
147         * cafe24plus 앱의 UA 초기화 케이스(ECHOSTING-437231 / 459719)는 서버가 HttpOnly 쿠키
148         * (EC_MOBILE_PLUS_APP) 로 폴백 판정하므로 클라가 단독으로 판단할 수 없다. 다만 앱 첫 진입은
149         * referer 가 비어 조건 ③ 으로 항상 발사되고 거기서 ec_cafe_mkt 가 심어지므로, 이후 내부 회유가
150         * 차단돼도 attribution 손실이 없다.
151         * fb_external_id, fb_event_id, siteLT 쿠키가 없으면 inflow 비콘을 호출해줘야 함. 
152         */
153        if (RE_LOC_SIGNAL.test(sRloc) === false
154            && RE_UA_SIGNAL.test(navigator.userAgent) === false
155            && isInternalReferrer(sRref) === true
156            && document.cookie.includes('fb_external_id') === true
157            && document.cookie.includes('fb_event_id') === true
158        ) {
159            return;
160        }
161
162        var sUrl = '/exec/front/inflow/main';
163        var sBody = 'rloc=' + encodeURIComponent(sRloc)
164                  + '&rref=' + encodeURIComponent(sRref);
165
166        // 접속통계 iframe 이관: 쿠키가 없는 경우에만 sessionStorage 값을 비콘에 실어 전달한다.
167        // 전송 조건은 weblog.js 와 동일하게 "쿠키 우선, 부재 시 sessionStorage" 규칙을 따른다.
168        // u 파싱은 URLSearchParams 미지원 브라우저를 고려해 별도 try/catch 로 격리한다.
169        // 파싱이 실패해도 external_id/event_id 수집은 계속 진행되어야 한다.
170        // @see https://jira.simplexi.com/browse/ECHOSTING-632441
171        var sMid = '';
172        var sShopNo = '';
173        try {
174            if (document.currentScript && document.currentScript.src) {
175                var sSrc = document.currentScript.src;
176                var sUnit = '';
177                if (typeof URLSearchParams === 'function') {
178                    sUnit = new URLSearchParams(sSrc.split('?')[1]).get('u') || '';
179                } else {
180                    var aMatch = sSrc.match(/[?&]u=([^&]*)/);
181                    sUnit = aMatch ? decodeURIComponent(aMatch[1]) : '';
182                }
183                var aUnit = sUnit.split('.');
184                sMid = aUnit[0] || '';
185                sShopNo = aUnit[1] || '';
186            }
187        } catch (eUnit) {
188            sMid = '';
189            sShopNo = '';
190        }
191
192        if (!document.cookie.includes('fb_external_id') && sessionStorage.getItem('fb_external_id')) {
193            sBody += '&ca_external_id=' + encodeURIComponent(sessionStorage.getItem('fb_external_id'));
194        }
195        if (!document.cookie.includes('fb_event_id') && sessionStorage.getItem('fb_event_id')) {
196            sBody += '&ca_event_id=' + encodeURIComponent(sessionStorage.getItem('fb_event_id'));
197        }
198        if (!document.cookie.includes('siteLT') && sMid !== '' && sShopNo !== '') {
199            var sSiteLt = sessionStorage.getItem('siteLT_' + sMid + '_' + sShopNo);
200            if (sSiteLt) {
201                // ca_shop_no 는 서버에서 현재 요청 몰과의 일치를 재검증하기 위해 함께 보낸다.
202                sBody += '&ca_sitelt=' + encodeURIComponent(sSiteLt)
203                       + '&ca_shop_no=' + encodeURIComponent(sShopNo);
204            }
205        }
206
207        if (navigator.sendBeacon) {
208            navigator.sendBeacon(sUrl, new Blob([sBody], {type: 'application/x-www-form-urlencoded'}));
209        } else {
210            var oXhr = new XMLHttpRequest();
211            oXhr.open('POST', sUrl, true);
212            oXhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
213            oXhr.send(sBody);
214        }
215    } catch (e) {}
216})();

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.