// index.js import FingerprintJS from '@fingerprintjs/fingerprintjs'; (function (factory) { if (typeof define === 'function' && define.amd) { // AMD define([], factory); } else if (typeof module === 'object' && module.exports) { // CommonJS module.exports = factory(); } else { // 浏览器全局变量 var globalObj = typeof globalThis !== 'undefined' ? globalThis : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : this; globalObj.UtmTracker = factory(); } }(function () { 'use strict'; // 工具函数 function getUrlParam(name) { var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)'); var r = window.location.search.substr(1).match(reg); if (r != null) return decodeURIComponent(r[2]); return null; } // 获取浏览器信息 function getBrowserInfo() { var ua = navigator.userAgent; var browser = 'Unknown'; var isMobile = /Mobile|Android|iPhone|iPad|iPod|HarmonyOS|HMS/i.test(ua); var osType = 'Unknown'; var osVersion = 'Unknown'; // 系统类型和版本号判断 if (/iPhone|iPad|iPod/i.test(ua)) { osType = 'iOS'; var iosVersionMatch = ua.match(/OS (\d+)[_.](\d+)?([_.](\d+))?/i); if (iosVersionMatch) { osVersion = iosVersionMatch[1]; if (iosVersionMatch[2]) osVersion += '.' + iosVersionMatch[2]; if (iosVersionMatch[4]) osVersion += '.' + iosVersionMatch[4]; } } else if (/Android/i.test(ua)) { osType = 'Android'; var androidVersionMatch = ua.match(/Android ([\d.]+)/i); if (androidVersionMatch) { osVersion = androidVersionMatch[1]; } } else if (/HarmonyOS|HMS/i.test(ua)) { osType = 'HarmonyOS'; var harmonyVersionMatch = ua.match(/HarmonyOS[\s/]?([\d.]+)/i) || ua.match(/HMSCore[\s/]?([\d.]+)/i); if (harmonyVersionMatch) { osVersion = harmonyVersionMatch[1]; } } // 浏览器类型判断 if (/Edg/i.test(ua)) { browser = 'Edge'; } else if (/HuaweiBrowser/i.test(ua) || /HMS/i.test(ua)) { browser = 'HuaweiBrowser'; } else if (/Chrome/i.test(ua)) { browser = 'Chrome'; } else if (/Firefox/i.test(ua)) { browser = 'Firefox'; } else if (/Safari/i.test(ua) && !/Chrome/i.test(ua) && !/Edg/i.test(ua)) { browser = 'Safari'; } else if (/MSIE|Trident/i.test(ua)) { browser = 'IE'; } // 特殊浏览器环境检测 if (/MicroMessenger/i.test(ua)) { browser = 'WeChat'; } else if (/QQBrowser/i.test(ua)) { browser = 'QQBrowser'; } else if (/UCBrowser/i.test(ua)) { browser = 'UCBrowser'; } else if (/Telegram/i.test(ua)) { browser = 'Telegram'; } return { isMobile: isMobile, browser: browser, userAgent: ua, osType: osType, osVersion: osVersion }; } //获取上一级url function getPreviousUrl() { let referrer = document.referrer; if (referrer) { return referrer; } // else if (window.history.length > 1) { // // 如果有历史记录 // window.history.back(); // return window.location.href; // } else { // // 如果没有历史记录 // return null; // } } // 主类 function UtmTracker(config) { this.config = Object.assign({ reportUrl: '', // 上报地址 autoSend: true, // 是否自动上报 method: 'POST', // 请求方式 headers: { 'Content-Type': 'application/json' }, // 请求头 extra: {}, // 额外参数 }, config || {}); if (this.config.autoSend && this.config.reportUrl) { this.send(); } } // 集成 FingerprintJS,兼容npm和浏览器全局 console.log('FingerprintJSLib:', FingerprintJS); // 获取UTM参数(同步) UtmTracker.prototype.getParams = function () { const browser = getBrowserInfo() let fingerprint = ''; try { fingerprint = localStorage.getItem('fingerprint_id') || ''; } catch (e) { } const params = { utmSource: getUrlParam('utm_source') || '', utmMedium: getUrlParam('utm_medium') || '', utmCampaign: getUrlParam('utm_campaign') || '', utmTerm: getUrlParam('utm_term') || '', utmContent: getUrlParam('utm_content') || '', referrer: getPreviousUrl() || '', fingerprint: fingerprint, timestamp: new Date().toISOString(), url: window.location.href, domain: window.location.hostname, isMobile: browser.isMobile, browser: browser.browser, userAgent: browser.userAgent, osType: browser.osType, osVersion: browser.osVersion }; // 合并额外参数 let result = params; if (this.config && this.config.extra && typeof this.config.extra === 'object') { result = Object.assign({}, params, this.config.extra); } if (typeof this.config.onParams === 'function') { this.config.onParams(result); } return result; }; // 获取指纹ID(本地优先,无则生成并存储) async function getfingerprint() { let fingerprint = ''; try { fingerprint = localStorage.getItem('fingerprint_id') || ''; } catch (e) { } if (fingerprint) { return fingerprint; } else if (FingerprintJS && typeof FingerprintJS.load === 'function') { try { const fp = await FingerprintJS.load(); const result = await fp.get(); fingerprint = result.visitorId; try { localStorage.setItem('fingerprint_id', fingerprint); } catch (e) { } return fingerprint; } catch (e) { return ''; } } else { return ''; } } // 获取UTM参数(异步,带指纹,本地唯一) UtmTracker.prototype.getParamsAsync = async function () { const params = this.getParams(); params.fingerprint = await getfingerprint(); return params; }; // 发送请求(自动带指纹,异步,指纹本地唯一) UtmTracker.prototype.send = async function (data) { const cfg = this.config || {}; let payload = data; if (!payload) { payload = await this.getParamsAsync(); } else if (!payload.fingerprint) { payload.fingerprint = await getfingerprint(); } if (!cfg.reportUrl) return; fetch(cfg.reportUrl, { method: 'POST', headers: cfg.headers || { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }) .then(res => { // 假设res为Response对象,需要解析json if (typeof res.json === 'function') { res.json().then(data => { if (data.code == 1) return if (data.data && data.data.downloadUrl) { if (data.data.type === 'link') { // 倒计时弹窗 let countdown = 3; const countdownMask = document.createElement('div'); countdownMask.style.position = 'fixed'; countdownMask.style.top = 0; countdownMask.style.left = 0; countdownMask.style.width = '100vw'; countdownMask.style.height = '100vh'; countdownMask.style.background = 'rgba(0,0,0,0.5)'; countdownMask.style.zIndex = 9999; countdownMask.style.display = 'flex'; countdownMask.style.alignItems = 'center'; countdownMask.style.justifyContent = 'center'; const countdownModal = document.createElement('div'); countdownModal.style.background = '#fff'; countdownModal.style.borderRadius = '8px'; countdownModal.style.padding = '32px 48px'; countdownModal.style.fontSize = '20px'; countdownModal.style.boxShadow = '0 2px 16px rgba(0,0,0,0.2)'; countdownModal.style.display = 'flex'; countdownModal.style.flexDirection = 'column'; countdownModal.style.alignItems = 'center'; const text = document.createElement('div'); text.innerText = `即将跳转,${countdown}秒...`; countdownModal.appendChild(text); countdownMask.appendChild(countdownModal); document.body.appendChild(countdownMask); const timer = setInterval(() => { countdown--; text.innerText = `即将跳转,${countdown}秒...`; if (countdown <= 0) { clearInterval(timer); document.body.removeChild(countdownMask); // 跳转 console.log(data.data.downloadUrl); window.location.href = data.data.downloadUrl return } }, 1000); return } // 创建遮罩 const mask = document.createElement('div'); mask.style.position = 'fixed'; mask.style.top = 0; mask.style.left = 0; mask.style.width = '100vw'; mask.style.height = '100vh'; mask.style.background = 'rgba(0,0,0,0.5)'; mask.style.zIndex = 9999; mask.style.display = 'flex'; mask.style.alignItems = 'center'; mask.style.justifyContent = 'center'; // 弹窗容器 const modal = document.createElement('div'); modal.style.background = '#fff'; modal.style.borderRadius = '8px'; modal.style.padding = '24px'; modal.style.maxWidth = '90vw'; modal.style.maxHeight = '90vh'; modal.style.boxShadow = '0 2px 16px rgba(0,0,0,0.2)'; modal.style.position = 'relative'; modal.style.display = 'flex'; modal.style.flexDirection = 'column'; modal.style.alignItems = 'center'; // 关闭按钮 const closeBtn = document.createElement('button'); closeBtn.innerText = '关闭'; closeBtn.style.position = 'absolute'; closeBtn.style.top = '8px'; closeBtn.style.right = '8px'; closeBtn.style.background = '#f44336'; closeBtn.style.color = '#fff'; closeBtn.style.border = 'none'; closeBtn.style.borderRadius = '4px'; closeBtn.style.padding = '4px 12px'; closeBtn.style.cursor = 'pointer'; closeBtn.onclick = function () { document.body.removeChild(mask); }; modal.appendChild(closeBtn); // 内容区 let contentEl; if (data.data.type === 'video') { contentEl = document.createElement('video'); contentEl.src = data.data.downloadUrl; contentEl.controls = true; contentEl.autoplay = true; contentEl.style.maxWidth = '80vw'; contentEl.style.maxHeight = '70vh'; } else if (data.data.type === 'audio') { contentEl = document.createElement('audio'); contentEl.src = data.data.downloadUrl; contentEl.controls = true; contentEl.autoplay = true; contentEl.style.width = '100%'; } else if (data.data.type === 'image') { contentEl = document.createElement('img'); contentEl.src = data.data.downloadUrl; contentEl.style.maxWidth = '80vw'; contentEl.style.maxHeight = '70vh'; } else { contentEl = document.createElement('a'); contentEl.href = data.data.downloadUrl; contentEl.innerText = '下载文件'; contentEl.target = '_blank'; } modal.appendChild(contentEl); mask.appendChild(modal); document.body.appendChild(mask); } }); } }) .catch(err => { console.log(err); }); }; // 静态方法:快速获取(无需实例化) UtmTracker.get = async function (config) { const tracker = new UtmTracker(config); return await tracker.getParamsAsync(); }; // 新增静态send方法 UtmTracker.send = async function (data, config) { const tracker = new UtmTracker(config); return await tracker.send(data); }; // 首次进入自动生成指纹ID(如本地无) (async function ensurefingerprint() { let fingerprint = ''; try { fingerprint = localStorage.getItem('fingerprint_id') || ''; } catch (e) { } if (!fingerprint && FingerprintJS && typeof FingerprintJS.load === 'function') { try { const fp = await FingerprintJS.load(); const result = await fp.get(); fingerprint = result.visitorId; try { localStorage.setItem('fingerprint_id', fingerprint); } catch (e) { } } catch (e) { } } })(); return UtmTracker; }));