//if (typeof _NAVIPLUS === 'undefined') { // Gây lỗi do đè lấp script lên nhau if( true ) { var _NAVIPLUS_VERSION = /*replace*/'DEV'/*endreplace*/; //==> with be replaced to $_GET['version'] //******** Khai báo các biến cần thiết ****************************/ var cdnGithub = 'https://cdn.jsdelivr.net/gh/khoipn/naviplus-fe-live@main'; // var cdnCloudflare = 'https://cdn.naviplus.app/naviplus/golive/live'; var cdnCloudflare = 'https://live.naviplus.app/live'; var cdnUigenCSS = 'https://dev-shopify.naviplus.app/naviplus/frontend'; // if( _NAVIPLUS_VERSION != 'DEV') cdnUigenCSS = cdnGithub + "/" + _NAVIPLUS_VERSION; if( _NAVIPLUS_VERSION != 'DEV') cdnUigenCSS = cdnCloudflare + "/" + _NAVIPLUS_VERSION; var cdnUigenJS = 'https://dev-shopify.naviplus.app/naviplus/frontend'; if( _NAVIPLUS_VERSION != 'DEV') cdnUigenJS = cdnCloudflare + "/" + _NAVIPLUS_VERSION; var cdnJson = 'https://cdn.naviplus.app/naviplus/data/json'; var startJs = 'start.js.php'; if( _NAVIPLUS_VERSION != 'DEV') startJs = "start.js"; var doc = document; var uigenCss = "/uigen/uigen.css.php"; if( _NAVIPLUS_VERSION != 'DEV') uigenCss = "/uigen.min.css"; var uigenJs = "/uigen.js.php"; if( _NAVIPLUS_VERSION != 'DEV') uigenJs = "/uigen.min.js"; //******** Khai báo các biến cần thiết ****************************/ var _NAVIPLUS = { VER: _NAVIPLUS_VERSION, SHOP: '', // ATTRS: {}, VARS: [ cdnUigenCSS + uigenCss, //==> with be replaced to uigen.min.css 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css', 'https://static.naviplus.app/icons/remixicon-lastest/remixicon.css', // Cần phải đánh giá vì hiệu qủa có khi ko lớn mà ko update ngay. // cdnJson + '/{shop}.all.json', // cdnJson + '/{shop}.info.json', cdnUigenJS + uigenJs //==> with be replaced to uigen.min.js ], GO: (function() { const _loaded = { css: [], js: [], json: [] }; function addNavimanApp() { // Đã thử đặt style="display: none;" - Không chạy tốt vì lý do nào đó, tìm cách khác if (!doc.getElementById("naviman_app")) doc.body.insertAdjacentHTML("afterbegin", ''); } function addVersionToUrl(url, v) { return v ? url + (url.includes('?') ? '&' : '?') + 'v=' + v : url; } function findCurrentScript() { let s = doc.currentScript; if (!s) { const sc = doc.querySelectorAll('script[src*="' + startJs + '"]'); if (sc.length) s = sc[sc.length - 1]; } return s; } function getFromScript(s, attr) { if (!s) return null; const val = s.getAttribute(attr); return attr === 'src' ? (val.match(/[?&]v=([^&]+)/) || [])[1] || null : val; } function initDOM() { if (window._NAVIPLUS_APP_LOADED) return; window._NAVIPLUS_APP_LOADED = true; if (doc.body) { addNavimanApp(); } else { new MutationObserver((m, o) => { if (doc.body) { addNavimanApp(); o.disconnect(); } }).observe(doc.documentElement, { childList: true }); } } function loadJS(u, v) { // Validate URL đầu vào if (!u || typeof u !== 'string' || u.trim() === '') { console.error("loadJS: Invalid URL", u); return; } const f = addVersionToUrl(u, v); // Validate URL sau khi thêm version if (!f || typeof f !== 'string' || f.trim() === '') { console.error("loadJS: Invalid URL after addVersionToUrl", { u, v, f }); return; } _loaded.js.push(f); // Không dùng cái này vì dễ gây lỗi currentScript // ---- TỐI ƯU 1: Preload để browser ưu tiên tải ngay lập tức ---- /* const preload = doc.createElement("link"); preload.rel = "preload"; preload.as = "script"; preload.href = f; doc.head.appendChild(preload); */ // ---- TỐI ƯU 2: Tạo script ---- const script = doc.createElement("script"); script.src = f; // ---- TỐI ƯU 3: Nếu là uigen, copy attributes từ ATTRS ---- const isUigenFile = u.includes('/uigen.js') || u.includes('/uigen.min.js') || f.includes('/uigen.min.js'); const currentScript = doc.currentScript; if (isUigenFile && currentScript) { for (let i = 0; i < currentScript.attributes.length; i++) { const attr = currentScript.attributes[i]; const name = attr.name; // Bỏ qua src và async (sẽ set riêng sau) if (name !== 'src' && name !== 'async' && name !== 'onerror') { script.setAttribute(name, attr.value); } } // ---- TỐI ƯU 4: Luôn defer thay vì async cho uigen file ---- // Remove async attribute nếu có (từ script gốc hoặc đã set trước đó) script.removeAttribute('async'); script.async = false; script.defer = true; } else { // ---- TỐI ƯU 5: Luôn defer thay vì async ---- script.async = false; script.defer = true; } // ---- TỐI ƯU 6: Fallback nếu CDN chậm ---- script.onerror = () => { console.warn("Load failed, fallback:", f); var version = _NAVIPLUS.VER || "live-999"; const backup = `https://flov.naviplus.app/live/${version}/uigen.min.js`; const attrs = currentScript ? currentScript.attributes : {}; const currentEmbedId = s && s.hasAttribute('embed_id') ? String(s.getAttribute('embed_id')) : null; //const currentEmbedId = document.currentScript.getAttribute('embed_id'); // Lỗi toè loe // 2 script được coi là giống nhau nếu: // - Cùng src // - Cùng embed_id (hoặc cả hai đều không có embed_id) function isSameScript(existing) { // return false; const existingEmbedId = existing.getAttribute('embed_id'); console.log("existingEmbedId", existingEmbedId); console.log("currentEmbedId", currentEmbedId); if (currentEmbedId === null && existingEmbedId === null) return true; return existingEmbedId === currentEmbedId; } // Nếu đã có 1 script fallback với cùng src + cùng tập attr rồi thì không chèn thêm nữa const existingScripts = doc.querySelectorAll(`script[src="${backup}"]`); for (let i = 0; i < existingScripts.length; i++) { if (isSameScript(existingScripts[i])) { console.warn(">>>> Identical fallback script already exists, skip append:", backup); return; } } const s2 = doc.createElement("script"); // Bơm lại toàn bộ attrs từ _NAVIPLUS.ATTRS for (const name in attrs) { if (!Object.prototype.hasOwnProperty.call(attrs, name)) continue; if (name === 'src' || name === 'onerror') continue; s2.setAttribute(name, attrs[name]); } s2.src = backup; s2.async = false; s2.defer = true; s2.onerror = () => { console.error("Fallback also failed:", backup); }; doc.head.appendChild(s2); }; // ---- Append vào head ---- doc.head.appendChild(script); } function loadCSS(u, v) { const f = addVersionToUrl(u, v); if (doc.querySelector(`link[href="${f}"]`)) return; _loaded.css.push(f); doc.head.appendChild(Object.assign(doc.createElement("link"), { rel: "stylesheet", href: f })); } const _json = new Set(); function loadJSON(u) { if (!u || _json.has(u)) return; _loaded.json.push(u); _json.add(u); fetch(u, { cache: "force-cache" }).catch(() => {}); } function initResources(vars) { const cs = findCurrentScript(); if (!cs) return; const ver = getFromScript(cs, 'src'); // const shop = getFromScript(cs, 'shop'); const resources = Array.isArray(vars) ? vars : [vars]; resources.forEach(url => { if (!url || typeof url !== 'string') return; const isUigen = url.includes('/uigen.js') || url.includes('/uigen.css'); if (url.includes('.json')) { loadJSON(url); } else if (url.includes('.css')) { loadCSS(url, isUigen ? ver : null); } else if (url.includes('.js') || url.includes('javascript')) { loadJS(url, isUigen ? ver : null); } }); } function init(vars) { initDOM(); initResources(vars); } return { init, loadJS, loadCSS, loadJSON }; })() }; } /** Chỉnh lại _NAVIPLUS ************************************************************************/ (function() { const s = doc.currentScript; if (!s) return; // console.log( "Attribute shop: " + s.getAttribute('shop') + " | " + s.getAttribute('embed_id')); /* for (let i = 0; i < s.attributes.length; i++) { const attr = s.attributes[i]; if (attr.name !== 'src') { _NAVIPLUS.ATTRS[attr.name] = attr.value; } }*/ // Biến này bị đè bởi các script khác rồi. Nên in ra để debug. let shop = s.getAttribute('shop'); // _NAVIPLUS.ATTRS.shop; let env = s.getAttribute('env'); if (env && env != "shopify") shop = shop.replace(/^[^.]+\.(.*)$/, '$1'); if (shop) { // Với tên miền dạng domain.myshopify.com thì cần phải bỏ phần domain phụ shop = shop.replace('.myshopify.com', ''); _NAVIPLUS.SHOP = shop; _NAVIPLUS.VARS = _NAVIPLUS.VARS.map(url => typeof url === 'string' && url.includes('{shop}') ? url.replace('{shop}', shop) : url ); } })(); /** Kiểm tra lỗi Safari ****************************************************************************/ // Nó bắt toàn bộ lỗi chưa được xử lý, chỉ để debug phần lỗi trong Safari mà ko nói gì. // --> Cần phải bỏ đi sau khi fix xong lỗi trong Safari!!! /* window.addEventListener("error", e => { console.error("Global Error:", e.message, e.filename, e.lineno); }); window.addEventListener("unhandledrejection", e => { console.error("Unhandled Promise:", e.reason); }); */ /** In ra _naviplus ****************************************************************************/ if (!window._NAVIPLUS_LOGGED) { window._NAVIPLUS_LOGGED = true; console.log(_NAVIPLUS); } /** Khởi tạo Navi+ ****************************************************************************/ _NAVIPLUS.GO.init(_NAVIPLUS.VARS); /** Kiểm tra time logging *********************************************************************/ if (!window._NAVIPLUS_TIMING_LOGGED) { window._NAVIPLUS_TIMING_LOGGED = true; if (typeof Naviplus === 'undefined') { Naviplus = {}; } if (typeof Naviplus.Start === 'undefined') { Naviplus.Start = {}; } Naviplus.Start.Optimize = (function() { const timing = { start: performance.now(), uigenCss: null, uigenJs: null }; let hasLogged = false; function logTiming() { if (hasLogged) return; if (timing.uigenCss !== null && timing.uigenJs !== null) { hasLogged = true; window._NAVIPLUS_TIMING = { start: timing.start, uigenCss: timing.uigenCss, uigenJs: timing.uigenJs }; console.log(window._NAVIPLUS_TIMING); } } function waitForCSSLoaded(urlContains, callback) { const startTime = performance.now(); const timer = setInterval(() => { const links = document.querySelectorAll('link[rel="stylesheet"]'); for (const link of links) { if (link.href.includes(urlContains)) { if (link.sheet) { clearInterval(timer); const elapsed = performance.now() - startTime; callback(elapsed); return; } else { link.onload = () => { clearInterval(timer); const elapsed = performance.now() - startTime; callback(elapsed); }; return; } } } }, 10); } function waitForJSLoaded(varName, callback) { const startTime = performance.now(); const timer = setInterval(() => { if (window[varName] !== undefined) { clearInterval(timer); const elapsed = performance.now() - startTime; callback(elapsed); } }, 10); } waitForCSSLoaded("uigen", function(elapsed) { timing.uigenCss = elapsed; logTiming(); }); waitForJSLoaded("naviman_version", function(elapsed) { timing.uigenJs = elapsed; logTiming(); }); return { getTiming: function() { return timing; } }; })(); }