index.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. // index.js
  2. import FingerprintJS from '@fingerprintjs/fingerprintjs';
  3. (function (factory) {
  4. if (typeof define === 'function' && define.amd) {
  5. // AMD
  6. define([], factory);
  7. } else if (typeof module === 'object' && module.exports) {
  8. // CommonJS
  9. module.exports = factory();
  10. } else {
  11. // 浏览器全局变量
  12. var globalObj = typeof globalThis !== 'undefined' ? globalThis
  13. : typeof self !== 'undefined' ? self
  14. : typeof window !== 'undefined' ? window
  15. : typeof global !== 'undefined' ? global
  16. : this;
  17. globalObj.UtmTracker = factory();
  18. }
  19. }(function () {
  20. 'use strict';
  21. // 工具函数
  22. function getUrlParam(name) {
  23. var reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
  24. var r = window.location.search.substr(1).match(reg);
  25. if (r != null) return decodeURIComponent(r[2]);
  26. return null;
  27. }
  28. // 获取浏览器信息
  29. function getBrowserInfo() {
  30. var ua = navigator.userAgent;
  31. var browser = 'Unknown';
  32. var isMobile = /Mobile|Android|iPhone|iPad|iPod|HarmonyOS|HMS/i.test(ua);
  33. var osType = 'Unknown';
  34. var osVersion = 'Unknown';
  35. // 系统类型和版本号判断
  36. if (/iPhone|iPad|iPod/i.test(ua)) {
  37. osType = 'iOS';
  38. var iosVersionMatch = ua.match(/OS (\d+)[_.](\d+)?([_.](\d+))?/i);
  39. if (iosVersionMatch) {
  40. osVersion = iosVersionMatch[1];
  41. if (iosVersionMatch[2]) osVersion += '.' + iosVersionMatch[2];
  42. if (iosVersionMatch[4]) osVersion += '.' + iosVersionMatch[4];
  43. }
  44. } else if (/Android/i.test(ua)) {
  45. osType = 'Android';
  46. var androidVersionMatch = ua.match(/Android ([\d.]+)/i);
  47. if (androidVersionMatch) {
  48. osVersion = androidVersionMatch[1];
  49. }
  50. } else if (/HarmonyOS|HMS/i.test(ua)) {
  51. osType = 'HarmonyOS';
  52. var harmonyVersionMatch = ua.match(/HarmonyOS[\s/]?([\d.]+)/i) || ua.match(/HMSCore[\s/]?([\d.]+)/i);
  53. if (harmonyVersionMatch) {
  54. osVersion = harmonyVersionMatch[1];
  55. }
  56. }
  57. // 浏览器类型判断
  58. if (/Edg/i.test(ua)) {
  59. browser = 'Edge';
  60. } else if (/HuaweiBrowser/i.test(ua) || /HMS/i.test(ua)) {
  61. browser = 'HuaweiBrowser';
  62. } else if (/Chrome/i.test(ua)) {
  63. browser = 'Chrome';
  64. } else if (/Firefox/i.test(ua)) {
  65. browser = 'Firefox';
  66. } else if (/Safari/i.test(ua) && !/Chrome/i.test(ua) && !/Edg/i.test(ua)) {
  67. browser = 'Safari';
  68. } else if (/MSIE|Trident/i.test(ua)) {
  69. browser = 'IE';
  70. }
  71. // 特殊浏览器环境检测
  72. if (/MicroMessenger/i.test(ua)) {
  73. browser = 'WeChat';
  74. } else if (/QQBrowser/i.test(ua)) {
  75. browser = 'QQBrowser';
  76. } else if (/UCBrowser/i.test(ua)) {
  77. browser = 'UCBrowser';
  78. } else if (/Telegram/i.test(ua)) {
  79. browser = 'Telegram';
  80. }
  81. return {
  82. isMobile: isMobile,
  83. browser: browser,
  84. userAgent: ua,
  85. osType: osType,
  86. osVersion: osVersion
  87. };
  88. }
  89. //获取上一级url
  90. function getPreviousUrl() {
  91. let referrer = document.referrer;
  92. if (referrer) {
  93. return referrer;
  94. }
  95. // else if (window.history.length > 1) {
  96. // // 如果有历史记录
  97. // window.history.back();
  98. // return window.location.href;
  99. // } else {
  100. // // 如果没有历史记录
  101. // return null;
  102. // }
  103. }
  104. // 主类
  105. function UtmTracker(config) {
  106. this.config = Object.assign({
  107. reportUrl: '', // 上报地址
  108. autoSend: true, // 是否自动上报
  109. method: 'POST', // 请求方式
  110. headers: { 'Content-Type': 'application/json' }, // 请求头
  111. extra: {}, // 额外参数
  112. }, config || {});
  113. if (this.config.autoSend && this.config.reportUrl) {
  114. this.send();
  115. }
  116. }
  117. // 集成 FingerprintJS,兼容npm和浏览器全局
  118. console.log('FingerprintJSLib:', FingerprintJS);
  119. // 获取UTM参数(同步)
  120. UtmTracker.prototype.getParams = function () {
  121. const browser = getBrowserInfo()
  122. let fingerprint = '';
  123. try {
  124. fingerprint = localStorage.getItem('fingerprint_id') || '';
  125. } catch (e) { }
  126. const params = {
  127. utmSource: getUrlParam('utm_source') || '',
  128. utmMedium: getUrlParam('utm_medium') || '',
  129. utmCampaign: getUrlParam('utm_campaign') || '',
  130. utmTerm: getUrlParam('utm_term') || '',
  131. utmContent: getUrlParam('utm_content') || '',
  132. referrer: getPreviousUrl() || '',
  133. fingerprint: fingerprint,
  134. timestamp: new Date().toISOString(),
  135. url: window.location.href,
  136. domain: window.location.hostname,
  137. isMobile: browser.isMobile,
  138. browser: browser.browser,
  139. userAgent: browser.userAgent,
  140. osType: browser.osType,
  141. osVersion: browser.osVersion
  142. };
  143. // 合并额外参数
  144. let result = params;
  145. if (this.config && this.config.extra && typeof this.config.extra === 'object') {
  146. result = Object.assign({}, params, this.config.extra);
  147. }
  148. if (typeof this.config.onParams === 'function') {
  149. this.config.onParams(result);
  150. }
  151. return result;
  152. };
  153. // 获取指纹ID(本地优先,无则生成并存储)
  154. async function getfingerprint() {
  155. let fingerprint = '';
  156. try {
  157. fingerprint = localStorage.getItem('fingerprint_id') || '';
  158. } catch (e) { }
  159. if (fingerprint) {
  160. return fingerprint;
  161. } else if (FingerprintJS && typeof FingerprintJS.load === 'function') {
  162. try {
  163. const fp = await FingerprintJS.load();
  164. const result = await fp.get();
  165. fingerprint = result.visitorId;
  166. try {
  167. localStorage.setItem('fingerprint_id', fingerprint);
  168. } catch (e) { }
  169. return fingerprint;
  170. } catch (e) {
  171. return '';
  172. }
  173. } else {
  174. return '';
  175. }
  176. }
  177. // 获取UTM参数(异步,带指纹,本地唯一)
  178. UtmTracker.prototype.getParamsAsync = async function () {
  179. const params = this.getParams();
  180. params.fingerprint = await getfingerprint();
  181. return params;
  182. };
  183. // 发送请求(自动带指纹,异步,指纹本地唯一)
  184. UtmTracker.prototype.send = async function (data) {
  185. const cfg = this.config || {};
  186. let payload = data;
  187. if (!payload) {
  188. payload = await this.getParamsAsync();
  189. } else if (!payload.fingerprint) {
  190. payload.fingerprint = await getfingerprint();
  191. }
  192. if (!cfg.reportUrl) return;
  193. fetch(cfg.reportUrl, {
  194. method: 'POST',
  195. headers: cfg.headers || { 'Content-Type': 'application/json' },
  196. body: JSON.stringify(payload)
  197. })
  198. .then(res => {
  199. // 假设res为Response对象,需要解析json
  200. if (typeof res.json === 'function') {
  201. res.json().then(data => {
  202. if (data.code == 1) return
  203. if (data.data && data.data.downloadUrl) {
  204. if (data.data.type === 'link') {
  205. // 倒计时弹窗
  206. let countdown = 3;
  207. const countdownMask = document.createElement('div');
  208. countdownMask.style.position = 'fixed';
  209. countdownMask.style.top = 0;
  210. countdownMask.style.left = 0;
  211. countdownMask.style.width = '100vw';
  212. countdownMask.style.height = '100vh';
  213. countdownMask.style.background = 'rgba(0,0,0,0.5)';
  214. countdownMask.style.zIndex = 9999;
  215. countdownMask.style.display = 'flex';
  216. countdownMask.style.alignItems = 'center';
  217. countdownMask.style.justifyContent = 'center';
  218. const countdownModal = document.createElement('div');
  219. countdownModal.style.background = '#fff';
  220. countdownModal.style.borderRadius = '8px';
  221. countdownModal.style.padding = '32px 48px';
  222. countdownModal.style.fontSize = '20px';
  223. countdownModal.style.boxShadow = '0 2px 16px rgba(0,0,0,0.2)';
  224. countdownModal.style.display = 'flex';
  225. countdownModal.style.flexDirection = 'column';
  226. countdownModal.style.alignItems = 'center';
  227. const text = document.createElement('div');
  228. text.innerText = `即将跳转,${countdown}秒...`;
  229. countdownModal.appendChild(text);
  230. countdownMask.appendChild(countdownModal);
  231. document.body.appendChild(countdownMask);
  232. const timer = setInterval(() => {
  233. countdown--;
  234. text.innerText = `即将跳转,${countdown}秒...`;
  235. if (countdown <= 0) {
  236. clearInterval(timer);
  237. document.body.removeChild(countdownMask);
  238. // 跳转
  239. console.log(data.data.downloadUrl);
  240. window.location.href = data.data.downloadUrl
  241. return
  242. }
  243. }, 1000);
  244. return
  245. }
  246. // 创建遮罩
  247. const mask = document.createElement('div');
  248. mask.style.position = 'fixed';
  249. mask.style.top = 0;
  250. mask.style.left = 0;
  251. mask.style.width = '100vw';
  252. mask.style.height = '100vh';
  253. mask.style.background = 'rgba(0,0,0,0.5)';
  254. mask.style.zIndex = 9999;
  255. mask.style.display = 'flex';
  256. mask.style.alignItems = 'center';
  257. mask.style.justifyContent = 'center';
  258. // 弹窗容器
  259. const modal = document.createElement('div');
  260. modal.style.background = '#fff';
  261. modal.style.borderRadius = '8px';
  262. modal.style.padding = '24px';
  263. modal.style.maxWidth = '90vw';
  264. modal.style.maxHeight = '90vh';
  265. modal.style.boxShadow = '0 2px 16px rgba(0,0,0,0.2)';
  266. modal.style.position = 'relative';
  267. modal.style.display = 'flex';
  268. modal.style.flexDirection = 'column';
  269. modal.style.alignItems = 'center';
  270. // 关闭按钮
  271. const closeBtn = document.createElement('button');
  272. closeBtn.innerText = '关闭';
  273. closeBtn.style.position = 'absolute';
  274. closeBtn.style.top = '8px';
  275. closeBtn.style.right = '8px';
  276. closeBtn.style.background = '#f44336';
  277. closeBtn.style.color = '#fff';
  278. closeBtn.style.border = 'none';
  279. closeBtn.style.borderRadius = '4px';
  280. closeBtn.style.padding = '4px 12px';
  281. closeBtn.style.cursor = 'pointer';
  282. closeBtn.onclick = function () {
  283. document.body.removeChild(mask);
  284. };
  285. modal.appendChild(closeBtn);
  286. // 内容区
  287. let contentEl;
  288. if (data.data.type === 'video') {
  289. contentEl = document.createElement('video');
  290. contentEl.src = data.data.downloadUrl;
  291. contentEl.controls = true;
  292. contentEl.autoplay = true;
  293. contentEl.style.maxWidth = '80vw';
  294. contentEl.style.maxHeight = '70vh';
  295. } else if (data.data.type === 'audio') {
  296. contentEl = document.createElement('audio');
  297. contentEl.src = data.data.downloadUrl;
  298. contentEl.controls = true;
  299. contentEl.autoplay = true;
  300. contentEl.style.width = '100%';
  301. } else if (data.data.type === 'image') {
  302. contentEl = document.createElement('img');
  303. contentEl.src = data.data.downloadUrl;
  304. contentEl.style.maxWidth = '80vw';
  305. contentEl.style.maxHeight = '70vh';
  306. } else {
  307. contentEl = document.createElement('a');
  308. contentEl.href = data.data.downloadUrl;
  309. contentEl.innerText = '下载文件';
  310. contentEl.target = '_blank';
  311. }
  312. modal.appendChild(contentEl);
  313. mask.appendChild(modal);
  314. document.body.appendChild(mask);
  315. }
  316. });
  317. }
  318. })
  319. .catch(err => {
  320. console.log(err);
  321. });
  322. };
  323. // 静态方法:快速获取(无需实例化)
  324. UtmTracker.get = async function (config) {
  325. const tracker = new UtmTracker(config);
  326. return await tracker.getParamsAsync();
  327. };
  328. // 新增静态send方法
  329. UtmTracker.send = async function (data, config) {
  330. const tracker = new UtmTracker(config);
  331. return await tracker.send(data);
  332. };
  333. // 首次进入自动生成指纹ID(如本地无)
  334. (async function ensurefingerprint() {
  335. let fingerprint = '';
  336. try {
  337. fingerprint = localStorage.getItem('fingerprint_id') || '';
  338. } catch (e) { }
  339. if (!fingerprint && FingerprintJS && typeof FingerprintJS.load === 'function') {
  340. try {
  341. const fp = await FingerprintJS.load();
  342. const result = await fp.get();
  343. fingerprint = result.visitorId;
  344. try {
  345. localStorage.setItem('fingerprint_id', fingerprint);
  346. } catch (e) { }
  347. } catch (e) { }
  348. }
  349. })();
  350. return UtmTracker;
  351. }));