1/** 2 * google-onetap-capture â khá»i tạo Google One Tap và lá»i chà o trên top-bar. 3 * 4 * Phụ thuá»c: core.js (window.GOT), phone-popup.js (GOT.showPhonePopup), 5 * thư viá»n GSI cá»§a Google (accounts.google.com/gsi/client). 6 */ 7(function (w) { 8 'use strict'; 9 10 var G = w.GOT; 11 if (!G) return; 12 13 var cfg = G.cfg; 14 15 // Bấm X xong bao lâu thì má»i ÄÄng nháºp lại. Äá» 0 thì prompt nhảy ra tức thì, 16 // trông như trang bá» lá»i; và i giây là Äá»§ Äá» user hiá»u mình vừa thoát xong. 17 var RELOGIN_MS = 3000; 18 19 // Decode payload JWT chá» Äá» hiá»n thá» (tên/ảnh) â không phải Äá» xác thá»±c 20 function decodePayload(jwt) { 21 try { 22 var p = jwt.split('.')[1].replace(/-/g, '+').replace(/_/g, '/'); 23 while (p.length % 4) p += '='; 24 var bytes = Uint8Array.from(atob(p), function (c) { return c.charCodeAt(0); }); 25 return JSON.parse(new TextDecoder('utf-8').decode(bytes)); 26 } catch (e) { return {}; } 27 } 28 29 /** 30 * Tìm (hoặc tạo) chá» Äá» render lá»i chà o. 31 * mode: 32 * 'host' -> theme tá»± cung cấp <div id="got-user-greeting"> 33 * 'nav' -> chèn và o top-bar, ngay cạnh bá» chá»n ngôn ngữ (.choose-lang) 34 * 'floating' -> badge ná»i góc phải dưá»i (dá»± phòng khi không có top-bar) 35 */ 36 function resolveHost() { 37 // 1) Theme có sẵn <div id="got-user-greeting"></div> thì render và o Äó 38 var host = document.getElementById('got-user-greeting'); 39 // 2) Äã render trưá»c Äó -> tái sá» dụng Äúng chá» cÅ© 40 if (!host) host = document.getElementById('got-greeting-badge'); 41 if (host) { 42 return { 43 el: host, 44 mode: host.id === 'got-greeting-badge' ? (host.tagName === 'LI' ? 'nav' : 'floating') : 'host' 45 }; 46 } 47 48 // 3) Chèn thà nh 1 <li> nav má»i, NGAY SAU <li> chứa bá» chá»n ngôn ngữ, 49 // Äá» nằm NGANG HÃNG, bên phải "Vietnam" (các <li> trong top-bar xếp ngang). 50 var lang = document.querySelector('.choose-lang'); 51 if (lang) { 52 host = document.createElement('li'); 53 host.id = 'got-greeting-badge'; 54 host.className = 'html custom got-badge--nav'; 55 var langLi = lang.closest('li'); 56 var navUl = langLi ? langLi.parentNode : null; 57 if (navUl && langLi) navUl.insertBefore(host, langLi.nextSibling); 58 else { host.style.marginLeft = '12px'; lang.appendChild(host); } 59 return { el: host, mode: 'nav' }; 60 } 61 62 // 4) Dá»± phòng: badge ná»i góc phải dưá»i. 63 // bottom:90px Äá» ná»i LÃN TRÃN bong bóng chat/dock thưá»ng nằm sát Äáy phải; 64 // z-index tá»i Äa Äá» không bá» widget khác Äè. 65 host = document.createElement('div'); 66 host.id = 'got-greeting-badge'; 67 host.className = 'got-badge--floating';
68 document.body.appendChild(host); 69 return { el: host, mode: 'floating' }; 70 } 71 72 function renderGreeting(u) { 73 if (!u) return; 74 75 var h = resolveHost(), host = h.el, mode = h.mode; 76 var xicon = '<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">' 77 + '<path d="M1.33333 12C0.966667 12 0.652889 11.8696 0.392 11.6087C0.131111 11.3478 0.000444444 11.0338 0 10.6667V1.33333C0 0.966667 0.130667 0.652889 0.392 0.392C0.653333 0.131111 0.967111 0.000444444 1.33333 0H6V1.33333H1.33333V10.6667H6V12H1.33333ZM8.66667 9.33333L7.75 8.36667L9.45 6.66667H4V5.33333H9.45L7.75 3.63333L8.66667 2.66667L12 6L8.66667 9.33333Z" fill="currentColor"/>' 78 + '</svg>'; 79 80 // Style á» assets/css/onetap.css (#got-greeting-badge) 81 host.innerHTML = 82 '<span class="got-hello">Xin chà o, <strong></strong></span>' 83 + '<a href="#" id="got-forget" class="got-logout" title="Thoát" aria-label="Thoát">' + xicon + '</a>'; 84 85 host.querySelector('strong').textContent = (u.name || u.email || 'bạn'); // chèn tên an toà n (chá»ng XSS) 86 87 var f = document.getElementById('got-forget'); 88 if (f) f.addEventListener('click', function (ev) { 89 ev.preventDefault(); 90 G.forgetUser(); 91 92 // KHÃNG gá»i disableAutoSelect() á» Äây. 93 // 94 // initialize() bên dưá»i Äã truyá»n auto_select:false nên tá»±-Äá»ng-ÄÄng-nháºp 95 // vá»n Äã tắt â gá»i thêm không ÄÆ°á»£c gì. Äá»i lại, á» chế Äá» FedCM nó ghi má»t 96 // dấu á» tầng TRÃNH DUYá»T (preventSilentAccess), nằm ngoà i cookie cá»§a site 97 // nên xoá cookie không dá»n ÄÆ°á»£c, và Äó là Äiá»m khác biá»t duy nhất giữa 98 // "và o trang lần Äầu" (chạy ÄÆ°á»£c) vá»i "sau khi thoát" (FedCM trả vá» 99 // NetworkError: Error retrieving a token). 100 // 101 // Quên user á» phÃa mình là Äá»§: lần sau prompt hiá»n lại y như khách má»i. 102 103 // Gỡ lá»i chà o. host mode: theme sá» hữu phần tá» -> chá» xoá ná»i dung. 104 if (mode !== 'host' && host.parentNode) host.parentNode.removeChild(host); 105 else host.innerHTML = ''; 106 107 // Rá»i má»i ÄÄng nháºp lại NGAY trên trang hiá»n tại. 108 // Plugin nà y Äá» thu lead chứ không phải Äá» ÄÄng nháºp: bá» trá»ng chá» ÄÄng 109 // nháºp sau khi bấm X là mất luôn ÄÆ°á»ng nháºn diá»n khách, mà user cÅ©ng 110 // không có cách nà o quay lại ngoà i viá»c tá»± F5. 111 setTimeout(armPrompt, RELOGIN_MS); 112 }); 113 } 114 115 function note() { 116 if (!cfg.consent || document.getElementById('got-consent-note')) return; 117 var d = document.createElement('div'); 118 d.id = 'got-consent-note'; // style á» assets/css/onetap.css 119 d.textContent = cfg.consent; 120 document.body.appendChild(d); 121 setTimeout(function () { 122 if (d && d.parentNode) { 123 d.classList.add('got-note--out'); 124 setTimeout(function () { if (d.parentNode) d.parentNode.removeChild(d); }, 400); 125 } 126 }, 9000); 127 } 128 129 function handle(resp) { 130 if (!resp || !resp.credential) return; 131 fetch(cfg.captureUrl, { 132 method: 'POST', 133 headers: { 'Content-Type': 'application/json' }, 134 body: JSON.stringify({ credential: resp.credential }) 135 }) 136 .then(function (r) { return r.json(); }) 137 .then(function (d) { 138 if (!d || !d.ok) { if (w.console) w.console.warn('[GOT] capture:', d); return; } 139 var p = decodePayload(resp.credential); 140 var u = { name: p.name || d.name || '', email: p.email || '', picture: p.picture || '', hp: !!d.has_phone }; 141 G.setUser(u); 142 var n = document.getElementById('got-consent-note'); 143 if (n && n.parentNode) n.parentNode.removeChild(n); 144 renderGreeting(u); 145 146 // Server Äã có SÄT (ká» cả do máy khác nháºp trưá»c Äó) -> im lặng, không há»i lại. 147 if (cfg.phoneOn && !d.has_phone && d.ticket && !G.getCookie(G.LATER)) { 148 G.showPhonePopup(d.ticket); 149 } 150 }) 151 .catch(function (e) { if (w.console) w.console.warn('[GOT]', e); }); 152 } 153 154 function startPrompt(nonce) { 155 if (!w.google || !w.google.accounts || !w.google.accounts.id) { 156 return setTimeout(function () { startPrompt(nonce); }, 300); 157 } 158 // Trong Má»T lần tải trang, sau khi GSI Äã trả credential thì prompt() gá»i 159 // lần nữa bá» bá» qua im lặng. cancel() Äóng phiên UI cÅ© Äá» initialize() bên 160 // dưá»i bắt Äầu lại từ Äầu. Thiếu dòng nà y thì bấm X xong dù có gá»i lại 161 // prompt() cÅ©ng không hiá»n gì. 162 try { w.google.accounts.id.cancel(); } catch (e) {} 163 164 w.google.accounts.id.initialize({ 165 client_id: cfg.clientId, 166 callback: handle, 167 nonce: nonce, 168 use_fedcm_for_prompt: true,
169 auto_select: false, 170 cancel_on_tap_outside: false 171 }); 172 w.google.accounts.id.prompt(); 173 note(); 174 } 175 176 /** 177 * Lấy nonce TÆ¯Æ I rá»i báºt prompt. 178 * 179 * Nonce lấy má»i má»i lần chứ không dùng lại: server Äá»i chiếu nonce trong JWT, 180 * và endpoint không-cache là cách duy nhất Äúng khi site báºt full-page cache. 181 * Dùng chung cho cả lúc và o trang lẫn lúc báºt lại sau khi bấm X. 182 */ 183 function armPrompt() { 184 fetch(cfg.nonceUrl, { headers: { Accept: 'application/json' }, cache: 'no-store' }) 185 .then(function (r) { return r.json(); }) 186 .then(function (d) { startPrompt(d && d.nonce ? d.nonce : ''); }) 187 .catch(function () {}); 188 } 189 190 // Top-bar (chứa "Vietnam" + lá»i chà o) mặc Äá»nh bá» .hide-for-medium ẩn á» <=849px. 191 // Cho hiá»n lại RIÃNG cá»t phải trên mobile, và chá» chừa mục ngôn ngữ + lá»i chà o. 192 function showTopbarOnMobile() { 193 var lang = document.querySelector('.choose-lang'); 194 if (!lang) return; 195 var col = lang.closest('.flex-col'); 196 if (col) col.classList.add('got-show-mobile'); 197 // #top-bar (.header-top) Tá»° Nà có .hide-for-medium -> phải un-hide cả wrapper, 198 // nếu không thì các cá»t con dù bỠẩn vẫn nằm trong cha display:none. 199 var bar = lang.closest('.header-top'); 200 if (bar) bar.classList.add('got-show-mobile-bar'); 201 // Các rule @media tương ứng nằm á» assets/css/onetap.css â á» Äây chá» gắn class. 202 } 203 204 function init() { 205 showTopbarOnMobile(); // luôn chạy: "Vietnam" hiá»n trên mobile ká» cả khi chưa ÄÄng nháºp 206 G.captureUtm(); // chá»t UTM ngay trang Äầu, trưá»c khi user Äi tiếp 207 208 // Bản 1.4.1 trá» vá» trưá»c từng Äặt cookie chặn 30 ngà y sau khi bấm X, khiến 209 // ÄÄng xuất xong là không ÄÄng nháºp lại ÄÆ°á»£c. Xoá nếu trình duyá»t còn giữ. 210 if (G.getCookie(G.OPTOUT)) G.delCookie(G.OPTOUT); 211 212 var u = G.getUser(); 213 if (u) { 214 renderGreeting(u); 215 // Äã có SÄT, hoặc vừa Äóng popup -> xong, không cần prompt nữa. 216 // Ngược lại rÆ¡i xuá»ng dưá»i chạy prompt Äá» lấy ticket má»i cho popup SÄT. 217 if (!cfg.phoneOn || u.hp || G.getCookie(G.LATER)) return; 218 } 219 220 armPrompt(); 221 } 222 223 // Hà m phụ cho viá»c test/gỡ lá»i ngoà i luá»ng tá»± chạy 224 G.renderGreeting = renderGreeting; 225 G.init = init; 226 227 if (document.readyState === 'complete') init(); 228 else w.addEventListener('load', init); 229})(window);
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.