buriedPiont.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. // 通用 JSBridge
  2. window.JSBridge = {
  3. getOS: function () {
  4. const ua = navigator.userAgent || navigator.vendor || window.opera;
  5. if (/android/i.test(ua)) return 'Android';
  6. if (/iPad|iPhone|iPod/.test(ua) && !window.MSStream) return 'iOS';
  7. return 'Web';
  8. },
  9. getUserId: function (callback) {
  10. // Android WebView
  11. if (window.Android && typeof window.Android.getUserId === 'function') {
  12. callback(window.Android.getUserId());
  13. return;
  14. }
  15. // iOS WebView
  16. if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.getUserId) {
  17. window.webkit.messageHandlers.getUserId.postMessage(null);
  18. window.onUserIdReceived = callback; // iOS原生需回调window.onUserIdReceived(userId)
  19. return;
  20. }
  21. // Web环境(用 FingerprintJS 生成指纹)
  22. if (window.FingerprintJS) {
  23. FingerprintJS.load().then(fp => {
  24. fp.get().then(result => {
  25. callback(result.visitorId);
  26. });
  27. });
  28. } else {
  29. callback('unsupported');
  30. }
  31. }
  32. };
  33. let userId = '';
  34. JSBridge.getUserId(function (id) {
  35. userId = id;
  36. });
  37. class Tracker {
  38. constructor(options = {}) {
  39. this.baseUrl = options.baseUrl || '';
  40. this.timer = null;
  41. this.startTime = 0;
  42. this.heartbeatInterval = options.heartbeatInterval || 30000; // 默认30秒发送一次心跳
  43. this.visitCookieTimeout = options.visitCookieTimeout || 1; // 默认访问次数cookie有效期1分钟
  44. this.maxVisitCount = options.maxVisitCount || 5; // 默认最大访问次数5次
  45. this.blockCookieTimeout = options.blockCookieTimeout || 180; // 默认封锁cookie有效期180分钟
  46. this.myEventSend = options.myEventSend || (() => {}); // 是否开启自定义事件上报
  47. this.beforeDestroy = options.beforeDestroy || (() => {}); // 销毁前的回调函数
  48. }
  49. init() {
  50. const visitCookieName = 'userVisitCount';
  51. const blockCookieName = 'userBlocked';
  52. const isBlocked = getCookie(blockCookieName);
  53. const doneFN = () => {
  54. this.startTime = Date.now();
  55. this.bindEvents();
  56. this.startHeartbeat();
  57. }
  58. if (isBlocked) {
  59. // restoreBlock();
  60. return;
  61. }
  62. let visitCount = getCookie(visitCookieName);
  63. if (visitCount) {
  64. visitCount = parseInt(visitCount, 10);
  65. if (visitCount >= this.maxVisitCount) {
  66. setCookie(blockCookieName, 'true', this.blockCookieTimeout); // 封锁用户 180 分钟
  67. deleteCookie(visitCookieName);
  68. // restoreBlock();
  69. } else {
  70. visitCount += 1;
  71. setCookie(visitCookieName, visitCount, this.visitCookieTimeout); // 每次访问 +1,有效期 1 分钟
  72. doneFN();
  73. }
  74. } else {
  75. setCookie(visitCookieName, '1', this.visitCookieTimeout); // 首次访问,设置访问次数为 1
  76. doneFN();
  77. }
  78. }
  79. bindEvents() {
  80. window.addEventListener('beforeunload', this.handleBeforeUnload);
  81. window.addEventListener('click', this.handleClickEvent);
  82. this.myEventSend(this.sendData.bind(this)); // 绑定自定义事件上报
  83. }
  84. startHeartbeat() {
  85. this.timer = setInterval(() => {
  86. console.log(userId);
  87. this.sendData({
  88. eventType: 'heartbeat',
  89. duration: Math.round((Date.now() - this.startTime) / 1000),
  90. }, { heartbeatSeconds: this.heartbeatInterval, userId: userId });
  91. }, this.heartbeatInterval);
  92. }
  93. handleBeforeUnload = (e) => {
  94. const data = {
  95. eventType: 'page_close',
  96. duration: Math.round((Date.now() - this.startTime) / 1000),
  97. };
  98. const payload = JSON.stringify(data);
  99. if (navigator.sendBeacon) {
  100. navigator.sendBeacon(this.baseUrl, payload);
  101. } else {
  102. this.sendData(payload, { heartbeatSeconds: this.heartbeatInterval, userId: userId });
  103. }
  104. };
  105. handleClickEvent = (event) => {
  106. const target = event.target;
  107. if (target.matches('[data-track]')) {
  108. const trackInfo = {
  109. eventType: 'button_click',
  110. elementId: target.id || '无ID',
  111. elementClass: target.className || '无class',
  112. text: target.innerText || target.textContent || '',
  113. timestamp: new Date().toISOString(),
  114. };
  115. this.sendData(trackInfo, { heartbeatSeconds: this.heartbeatInterval, userId: userId });
  116. }
  117. };
  118. sendData(data, headers = {}) {
  119. console.log('上报埋点数据:', data);
  120. fetch(`${this.baseUrl}`, { method: 'POST', headers: { 'Content-Type': 'application/json' ,...headers}, body: JSON.stringify(data) });
  121. }
  122. destroy() {
  123. window.removeEventListener('beforeunload', this.handleBeforeUnload);
  124. window.removeEventListener('click', this.handleClickEvent);
  125. this.beforeDestroy();
  126. if (this.timer) {
  127. clearInterval(this.timer);
  128. }
  129. this.sendData({
  130. eventType: 'tracker_destroyed',
  131. duration: Math.round((Date.now() - this.startTime) / 1000),
  132. }, { heartbeatSeconds: this.heartbeatInterval, userId: userId });
  133. }
  134. }
  135. // 使用示例:
  136. // const tracker = new Tracker({ baseUrl: 'https://your-tracking-api.com' });
  137. // tracker.init();
  138. // 暴露给全局或者作为模块导出
  139. window.Tracker = Tracker;