@fle-sdk/event-tracking-web 1.2.5 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.esm.js CHANGED
@@ -865,11 +865,7 @@ function () {
865
865
 
866
866
  fingerprint.canvas = _this.getCanvasFingerprint(); // 8. 音频上下文指纹
867
867
 
868
- fingerprint.audio = _this.getAudioFingerprint(); // 9. 字体检测
869
-
870
- fingerprint.fonts = _this.getFontFingerprint(); // 10. 插件信息
871
-
872
- fingerprint.plugins = _this.getPluginsFingerprint(); // 11. 存储检测
868
+ fingerprint.audio = _this.getAudioFingerprint(); // 11. 存储检测
873
869
 
874
870
  fingerprint.localStorage = _this.hasLocalStorage();
875
871
  fingerprint.sessionStorage = _this.hasSessionStorage();
@@ -877,9 +873,7 @@ function () {
877
873
 
878
874
  fingerprint.hardwareConcurrency = navigator.hardwareConcurrency;
879
875
  fingerprint.deviceMemory = navigator.deviceMemory;
880
- fingerprint.maxTouchPoints = navigator.maxTouchPoints; // 13. 连接信息
881
-
882
- fingerprint.connection = _this.getConnectionFingerprint();
876
+ fingerprint.maxTouchPoints = navigator.maxTouchPoints;
883
877
  return fingerprint;
884
878
  };
885
879
  /**
@@ -951,75 +945,6 @@ function () {
951
945
  return "error";
952
946
  }
953
947
  };
954
- /**
955
- * 获取字体指纹
956
- * @returns 字体指纹字符串
957
- */
958
-
959
-
960
- this.getFontFingerprint = function () {
961
- try {
962
- var baseFonts_1 = ["monospace", "sans-serif", "serif"];
963
- var testString_1 = "mmmmmmmmmmlli";
964
- var testSize_1 = "72px";
965
- var canvas = document.createElement("canvas");
966
- var ctx_1 = canvas.getContext("2d");
967
- if (!ctx_1) return "not-supported";
968
- var detectedFonts_1 = []; // 测试字体列表
969
-
970
- var fonts = ["Arial", "Arial Black", "Comic Sans MS", "Courier New", "Georgia", "Helvetica", "Impact", "Times New Roman", "Trebuchet MS", "Verdana"]; // 获取基准宽度
971
-
972
- var baseWidths_1 = {};
973
- baseFonts_1.forEach(function (font) {
974
- ctx_1.font = testSize_1 + " " + font;
975
- baseWidths_1[font] = ctx_1.measureText(testString_1).width;
976
- }); // 测试每个字体
977
-
978
- fonts.forEach(function (font) {
979
- var detected = false;
980
- baseFonts_1.forEach(function (baseFont) {
981
- ctx_1.font = testSize_1 + " '" + font + "', " + baseFont;
982
- var width = ctx_1.measureText(testString_1).width;
983
-
984
- if (width !== baseWidths_1[baseFont]) {
985
- detected = true;
986
- }
987
- });
988
-
989
- if (detected) {
990
- detectedFonts_1.push(font);
991
- }
992
- });
993
- return detectedFonts_1.join(",");
994
- } catch (e) {
995
- return "error";
996
- }
997
- };
998
- /**
999
- * 获取插件指纹
1000
- * @returns 插件指纹字符串
1001
- */
1002
-
1003
-
1004
- this.getPluginsFingerprint = function () {
1005
- try {
1006
- var plugins = [];
1007
-
1008
- if (navigator.plugins) {
1009
- for (var i = 0; i < navigator.plugins.length; i++) {
1010
- var plugin = navigator.plugins[i];
1011
-
1012
- if (plugin) {
1013
- plugins.push(plugin.name + "|" + plugin.description + "|" + plugin.filename);
1014
- }
1015
- }
1016
- }
1017
-
1018
- return plugins.join(";");
1019
- } catch (e) {
1020
- return "error";
1021
- }
1022
- };
1023
948
  /**
1024
949
  * 检测localStorage支持
1025
950
  * @returns 是否支持localStorage
@@ -1061,22 +986,6 @@ function () {
1061
986
  this.hasIndexedDB = function () {
1062
987
  return "indexedDB" in window && indexedDB !== null;
1063
988
  };
1064
- /**
1065
- * 获取网络连接指纹
1066
- * @returns 网络连接指纹字符串
1067
- */
1068
-
1069
-
1070
- this.getConnectionFingerprint = function () {
1071
- try {
1072
- var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
1073
- if (!connection) return "not-supported"; // 只使用稳定的 effectiveType,不使用 downlink 和 rtt(会随网络状态变化导致指纹不一致)
1074
-
1075
- return connection.effectiveType || "unknown";
1076
- } catch (e) {
1077
- return "error";
1078
- }
1079
- };
1080
989
  /**
1081
990
  * 将指纹信息哈希为唯一ID
1082
991
  * @param fingerprint 指纹信息
@@ -1230,1505 +1139,1579 @@ function () {
1230
1139
  return WebTrackingTools;
1231
1140
  }();
1232
1141
 
1233
- var WebTracking =
1142
+ var ExposureTracker =
1234
1143
  /** @class */
1235
- function (_super) {
1236
- __extends(WebTracking, _super);
1237
-
1238
- function WebTracking() {
1239
- var _this = _super.call(this) || this; // 批量发送定时器
1240
-
1144
+ function () {
1145
+ function ExposureTracker(config, callbacks) {
1146
+ this.observer = null;
1147
+ this.mutationObserver = null;
1148
+ this.elementsMap = new Map();
1149
+ this.config = config;
1150
+ this.callbacks = callbacks;
1151
+ }
1241
1152
 
1242
- _this.batchTimer = null; // LocalStorage 存储 key
1153
+ ExposureTracker.prototype.updateConfig = function (config) {
1154
+ this.config = __assign(__assign({}, this.config), config);
1155
+ };
1243
1156
 
1244
- _this.BATCH_QUEUE_STORAGE_KEY = "web_tracking_batch_queue"; // 是否使用自定义 pageKey(如果为 true,路由变化时不会自动更新 pageKey)
1157
+ ExposureTracker.prototype.init = function () {
1158
+ var _this = this;
1245
1159
 
1246
- _this.useCustomPageKey = false; // 页面卸载监听器是否已设置
1160
+ if (!this.config.autoTrackExposure) {
1161
+ return;
1162
+ }
1247
1163
 
1248
- _this.isUnloadListenerSetup = false; // 定时上报页面停留时长的定时器
1164
+ if (!("IntersectionObserver" in window)) {
1165
+ if (this.config.showLog) {
1166
+ this.callbacks.printLog("当前浏览器不支持 IntersectionObserver,无法启用曝光埋点");
1167
+ }
1249
1168
 
1250
- _this.pageDurationTimer = null; // LocalStorage 存储 key(待发送请求)
1169
+ return;
1170
+ }
1251
1171
 
1252
- _this.PENDING_REQUESTS_STORAGE_KEY = "web_tracking_pending_requests"; // 待发送请求队列最大大小(默认值,可通过配置覆盖)
1172
+ this.observer = new IntersectionObserver(function (entries) {
1173
+ entries.forEach(function (entry) {
1174
+ var element = entry.target;
1253
1175
 
1254
- _this.DEFAULT_PENDING_REQUESTS_MAX_SIZE = 50; // LocalStorage 最大大小限制(4MB)
1176
+ var elementInfo = _this.elementsMap.get(element);
1255
1177
 
1256
- _this.MAX_STORAGE_SIZE = 4 * 1024 * 1024; // IntersectionObserver 实例
1178
+ if (!elementInfo) {
1179
+ return;
1180
+ }
1257
1181
 
1258
- _this.exposureObserver = null; // 曝光元素映射
1182
+ var exposureTime = _this.config.exposureTime || 500;
1259
1183
 
1260
- _this.exposureElementsMap = new Map(); // MutationObserver 实例(用于监听动态添加的元素)
1184
+ if (entry.isIntersecting) {
1185
+ elementInfo.isVisible = true;
1186
+ elementInfo.visibleStartTime = _this.callbacks.getTimeStamp();
1261
1187
 
1262
- _this.mutationObserver = null; // 用户信息
1188
+ if (elementInfo.exposureTimer) {
1189
+ clearTimeout(elementInfo.exposureTimer);
1190
+ }
1263
1191
 
1264
- _this.userInfo = null; // 当前路由
1192
+ elementInfo.exposureTimer = window.setTimeout(function () {
1193
+ if (elementInfo.isVisible) {
1194
+ var exposureScreenIndex = _this.calculateexposureScreenIndex(element);
1265
1195
 
1266
- _this.currentUrl = ""; // 页面唯一标识,取当前路由 window.location.pathname.replace(/\//g, '_').substr(1)
1196
+ _this.callbacks.reportExposure(element, exposureScreenIndex);
1197
+ }
1198
+ }, exposureTime);
1199
+ } else {
1200
+ elementInfo.isVisible = false;
1267
1201
 
1268
- _this.pageKey = ""; // 设备唯一标识
1202
+ if (elementInfo.exposureTimer) {
1203
+ clearTimeout(elementInfo.exposureTimer);
1204
+ elementInfo.exposureTimer = null;
1205
+ }
1206
+ }
1207
+ });
1208
+ }, {
1209
+ threshold: this.config.exposureThreshold || 0.5
1210
+ });
1211
+ this.observeExposureElements();
1212
+ this.initMutationObserver();
1213
+ };
1269
1214
 
1270
- _this.deviceId = ""; // 上传事件描述
1215
+ ExposureTracker.prototype.stop = function () {
1216
+ if (this.observer) {
1217
+ this.observer.disconnect();
1218
+ this.observer = null;
1219
+ this.elementsMap.forEach(function (elementInfo) {
1220
+ if (elementInfo.exposureTimer) {
1221
+ clearTimeout(elementInfo.exposureTimer);
1222
+ }
1223
+ });
1224
+ this.elementsMap.clear();
1225
+ }
1271
1226
 
1272
- _this.eventDescMap = {
1273
- PageView: "Web 浏览页面",
1274
- WebClick: "Web 元素点击",
1275
- PageRetained: "Web 页面浏览时长",
1276
- CustomTrack: "Web 自定义代码上报",
1277
- WebExposure: "Web 元素曝光"
1278
- };
1279
- /**
1280
- * @description 初始化函数
1281
- * @param {object} InitParams [初始化参数]
1282
- */
1227
+ if (this.mutationObserver) {
1228
+ this.mutationObserver.disconnect();
1229
+ this.mutationObserver = null;
1230
+ }
1283
1231
 
1284
- _this.init = function (initParams) {
1285
- _this.preset(initParams);
1232
+ if (this.config.showLog) {
1233
+ this.callbacks.printLog("曝光监听已停止");
1234
+ }
1235
+ };
1286
1236
 
1287
- var pathname = window.location.pathname;
1288
- _this.currentUrl = window.location.href; // 如果传入了自定义 pageKey,使用自定义值,否则自动生成
1237
+ ExposureTracker.prototype.addExposureElement = function (element) {
1238
+ if (!this.elementsMap.has(element)) {
1239
+ this.elementsMap.set(element, {
1240
+ element: element,
1241
+ visibleStartTime: 0,
1242
+ exposureCount: 0,
1243
+ isVisible: false,
1244
+ exposureTimer: null
1245
+ });
1289
1246
 
1290
- if (initParams.pageKey) {
1291
- _this.pageKey = initParams.pageKey;
1292
- _this.useCustomPageKey = true;
1293
- } else {
1294
- _this.pageKey = pathname.replace(/\//g, "_").substring(1);
1295
- _this.useCustomPageKey = false;
1247
+ if (this.observer) {
1248
+ this.observer.observe(element);
1296
1249
  }
1250
+ }
1251
+ };
1297
1252
 
1298
- _this.systemsInfo = _this.getSystemsInfo(initParams.platform); // 获取设备ID
1299
-
1300
- _this.deviceId = _this.getDeviceId(); // 如果传入了 userInfo,设置用户信息
1253
+ ExposureTracker.prototype.observeExposureElements = function () {
1254
+ var _this = this;
1301
1255
 
1302
- if (initParams.userInfo && _this.isObject(initParams.userInfo)) {
1303
- _this.userInfo = initParams.userInfo;
1304
- }
1256
+ if (!this.observer) {
1257
+ return;
1258
+ }
1305
1259
 
1306
- _this.setCookie("retainedStartTime", _this.getTimeStamp()); // 如果启用了批量发送,从 LocalStorage 恢复队列
1260
+ var elements = document.querySelectorAll('[data-exposure="true"]');
1261
+ elements.forEach(function (element) {
1262
+ _this.addExposureElement(element);
1263
+ });
1307
1264
 
1265
+ if (this.config.showLog && elements.length > 0) {
1266
+ this.callbacks.printLog("\u5DF2\u76D1\u542C " + elements.length + " \u4E2A\u66DD\u5149\u5143\u7D20");
1267
+ }
1308
1268
 
1309
- if (_this.initConfig.batchSend) {
1310
- _this.restoreBatchQueueFromStorage();
1311
- } // 恢复待发送的单个请求
1269
+ if (!document.body) {
1270
+ document.addEventListener("DOMContentLoaded", function () {
1271
+ var deferredElements = document.querySelectorAll('[data-exposure="true"]');
1272
+ deferredElements.forEach(function (element) {
1273
+ _this.addExposureElement(element);
1274
+ });
1312
1275
 
1276
+ if (_this.config.showLog && deferredElements.length > 0) {
1277
+ _this.callbacks.printLog("DOMContentLoaded \u540E\u76D1\u542C " + deferredElements.length + " \u4E2A\u66DD\u5149\u5143\u7D20");
1278
+ }
1279
+ });
1280
+ }
1281
+ };
1313
1282
 
1314
- _this.restorePendingRequestsFromStorage(); // 无论是否启用批量发送,都需要监听页面卸载事件,确保数据发送
1283
+ ExposureTracker.prototype.initMutationObserver = function () {
1284
+ var _this = this;
1315
1285
 
1286
+ if (!("MutationObserver" in window)) {
1287
+ if (this.config.showLog) {
1288
+ this.callbacks.printLog("当前浏览器不支持 MutationObserver,无法监听动态添加的曝光元素");
1289
+ }
1316
1290
 
1317
- _this.setupBeforeUnloadListener(); // 如果启用了定时上报,启动定时器
1291
+ return;
1292
+ }
1318
1293
 
1294
+ this.mutationObserver = new MutationObserver(function (mutations) {
1295
+ mutations.forEach(function (mutation) {
1296
+ mutation.addedNodes.forEach(function (node) {
1297
+ if (node.nodeType === Node.ELEMENT_NODE) {
1298
+ var element = node;
1319
1299
 
1320
- if (_this.initConfig.autoTrackPageDurationInterval) {
1321
- _this.startPageDurationTimer();
1322
- } // 如果启用了曝光监听,初始化曝光监听
1300
+ if (element.hasAttribute("data-exposure") && element.getAttribute("data-exposure") === "true") {
1301
+ _this.addExposureElement(element);
1302
+ } else {
1303
+ var exposureElements = element.querySelectorAll('[data-exposure="true"]');
1304
+ exposureElements.forEach(function (exposureElement) {
1305
+ _this.addExposureElement(exposureElement);
1306
+ });
1307
+ }
1308
+ }
1309
+ });
1310
+ });
1311
+ });
1323
1312
 
1313
+ if (document.body) {
1314
+ this.mutationObserver.observe(document.body, {
1315
+ childList: true,
1316
+ subtree: true
1317
+ });
1324
1318
 
1325
- if (_this.initConfig.autoTrackExposure) {
1326
- _this.initExposureObserver();
1319
+ if (this.config.showLog) {
1320
+ this.callbacks.printLog("MutationObserver 已启动,监听动态添加的曝光元素");
1327
1321
  }
1328
- };
1329
- /**
1330
- * @description 预置参数
1331
- * @param {object} PresetParams [预置参数]
1332
- */
1333
-
1322
+ } else {
1323
+ document.addEventListener("DOMContentLoaded", function () {
1324
+ if (_this.mutationObserver && document.body) {
1325
+ _this.mutationObserver.observe(document.body, {
1326
+ childList: true,
1327
+ subtree: true
1328
+ });
1334
1329
 
1335
- _this.preset = function (presetParams) {
1336
- if (presetParams instanceof Object) {
1337
- // 处理 pageKey 特殊逻辑
1338
- if (presetParams.pageKey !== undefined) {
1339
- if (presetParams.pageKey === null || presetParams.pageKey === '') {
1340
- // 恢复自动生成
1341
- _this.useCustomPageKey = false;
1342
- var pathname = window.location.pathname;
1343
- _this.pageKey = pathname.replace(/\//g, "_").substring(1);
1344
- } else {
1345
- _this.pageKey = presetParams.pageKey;
1346
- _this.useCustomPageKey = true;
1330
+ if (_this.config.showLog) {
1331
+ _this.callbacks.printLog("MutationObserver 已启动,监听动态添加的曝光元素");
1347
1332
  }
1348
1333
  }
1334
+ });
1335
+ }
1336
+ };
1349
1337
 
1350
- _this.each(presetParams, function (val, key) {
1351
- // 跳过 pageKey,因为已经单独处理
1352
- if (key === 'pageKey') return;
1338
+ ExposureTracker.prototype.calculateexposureScreenIndex = function (element) {
1339
+ var rect = element.getBoundingClientRect();
1340
+ var viewportHeight = window.innerHeight || document.documentElement.clientHeight;
1341
+ var scrollTop = window.scrollY || document.documentElement.scrollTop;
1342
+ var elementPageTop = rect.top + scrollTop;
1353
1343
 
1354
- if (_this.initConfig.hasOwnProperty(key)) {
1355
- // 参数验证
1356
- var validationResult = _this.validateConfigParam(String(key), val);
1344
+ if (elementPageTop <= 0) {
1345
+ return 1;
1346
+ }
1357
1347
 
1358
- if (validationResult.valid) {
1359
- _this.initConfig[key] = val;
1360
- } else {
1361
- _this.printLog("\u914D\u7F6E\u53C2\u6570\u9A8C\u8BC1\u5931\u8D25: " + String(key) + " = " + val + ", \u539F\u56E0: " + validationResult.message);
1362
- }
1363
- }
1364
- });
1365
- }
1348
+ return Math.ceil(elementPageTop / viewportHeight);
1349
+ };
1366
1350
 
1367
- if (!/^(((ht|f)tps?):\/\/)?[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-\(\)]*[\w@?^=%&/~+#-\(\)])?$/.test(_this.initConfig["serverUrl"])) {
1368
- _this.printLog("当前 server_url 为空或不正确,只在控制台打印日志,network 中不会发数据,请配置正确的 server_url!");
1351
+ ExposureTracker.prototype.getElementInfo = function (element) {
1352
+ return this.elementsMap.get(element);
1353
+ };
1369
1354
 
1370
- _this.initConfig["showLog"] = true;
1371
- } // 如果启用了全埋点或启用了 data-part-key 点击追踪
1355
+ ExposureTracker.prototype.incrementExposureCount = function (element) {
1356
+ var elementInfo = this.elementsMap.get(element);
1372
1357
 
1358
+ if (elementInfo) {
1359
+ elementInfo.exposureCount++;
1360
+ }
1361
+ };
1373
1362
 
1374
- if (!!_this.initConfig["autoTrack"] || !!_this.initConfig["trackPartKeyClick"]) {
1375
- // 启用监听
1376
- _this.listener();
1377
- } else {
1378
- // 取消监听
1379
- _this.unlistener();
1380
- } // 处理定时上报配置
1363
+ ExposureTracker.prototype.clearExposureTimer = function (element) {
1364
+ var elementInfo = this.elementsMap.get(element);
1381
1365
 
1366
+ if (elementInfo && elementInfo.exposureTimer) {
1367
+ clearTimeout(elementInfo.exposureTimer);
1368
+ elementInfo.exposureTimer = null;
1369
+ }
1370
+ };
1382
1371
 
1383
- if (_this.initConfig.autoTrackPageDurationInterval) {
1384
- _this.startPageDurationTimer();
1385
- } else {
1386
- _this.stopPageDurationTimer();
1387
- } // 处理曝光监听配置
1372
+ return ExposureTracker;
1373
+ }();
1388
1374
 
1375
+ var PageDurationTracker =
1376
+ /** @class */
1377
+ function () {
1378
+ function PageDurationTracker(config, callbacks) {
1379
+ this.timer = null;
1380
+ this.config = config;
1381
+ this.callbacks = callbacks;
1382
+ }
1389
1383
 
1390
- if (_this.initConfig.autoTrackExposure) {
1391
- _this.initExposureObserver();
1392
- } else {
1393
- _this.stopExposureObserver();
1394
- }
1395
- };
1396
- /**
1397
- * @description 验证配置参数
1398
- * @param key 参数名
1399
- * @param value 参数值
1400
- * @returns 验证结果
1401
- */
1384
+ PageDurationTracker.prototype.updateConfig = function (config) {
1385
+ var oldInterval = this.config.pageDurationInterval;
1386
+ this.config = __assign(__assign({}, this.config), config);
1402
1387
 
1388
+ if (config.pageDurationInterval !== undefined && config.pageDurationInterval !== oldInterval && this.timer !== null) {
1389
+ this.stop();
1390
+ this.start();
1391
+ }
1392
+ };
1403
1393
 
1404
- _this.validateConfigParam = function (key, value) {
1405
- switch (key) {
1406
- case 'sampleRate':
1407
- if (typeof value !== 'number' || value < 0 || value > 1) {
1408
- return {
1409
- valid: false,
1410
- message: 'sampleRate 必须是 0-1 之间的数字'
1411
- };
1412
- }
1394
+ PageDurationTracker.prototype.start = function () {
1395
+ var _this = this;
1413
1396
 
1414
- break;
1397
+ this.stop();
1398
+ var interval = this.config.pageDurationInterval || 30000;
1415
1399
 
1416
- case 'sendTimeout':
1417
- if (typeof value !== 'number' || value <= 0) {
1418
- return {
1419
- valid: false,
1420
- message: 'sendTimeout 必须是大于 0 的数字'
1421
- };
1422
- }
1400
+ if (interval <= 0) {
1401
+ if (this.config.showLog) {
1402
+ this.callbacks.printLog("定时上报间隔时间无效,已禁用定时上报");
1403
+ }
1423
1404
 
1424
- break;
1405
+ return;
1406
+ }
1425
1407
 
1426
- case 'batchInterval':
1427
- if (typeof value !== 'number' || value <= 0) {
1428
- return {
1429
- valid: false,
1430
- message: 'batchInterval 必须是大于 0 的数字'
1431
- };
1408
+ this.timer = window.setInterval(function () {
1409
+ if (document.visibilityState === "visible") {
1410
+ _this.callbacks.track(interval, {
1411
+ desc: "定时上报页面停留时长"
1412
+ }, true).catch(function (err) {
1413
+ if (_this.config.showLog) {
1414
+ _this.callbacks.printLog("\u5B9A\u65F6\u4E0A\u62A5\u9875\u9762\u505C\u7559\u65F6\u957F\u5931\u8D25: " + err);
1432
1415
  }
1416
+ });
1417
+ }
1418
+ }, interval);
1433
1419
 
1434
- break;
1420
+ if (this.config.showLog) {
1421
+ this.callbacks.printLog("\u5B9A\u65F6\u4E0A\u62A5\u9875\u9762\u505C\u7559\u65F6\u957F\u5DF2\u542F\u52A8\uFF0C\u95F4\u9694: " + interval + "ms");
1422
+ }
1423
+ };
1435
1424
 
1436
- case 'batchMaxSize':
1437
- if (typeof value !== 'number' || value <= 0 || !Number.isInteger(value)) {
1438
- return {
1439
- valid: false,
1440
- message: 'batchMaxSize 必须是大于 0 的整数'
1441
- };
1442
- }
1425
+ PageDurationTracker.prototype.stop = function () {
1426
+ if (this.timer !== null) {
1427
+ clearInterval(this.timer);
1428
+ this.timer = null;
1443
1429
 
1444
- break;
1430
+ if (this.config.showLog) {
1431
+ this.callbacks.printLog("定时上报页面停留时长已停止");
1432
+ }
1433
+ }
1434
+ };
1445
1435
 
1446
- case 'pendingRequestsMaxSize':
1447
- if (typeof value !== 'number' || value <= 0 || !Number.isInteger(value)) {
1448
- return {
1449
- valid: false,
1450
- message: 'pendingRequestsMaxSize 必须是大于 0 的整数'
1451
- };
1452
- }
1436
+ PageDurationTracker.prototype.calculateDuration = function (customDuration) {
1437
+ if (customDuration !== undefined && customDuration !== null) {
1438
+ return Math.max(customDuration, 0);
1439
+ }
1453
1440
 
1454
- break;
1441
+ var time = this.callbacks.getCookie("retainedStartTime");
1442
+ var retainedStartTime = time ? +time : this.callbacks.getTimeStamp();
1443
+ var currentTime = this.callbacks.getTimeStamp();
1444
+ return Math.max(currentTime - retainedStartTime, 0);
1445
+ };
1455
1446
 
1456
- case 'pageDurationInterval':
1457
- if (typeof value !== 'number' || value <= 0) {
1458
- return {
1459
- valid: false,
1460
- message: 'pageDurationInterval 必须是大于 0 的数字'
1461
- };
1462
- }
1447
+ return PageDurationTracker;
1448
+ }();
1463
1449
 
1464
- break;
1450
+ var BATCH_QUEUE_STORAGE_KEY = "web_tracking_batch_queue";
1451
+ var MAX_STORAGE_SIZE = 4 * 1024 * 1024;
1465
1452
 
1466
- case 'sendMethod':
1467
- if (typeof value !== 'string' || !['auto', 'xhr', 'beacon'].includes(value)) {
1468
- return {
1469
- valid: false,
1470
- message: 'sendMethod 必须是 auto、xhr 或 beacon'
1471
- };
1472
- }
1453
+ var BatchSender =
1454
+ /** @class */
1455
+ function () {
1456
+ function BatchSender(config, callbacks) {
1457
+ this.timer = null;
1458
+ this.isFlushingQueue = false;
1459
+ this.config = config;
1460
+ this.callbacks = callbacks;
1461
+ }
1473
1462
 
1474
- break;
1463
+ BatchSender.prototype.start = function () {
1464
+ var _this = this;
1475
1465
 
1476
- case 'exposureThreshold':
1477
- if (typeof value !== 'number' || value < 0 || value > 1) {
1478
- return {
1479
- valid: false,
1480
- message: 'exposureThreshold 必须是 0-1 之间的数字'
1481
- };
1482
- }
1466
+ if (!this.config.batchSend) {
1467
+ return;
1468
+ }
1483
1469
 
1484
- break;
1470
+ this.clearTimer();
1471
+ this.timer = window.setInterval(function () {
1472
+ _this.flushQueue();
1473
+ }, this.config.batchInterval);
1474
+ };
1485
1475
 
1486
- case 'exposureTime':
1487
- if (typeof value !== 'number' || value <= 0) {
1488
- return {
1489
- valid: false,
1490
- message: 'exposureTime 必须是大于 0 的数字'
1491
- };
1492
- }
1476
+ BatchSender.prototype.updateConfig = function (config) {
1477
+ this.config = __assign(__assign({}, this.config), config);
1478
+ this.clearTimer();
1479
+ this.start();
1480
+ };
1493
1481
 
1494
- break;
1482
+ BatchSender.prototype.addToQueue = function (params) {
1483
+ if (!this.config.batchSend) {
1484
+ return;
1485
+ }
1495
1486
 
1496
- case 'exposureNum':
1497
- if (value !== undefined && (typeof value !== 'number' || value <= 0 || !Number.isInteger(value))) {
1498
- return {
1499
- valid: false,
1500
- message: 'exposureNum 必须是大于 0 的整数或不限制'
1501
- };
1502
- }
1487
+ if (!this.callbacks.shouldSample()) {
1488
+ if (this.config.showLog) {
1489
+ this.callbacks.printLog("数据已采样跳过(批量模式)");
1490
+ }
1503
1491
 
1504
- break;
1492
+ return;
1493
+ }
1505
1494
 
1506
- case 'showLog':
1507
- case 'autoTrack':
1508
- case 'isTrackSinglePage':
1509
- case 'batchSend':
1510
- case 'trackPartKeyClick':
1511
- case 'autoTrackPageDurationInterval':
1512
- if (typeof value !== 'boolean') {
1513
- return {
1514
- valid: false,
1515
- message: key + " \u5FC5\u987B\u662F\u5E03\u5C14\u503C"
1516
- };
1517
- }
1495
+ var currentQueue = this.getQueueFromStorage();
1496
+ currentQueue.push(params);
1497
+ this.saveQueueToStorage(currentQueue);
1518
1498
 
1519
- break;
1499
+ if (currentQueue.length >= this.config.batchMaxSize) {
1500
+ this.flushQueue();
1501
+ return;
1502
+ }
1503
+ };
1520
1504
 
1521
- case 'business':
1522
- case 'header':
1523
- if (value !== null && _typeof(value) !== 'object') {
1524
- return {
1525
- valid: false,
1526
- message: key + " \u5FC5\u987B\u662F\u5BF9\u8C61\u6216 null"
1527
- };
1528
- }
1505
+ BatchSender.prototype.clearTimer = function () {
1506
+ if (this.timer !== null) {
1507
+ clearInterval(this.timer);
1508
+ this.timer = null;
1509
+ }
1510
+ };
1529
1511
 
1530
- break;
1512
+ BatchSender.prototype.clearQueue = function () {
1513
+ this.callbacks.setLocalStorage(BATCH_QUEUE_STORAGE_KEY, "[]");
1531
1514
 
1532
- case 'contentType':
1533
- if (value !== 'application/json' && value !== 'application/x-www-form-urlencoded') {
1534
- return {
1535
- valid: false,
1536
- message: 'contentType 必须是 application/json 或 application/x-www-form-urlencoded'
1537
- };
1538
- }
1515
+ if (this.config.showLog) {
1516
+ this.callbacks.printLog("批量队列已清空");
1517
+ }
1518
+ };
1539
1519
 
1540
- break;
1520
+ BatchSender.prototype.restoreQueue = function () {
1521
+ var queue = this.getQueueFromStorage();
1541
1522
 
1542
- case 'platform':
1543
- if (typeof value !== 'string') {
1544
- return {
1545
- valid: false,
1546
- message: 'platform 必须是字符串'
1547
- };
1548
- }
1523
+ if (queue.length > 0 && this.config.showLog) {
1524
+ this.callbacks.printLog("\u4ECE LocalStorage \u6062\u590D " + queue.length + " \u6761\u6279\u91CF\u6570\u636E");
1525
+ }
1526
+ };
1549
1527
 
1550
- break;
1551
- }
1528
+ BatchSender.prototype.getQueueFromStorage = function () {
1529
+ try {
1530
+ var storedQueue = this.callbacks.getLocalStorage(BATCH_QUEUE_STORAGE_KEY);
1552
1531
 
1553
- return {
1554
- valid: true
1555
- };
1556
- };
1557
- /**
1558
- * 用户登录
1559
- */
1532
+ if (storedQueue) {
1533
+ var parsedQueue = JSON.parse(storedQueue);
1560
1534
 
1535
+ if (Array.isArray(parsedQueue)) {
1536
+ return parsedQueue;
1537
+ }
1538
+ }
1539
+ } catch (e) {
1540
+ this.callbacks.printLog("\u8BFB\u53D6\u6279\u91CF\u961F\u5217\u5931\u8D25: " + e);
1541
+ this.callbacks.setLocalStorage(BATCH_QUEUE_STORAGE_KEY, "[]");
1542
+ }
1561
1543
 
1562
- _this.login = function (userInfo) {
1563
- if (_this.isObject(userInfo)) _this.userInfo = userInfo;
1564
- };
1565
- /**
1566
- * 获取设备唯一标识
1567
- * @returns 设备唯一标识
1568
- */
1544
+ return [];
1545
+ };
1569
1546
 
1547
+ BatchSender.prototype.saveQueueToStorage = function (queue) {
1548
+ try {
1549
+ var queueString = JSON.stringify(queue);
1570
1550
 
1571
- _this.getDeviceId = function () {
1572
- // 如果已有设备ID,直接返回
1573
- if (_this.deviceId) {
1574
- return _this.deviceId;
1575
- } // 获取已存储的设备ID
1551
+ if (queueString.length > MAX_STORAGE_SIZE) {
1552
+ var maxItems = Math.floor(queue.length * 0.8);
1553
+ var trimmedQueue = queue.slice(-maxItems);
1554
+ this.callbacks.printLog("\u961F\u5217\u8FC7\u5927\uFF0C\u5DF2\u622A\u65AD\u4FDD\u7559\u6700\u65B0 " + maxItems + " \u6761\u6570\u636E\uFF08\u9650\u5236: " + MAX_STORAGE_SIZE / 1024 / 1024 + "MB\uFF09");
1555
+ this.callbacks.setLocalStorage(BATCH_QUEUE_STORAGE_KEY, JSON.stringify(trimmedQueue));
1556
+ } else {
1557
+ this.callbacks.setLocalStorage(BATCH_QUEUE_STORAGE_KEY, queueString);
1558
+ }
1559
+ } catch (e) {
1560
+ this.callbacks.printLog("\u4FDD\u5B58\u6279\u91CF\u961F\u5217\u5230 LocalStorage \u5931\u8D25: " + e);
1561
+ }
1562
+ };
1576
1563
 
1564
+ BatchSender.prototype.flushQueue = function () {
1565
+ var _this = this;
1577
1566
 
1578
- var storedDeviceId = _this.getCookie("device_id") || _this.getLocalStorage("device_id");
1567
+ if (this.isFlushingQueue) {
1568
+ return;
1569
+ }
1579
1570
 
1580
- if (storedDeviceId) {
1581
- _this.deviceId = storedDeviceId;
1582
- return _this.deviceId;
1583
- } // 收集浏览器指纹信息
1571
+ var batchQueue = this.getQueueFromStorage();
1572
+ if (batchQueue.length === 0) return;
1573
+ var currentTime = this.callbacks.getTimeStamp();
1574
+ var readyToSend = batchQueue.filter(function (item) {
1575
+ if (!item._nextRetryTime) {
1576
+ return true;
1577
+ }
1584
1578
 
1579
+ return item._nextRetryTime <= currentTime;
1580
+ });
1585
1581
 
1586
- var fingerprint = _this.collectFingerprint(); // 生成设备ID
1582
+ if (readyToSend.length === 0) {
1583
+ if (this.config.showLog) {
1584
+ this.callbacks.printLog("\u6279\u91CF\u961F\u5217\u4E2D\u6709 " + batchQueue.length + " \u6761\u6570\u636E\u7B49\u5F85\u91CD\u8BD5");
1585
+ }
1587
1586
 
1587
+ return;
1588
+ }
1588
1589
 
1589
- var deviceId = _this.hashFingerprint(fingerprint); // 存储设备ID(统一使用2年过期时间,与tools.ts保持一致)
1590
+ var remainingQueue = batchQueue.filter(function (item) {
1591
+ if (!item._nextRetryTime) {
1592
+ return false;
1593
+ }
1590
1594
 
1595
+ return item._nextRetryTime > currentTime;
1596
+ });
1597
+ this.saveQueueToStorage(remainingQueue);
1598
+ this.isFlushingQueue = true;
1599
+ this.sendBatchData(readyToSend, function () {
1600
+ _this.isFlushingQueue = false;
1601
+ });
1602
+ };
1591
1603
 
1592
- _this.setCookie("device_id", deviceId, 365 * 2); // 存储2年
1604
+ BatchSender.prototype.sendBatchData = function (data, onComplete) {
1605
+ var _this = this;
1593
1606
 
1607
+ var _a = this.config,
1608
+ serverUrl = _a.serverUrl,
1609
+ contentType = _a.contentType,
1610
+ showLog = _a.showLog,
1611
+ sendMethod = _a.sendMethod,
1612
+ initHeader = _a.header;
1613
+
1614
+ if (showLog) {
1615
+ this.callbacks.printLog("\u6279\u91CF\u53D1\u9001 " + data.length + " \u6761\u6570\u636E");
1616
+ data.forEach(function (item) {
1617
+ return _this.callbacks.printLog(JSON.stringify(item));
1618
+ });
1619
+ }
1594
1620
 
1595
- _this.setLocalStorage("device_id", deviceId);
1621
+ var shouldUseBeacon = this.callbacks.shouldUseBeacon(sendMethod, undefined, initHeader);
1596
1622
 
1597
- _this.deviceId = deviceId;
1598
- return _this.deviceId;
1623
+ var complete = function complete() {
1624
+ if (onComplete) {
1625
+ onComplete();
1626
+ }
1599
1627
  };
1600
- /**
1601
- * 重置设备ID
1602
- * 清除存储的设备ID并重新生成
1603
- * @returns 新的设备ID
1604
- */
1605
-
1606
1628
 
1607
- _this.resetDeviceId = function () {
1608
- // 清除cookie和localStorage中的设备ID
1609
- document.cookie = "device_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
1610
- localStorage.removeItem("device_id"); // 清除内存中的设备ID
1611
-
1612
- _this.deviceId = ""; // 重新生成设备ID
1629
+ if (shouldUseBeacon) {
1630
+ try {
1631
+ var blob = new Blob([JSON.stringify(data)], {
1632
+ type: contentType || "application/json"
1633
+ });
1634
+ var sent = navigator.sendBeacon(serverUrl, blob);
1613
1635
 
1614
- var newDeviceId = _this.getDeviceId();
1636
+ if (sent) {
1637
+ if (showLog) {
1638
+ this.callbacks.printLog("\u6279\u91CF\u53D1\u9001\u6210\u529F: " + data.length + " \u6761\u6570\u636E");
1639
+ }
1615
1640
 
1616
- return newDeviceId;
1617
- };
1618
- /**
1619
- * 自定义代码埋点上报
1620
- * @param {object} TrackParams [自定义上报参数]
1621
- * @return {Promise<TrackingResponse>} [回调]
1622
- */
1641
+ complete();
1642
+ } else {
1643
+ this.callbacks.printLog("\u6279\u91CF\u53D1\u9001\u5931\u8D25: sendBeacon \u8FD4\u56DE false\uFF0C\u6570\u636E\u5DF2\u91CD\u65B0\u52A0\u5165\u961F\u5217");
1644
+ this.retryBatchData(data);
1645
+ complete();
1646
+ }
1647
+ } catch (e) {
1648
+ this.callbacks.printLog("\u6279\u91CF\u53D1\u9001\u5931\u8D25: " + e + "\uFF0C\u6570\u636E\u5DF2\u91CD\u65B0\u52A0\u5165\u961F\u5217");
1649
+ this.retryBatchData(data);
1650
+ complete();
1651
+ }
1652
+ } else {
1653
+ this.callbacks.ajax({
1654
+ url: serverUrl,
1655
+ type: "POST",
1656
+ data: JSON.stringify({
1657
+ events: data
1658
+ }),
1659
+ contentType: contentType,
1660
+ credentials: false,
1661
+ timeout: this.config.sendTimeout,
1662
+ cors: true,
1663
+ success: function success() {
1664
+ if (_this.config.showLog) {
1665
+ _this.callbacks.printLog("\u6279\u91CF\u53D1\u9001\u6210\u529F: " + data.length + " \u6761\u6570\u636E");
1666
+ }
1623
1667
 
1668
+ complete();
1669
+ },
1670
+ error: function error(err) {
1671
+ _this.callbacks.printLog("\u6279\u91CF\u53D1\u9001\u5931\u8D25: " + err + "\uFF0C\u6570\u636E\u5DF2\u91CD\u65B0\u52A0\u5165\u961F\u5217");
1624
1672
 
1625
- _this.track = function (_a) {
1626
- var desc = _a.desc,
1627
- pageKey = _a.pageKey,
1628
- partkey = _a.partkey,
1629
- business = _a.business,
1630
- header = _a.header;
1673
+ _this.retryBatchData(data);
1631
1674
 
1632
- var params = _this.getParams({
1633
- desc: desc,
1634
- event: "CustomTrack",
1635
- itemKey: _this.getItemKey(partkey, pageKey),
1636
- privateParamMap: {
1637
- business: business
1675
+ complete();
1638
1676
  }
1639
1677
  });
1678
+ }
1679
+ };
1640
1680
 
1641
- return _this.sendData(params, header);
1642
- };
1643
- /**
1644
- * @description 监听全埋点事件
1645
- */
1646
-
1647
-
1648
- _this.listener = function () {
1649
- // 先移除旧的监听器,避免重复绑定
1650
- _this.unlistener(); // 如果启用了全埋点,监听页面浏览事件
1651
-
1652
-
1653
- if (!!_this.initConfig.autoTrack) {
1654
- if (!!_this.initConfig.isTrackSinglePage) {
1655
- _this.rewriteHistory();
1681
+ BatchSender.prototype.retryBatchData = function (data) {
1682
+ var _this = this;
1656
1683
 
1657
- _this.addSinglePageEvent(_this.onPageViewCallback);
1658
- }
1684
+ var currentQueue = this.getQueueFromStorage();
1659
1685
 
1660
- _this.each(["load", "beforeunload"], function (historyType) {
1661
- _this.addEventListener(window, historyType, _this.onPageViewCallback);
1662
- });
1663
- } // 如果启用了全埋点或启用了 data-part-key 点击追踪,监听点击事件
1686
+ var getEventKey = function getEventKey(item) {
1687
+ return item.event + "_" + item.itemKey + "_" + item.requestTime;
1688
+ };
1664
1689
 
1690
+ var existingKeys = new Set(currentQueue.map(getEventKey));
1691
+ var maxRetryCount = 3;
1692
+ var currentTime = this.callbacks.getTimeStamp();
1693
+ var retryData = data.filter(function (item) {
1694
+ var key = getEventKey(item);
1665
1695
 
1666
- if (!!_this.initConfig.autoTrack || !!_this.initConfig.trackPartKeyClick) {
1667
- _this.addEventListener(window, "click", _this.onClickCallback);
1696
+ if (existingKeys.has(key)) {
1697
+ return false;
1668
1698
  }
1669
- };
1670
- /**
1671
- * @description 取消全埋点事件
1672
- */
1673
1699
 
1700
+ var retryCount = (item._retryCount || 0) + 1;
1674
1701
 
1675
- _this.unlistener = function () {
1676
- if (!!_this.initConfig.isTrackSinglePage) {
1677
- var historyPushState = window.history.pushState;
1678
- var singlePageEvent = !!historyPushState ? "popstate" : "hashchange";
1702
+ if (retryCount > maxRetryCount) {
1703
+ if (_this.config.showLog) {
1704
+ _this.callbacks.printLog("\u6570\u636E\u5DF2\u8FBE\u5230\u6700\u5927\u91CD\u8BD5\u6B21\u6570\uFF0C\u653E\u5F03\u91CD\u8BD5: " + key);
1705
+ }
1679
1706
 
1680
- _this.each(["pushState", "replaceState", singlePageEvent], function (historyName) {
1681
- _this.removeEventListener(window, historyName, _this.onPageViewCallback);
1682
- });
1707
+ return false;
1683
1708
  }
1684
1709
 
1685
- _this.each(["load", "beforeunload"], function (historyType) {
1686
- _this.removeEventListener(window, historyType, _this.onPageViewCallback);
1687
- });
1710
+ item._retryCount = retryCount;
1711
+ item._nextRetryTime = currentTime + Math.pow(2, retryCount) * 1000;
1712
+ existingKeys.add(key);
1713
+ return true;
1714
+ });
1688
1715
 
1689
- _this.removeEventListener(window, "click", _this.onClickCallback); // 清理批量发送定时器
1716
+ var retryQueue = __spreadArray(__spreadArray([], retryData), currentQueue);
1690
1717
 
1718
+ var maxSize = this.config.batchMaxSize * 2;
1719
+ var trimmedQueue = retryQueue.length > maxSize ? retryQueue.slice(0, maxSize) : retryQueue;
1720
+ this.saveQueueToStorage(trimmedQueue);
1691
1721
 
1692
- _this.clearBatchTimer(); // 清理定时上报定时器
1722
+ if (this.config.showLog) {
1723
+ this.callbacks.printLog("\u5DF2\u5C06 " + retryData.length + " \u6761\u6570\u636E\u52A0\u5165\u91CD\u8BD5\u961F\u5217");
1724
+ }
1725
+ };
1693
1726
 
1727
+ return BatchSender;
1728
+ }();
1694
1729
 
1695
- _this.stopPageDurationTimer();
1696
- };
1697
- /**
1698
- * @description 清理批量发送定时器
1699
- */
1730
+ var DeviceManager =
1731
+ /** @class */
1732
+ function () {
1733
+ function DeviceManager(callbacks) {
1734
+ this.deviceId = "";
1735
+ this.callbacks = callbacks;
1736
+ }
1700
1737
 
1738
+ DeviceManager.prototype.getDeviceId = function () {
1739
+ if (this.deviceId) {
1740
+ return this.deviceId;
1741
+ }
1701
1742
 
1702
- _this.clearBatchTimer = function () {
1703
- if (_this.batchTimer !== null) {
1704
- clearTimeout(_this.batchTimer);
1705
- _this.batchTimer = null;
1706
- }
1707
- };
1708
- /**
1709
- * @description 清空批量队列(包括 LocalStorage 中的数据)
1710
- */
1743
+ var storedDeviceId = this.callbacks.getCookie("device_id") || this.callbacks.getLocalStorage("device_id");
1711
1744
 
1745
+ if (storedDeviceId) {
1746
+ this.deviceId = storedDeviceId;
1747
+ return this.deviceId;
1748
+ }
1712
1749
 
1713
- _this.clearBatchQueue = function () {
1714
- _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
1750
+ var fingerprint = this.callbacks.collectFingerprint();
1751
+ var deviceId = this.callbacks.hashFingerprint(fingerprint);
1752
+ this.callbacks.setCookie("device_id", deviceId, 365 * 2);
1753
+ this.callbacks.setLocalStorage("device_id", deviceId);
1754
+ this.deviceId = deviceId;
1755
+ return this.deviceId;
1756
+ };
1715
1757
 
1716
- if (_this.initConfig.showLog) {
1717
- _this.printLog("批量队列已清空");
1718
- }
1719
- };
1720
- /**
1721
- * @description 从 LocalStorage 获取批量队列
1722
- * @returns 批量队列数组
1723
- */
1758
+ DeviceManager.prototype.resetDeviceId = function () {
1759
+ document.cookie = "device_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
1760
+ localStorage.removeItem("device_id");
1761
+ this.deviceId = "";
1762
+ var newDeviceId = this.getDeviceId();
1763
+ return newDeviceId;
1764
+ };
1765
+
1766
+ return DeviceManager;
1767
+ }();
1724
1768
 
1769
+ var PENDING_REQUESTS_STORAGE_KEY = "web_tracking_pending_requests";
1770
+ var DEFAULT_PENDING_REQUESTS_MAX_SIZE = 50;
1725
1771
 
1726
- _this.getBatchQueueFromStorage = function () {
1727
- try {
1728
- var storedQueue = _this.getLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY);
1772
+ var PendingRequestsManager =
1773
+ /** @class */
1774
+ function () {
1775
+ function PendingRequestsManager(config, callbacks) {
1776
+ this.isFlushingPendingData = false;
1777
+ this.isUnloadListenerSetup = false;
1778
+ this.config = config;
1779
+ this.callbacks = callbacks;
1780
+ }
1729
1781
 
1730
- if (storedQueue) {
1731
- var parsedQueue = JSON.parse(storedQueue);
1782
+ PendingRequestsManager.prototype.updateConfig = function (config) {
1783
+ this.config = __assign(__assign({}, this.config), config);
1784
+ };
1732
1785
 
1733
- if (Array.isArray(parsedQueue)) {
1734
- return parsedQueue;
1735
- }
1736
- }
1737
- } catch (e) {
1738
- _this.printLog("\u8BFB\u53D6\u6279\u91CF\u961F\u5217\u5931\u8D25: " + e); // 如果解析失败,清除损坏的数据
1786
+ PendingRequestsManager.prototype.addToQueue = function (params) {
1787
+ var currentRequests = this.getQueueFromStorage();
1788
+ currentRequests.push(params);
1789
+ var maxSize = this.config.pendingRequestsMaxSize || DEFAULT_PENDING_REQUESTS_MAX_SIZE;
1739
1790
 
1791
+ if (currentRequests.length > maxSize) {
1792
+ var trimmedRequests = currentRequests.slice(-maxSize);
1740
1793
 
1741
- _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
1794
+ if (this.config.showLog) {
1795
+ this.callbacks.printLog("\u5F85\u53D1\u9001\u8BF7\u6C42\u961F\u5217\u5DF2\u6EE1\uFF0C\u5DF2\u79FB\u9664\u6700\u65E7\u7684\u6570\u636E\uFF08\u6700\u5927\u9650\u5236: " + maxSize + "\uFF09");
1742
1796
  }
1743
1797
 
1744
- return [];
1745
- };
1746
- /**
1747
- * @description 保存批量队列到 LocalStorage
1748
- * @param queue 批量队列数组
1749
- */
1798
+ this.saveQueueToStorage(trimmedRequests);
1799
+ } else {
1800
+ this.saveQueueToStorage(currentRequests);
1801
+ }
1802
+ };
1750
1803
 
1804
+ PendingRequestsManager.prototype.restoreQueue = function () {
1805
+ var pendingRequests = this.getQueueFromStorage();
1751
1806
 
1752
- _this.saveBatchQueueToStorage = function (queue) {
1753
- try {
1754
- var queueString = JSON.stringify(queue); // 检查存储大小,避免超出 LocalStorage 限制
1807
+ if (pendingRequests.length > 0) {
1808
+ if (this.config.showLog) {
1809
+ this.callbacks.printLog("\u4ECE LocalStorage \u6062\u590D " + pendingRequests.length + " \u6761\u5F85\u53D1\u9001\u8BF7\u6C42");
1810
+ }
1811
+ }
1812
+ };
1755
1813
 
1756
- if (queueString.length > _this.MAX_STORAGE_SIZE) {
1757
- var maxItems = Math.floor(queue.length * 0.8); // 保留 80%
1814
+ PendingRequestsManager.prototype.flushQueue = function () {
1815
+ var _this = this;
1758
1816
 
1759
- var trimmedQueue = queue.slice(-maxItems);
1817
+ var pendingRequests = this.getQueueFromStorage();
1760
1818
 
1761
- _this.printLog("\u961F\u5217\u8FC7\u5927\uFF0C\u5DF2\u622A\u65AD\u4FDD\u7559\u6700\u65B0 " + maxItems + " \u6761\u6570\u636E\uFF08\u9650\u5236: " + _this.MAX_STORAGE_SIZE / 1024 / 1024 + "MB\uFF09");
1819
+ if (pendingRequests.length === 0) {
1820
+ return;
1821
+ }
1762
1822
 
1763
- _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, JSON.stringify(trimmedQueue));
1764
- } else {
1765
- _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, queueString);
1823
+ this.saveQueueToStorage([]);
1824
+ var _a = this.config,
1825
+ serverUrl = _a.serverUrl,
1826
+ sendTimeout = _a.sendTimeout,
1827
+ contentType = _a.contentType,
1828
+ showLog = _a.showLog,
1829
+ initHeader = _a.header;
1830
+ pendingRequests.forEach(function (params) {
1831
+ if (!_this.callbacks.shouldSample()) {
1832
+ if (showLog) {
1833
+ _this.callbacks.printLog("待发送请求已采样跳过");
1766
1834
  }
1767
- } catch (e) {
1768
- // LocalStorage 可能已满或不可用
1769
- _this.printLog("\u4FDD\u5B58\u6279\u91CF\u961F\u5217\u5230 LocalStorage \u5931\u8D25: " + e);
1835
+
1836
+ return;
1770
1837
  }
1771
- };
1772
- /**
1773
- * @description 从 LocalStorage 获取待发送请求队列
1774
- * @returns 待发送请求队列数组
1775
- */
1776
1838
 
1839
+ if (showLog) {
1840
+ _this.callbacks.printLog(JSON.stringify(params));
1841
+ }
1842
+
1843
+ _this.callbacks.ajax({
1844
+ url: serverUrl,
1845
+ type: "POST",
1846
+ data: JSON.stringify(params),
1847
+ contentType: contentType,
1848
+ header: initHeader,
1849
+ credentials: false,
1850
+ timeout: sendTimeout,
1851
+ cors: true,
1852
+ success: function success() {
1853
+ if (showLog) {
1854
+ _this.callbacks.printLog("待发送请求发送成功");
1855
+ }
1856
+ },
1857
+ error: function error(err) {
1858
+ if (showLog) {
1859
+ _this.callbacks.printLog("\u5F85\u53D1\u9001\u8BF7\u6C42\u53D1\u9001\u5931\u8D25\uFF08\u4E0D\u518D\u91CD\u8BD5\uFF09: " + err);
1860
+ }
1861
+ }
1862
+ });
1863
+ });
1864
+ };
1777
1865
 
1778
- _this.getPendingRequestsFromStorage = function () {
1779
- try {
1780
- var storedRequests = _this.getLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY);
1866
+ PendingRequestsManager.prototype.flushQueueWithBeacon = function () {
1867
+ var _this = this;
1781
1868
 
1782
- if (storedRequests) {
1783
- var parsedRequests = JSON.parse(storedRequests);
1869
+ if (this.isFlushingPendingData) {
1870
+ return;
1871
+ }
1784
1872
 
1785
- if (Array.isArray(parsedRequests)) {
1786
- return parsedRequests;
1787
- }
1788
- }
1789
- } catch (e) {
1790
- _this.printLog("\u8BFB\u53D6\u5F85\u53D1\u9001\u8BF7\u6C42\u5931\u8D25: " + e); // 如果解析失败,清除损坏的数据
1873
+ this.isFlushingPendingData = true;
1874
+ var pendingRequests = this.getQueueFromStorage();
1791
1875
 
1876
+ if (pendingRequests.length === 0) {
1877
+ this.isFlushingPendingData = false;
1878
+ return;
1879
+ }
1792
1880
 
1793
- _this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, "[]");
1881
+ var _a = this.config,
1882
+ serverUrl = _a.serverUrl,
1883
+ contentType = _a.contentType,
1884
+ showLog = _a.showLog;
1885
+ var successCount = 0;
1886
+ var failureCount = 0;
1887
+ pendingRequests.forEach(function (params) {
1888
+ if (_this.sendWithBeacon(params, serverUrl, contentType)) {
1889
+ successCount++;
1890
+ } else {
1891
+ failureCount++;
1794
1892
  }
1893
+ });
1795
1894
 
1796
- return [];
1797
- };
1798
- /**
1799
- * @description 保存待发送请求队列到 LocalStorage
1800
- * @param requests 待发送请求队列数组
1801
- */
1895
+ if (showLog) {
1896
+ this.callbacks.printLog("\u9875\u9762\u5378\u8F7D\u65F6\u53D1\u9001\u5F85\u8BF7\u6C42\u6570\u636E: \u6210\u529F " + successCount + " \u6761\uFF0C\u5931\u8D25 " + failureCount + " \u6761");
1897
+ }
1802
1898
 
1899
+ this.isFlushingPendingData = false;
1900
+ };
1803
1901
 
1804
- _this.savePendingRequestsToStorage = function (requests) {
1805
- try {
1806
- _this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, JSON.stringify(requests));
1807
- } catch (e) {
1808
- // LocalStorage 可能已满或不可用
1809
- _this.printLog("\u4FDD\u5B58\u5F85\u53D1\u9001\u8BF7\u6C42\u5230 LocalStorage \u5931\u8D25: " + e);
1810
- }
1811
- };
1812
- /**
1813
- * @description 设置自定义页面唯一标识
1814
- * @param pageKey 页面唯一标识,如果传入 null 或空字符串,则恢复自动生成
1815
- * @param autoUpdate 路由变化时是否自动更新(默认:false,使用自定义值后不再自动更新)
1816
- */
1902
+ PendingRequestsManager.prototype.setupUnloadListener = function () {
1903
+ var _this = this;
1817
1904
 
1905
+ if (this.isUnloadListenerSetup) {
1906
+ return;
1907
+ }
1818
1908
 
1819
- _this.setPageKey = function (pageKey, autoUpdate) {
1820
- if (autoUpdate === void 0) {
1821
- autoUpdate = false;
1909
+ this.isUnloadListenerSetup = true;
1910
+ document.addEventListener("visibilitychange", function () {
1911
+ if (_this.isPageUnloading()) {
1912
+ _this.flushQueueWithBeacon();
1822
1913
  }
1914
+ });
1915
+ window.addEventListener("beforeunload", function () {
1916
+ _this.flushQueueWithBeacon();
1917
+ });
1918
+ window.addEventListener("pagehide", function () {
1919
+ _this.flushQueueWithBeacon();
1920
+ });
1921
+ };
1823
1922
 
1824
- if (pageKey === null || pageKey === '') {
1825
- // 恢复自动生成
1826
- _this.useCustomPageKey = false;
1827
- var pathname = window.location.pathname;
1828
- _this.pageKey = pathname.replace(/\//g, "_").substring(1);
1923
+ PendingRequestsManager.prototype.getQueueFromStorage = function () {
1924
+ try {
1925
+ var storedRequests = this.callbacks.getLocalStorage(PENDING_REQUESTS_STORAGE_KEY);
1829
1926
 
1830
- if (_this.initConfig.showLog) {
1831
- _this.printLog("\u9875\u9762\u6807\u8BC6\u5DF2\u6062\u590D\u81EA\u52A8\u751F\u6210: " + _this.pageKey);
1832
- }
1833
- } else {
1834
- _this.pageKey = pageKey;
1835
- _this.useCustomPageKey = !autoUpdate;
1927
+ if (storedRequests) {
1928
+ var parsedRequests = JSON.parse(storedRequests);
1836
1929
 
1837
- if (_this.initConfig.showLog) {
1838
- _this.printLog("\u9875\u9762\u6807\u8BC6\u5DF2\u8BBE\u7F6E\u4E3A: " + pageKey + ", \u81EA\u52A8\u66F4\u65B0: " + autoUpdate);
1930
+ if (Array.isArray(parsedRequests)) {
1931
+ return parsedRequests;
1839
1932
  }
1840
1933
  }
1841
- };
1842
- /**
1843
- * @description 获取当前页面唯一标识
1844
- * @returns 当前页面唯一标识
1845
- */
1846
-
1934
+ } catch (e) {
1935
+ this.callbacks.printLog("\u8BFB\u53D6\u5F85\u53D1\u9001\u8BF7\u6C42\u5931\u8D25: " + e);
1936
+ this.callbacks.setLocalStorage(PENDING_REQUESTS_STORAGE_KEY, "[]");
1937
+ }
1847
1938
 
1848
- _this.getPageKey = function () {
1849
- return _this.pageKey;
1850
- };
1939
+ return [];
1940
+ };
1851
1941
 
1852
- _this.onClickCallback = function (e) {
1853
- var _a;
1942
+ PendingRequestsManager.prototype.saveQueueToStorage = function (requests) {
1943
+ try {
1944
+ this.callbacks.setLocalStorage(PENDING_REQUESTS_STORAGE_KEY, JSON.stringify(requests));
1945
+ } catch (e) {
1946
+ this.callbacks.printLog("\u4FDD\u5B58\u5F85\u53D1\u9001\u8BF7\u6C42\u5230 LocalStorage \u5931\u8D25: " + e);
1947
+ }
1948
+ };
1854
1949
 
1855
- var target = e.target;
1856
- if (!((_a = target === null || target === void 0 ? void 0 : target.dataset) === null || _a === void 0 ? void 0 : _a.partKey)) return;
1857
- var position = [e.pageX, e.pageY];
1858
- var id = target.id;
1859
- var className = target.className;
1860
- var nodeName = target.nodeName;
1861
- var targetEle = {
1862
- id: id,
1863
- nodeName: nodeName,
1864
- className: className,
1865
- position: position
1866
- };
1950
+ PendingRequestsManager.prototype.isPageUnloading = function () {
1951
+ return document.visibilityState === "hidden";
1952
+ };
1867
1953
 
1868
- var params = _this.getParams({
1869
- event: "WebClick",
1870
- desc: _this.eventDescMap["WebClick"],
1871
- itemKey: _this.getItemKey(target.dataset.partKey),
1872
- privateParamMap: {
1873
- targetEle: targetEle,
1874
- pointerType: e.pointerType,
1875
- currentUrl: _this.currentUrl,
1876
- elementSelector: _this.getDomSelector(target) || ""
1877
- }
1954
+ PendingRequestsManager.prototype.sendWithBeacon = function (params, serverUrl, contentType) {
1955
+ try {
1956
+ var blob = new Blob([JSON.stringify(params)], {
1957
+ type: contentType || "application/json"
1878
1958
  });
1879
-
1880
- _this.sendData(params);
1881
- };
1882
- /**
1883
- * @description 路由触发事件
1884
- */
1885
-
1886
-
1887
- _this.onPageViewCallback = function (e) {
1888
- var _a, _b; // 在路由变化前,先发送待发送的数据(避免被取消)
1889
-
1890
-
1891
- var pendingRequests = _this.getPendingRequestsFromStorage();
1892
-
1893
- var batchQueue = _this.initConfig.batchSend ? _this.getBatchQueueFromStorage() : [];
1894
-
1895
- if (pendingRequests.length > 0 || batchQueue.length > 0) {
1896
- _this.flushPendingData();
1959
+ return navigator.sendBeacon(serverUrl, blob);
1960
+ } catch (e) {
1961
+ if (this.config.showLog) {
1962
+ this.callbacks.printLog("sendBeacon \u53D1\u9001\u5931\u8D25: " + e);
1897
1963
  }
1898
1964
 
1899
- var ORGIN = window.location.origin;
1900
-
1901
- var params = _this.getParams({
1902
- event: "PageView",
1903
- desc: _this.eventDescMap["PageView"],
1904
- privateParamMap: {
1905
- currentUrl: _this.currentUrl,
1906
- targetUrl: ((_a = e.arguments) === null || _a === void 0 ? void 0 : _a[2]) ? ORGIN + ((_b = e.arguments) === null || _b === void 0 ? void 0 : _b[2]) : null
1907
- }
1908
- });
1909
-
1910
- _this.currentUrl = window.location.href; // 如果使用自定义 pageKey,路由变化时不自动更新
1911
-
1912
- if (!_this.useCustomPageKey) {
1913
- _this.pageKey = window.location.pathname.replace(/\//g, "_").substring(1);
1914
- } // 路由变化时,如果启用了定时上报,需要重启定时器
1915
-
1965
+ return false;
1966
+ }
1967
+ };
1916
1968
 
1917
- if (_this.initConfig.autoTrackPageDurationInterval) {
1918
- _this.stopPageDurationTimer();
1969
+ return PendingRequestsManager;
1970
+ }();
1919
1971
 
1920
- _this.startPageDurationTimer();
1921
- }
1972
+ var WebTracking =
1973
+ /** @class */
1974
+ function (_super) {
1975
+ __extends(WebTracking, _super);
1922
1976
 
1923
- _this.sendRetained(e.type);
1977
+ function WebTracking() {
1978
+ var _this = _super.call(this) || this;
1924
1979
 
1925
- _this.sendData(params);
1980
+ _this.useCustomPageKey = false;
1981
+ _this.userInfo = null;
1982
+ _this.currentUrl = "";
1983
+ _this.pageKey = "";
1984
+ _this.deviceId = "";
1985
+ _this.eventDescMap = {
1986
+ PageView: "Web 浏览页面",
1987
+ WebClick: "Web 元素点击",
1988
+ PageRetained: "Web 页面浏览时长",
1989
+ CustomTrack: "Web 自定义代码上报",
1990
+ WebExposure: "Web 元素曝光"
1926
1991
  };
1927
1992
 
1928
- _this.getParams = function (_a) {
1929
- var event = _a.event,
1930
- desc = _a.desc,
1931
- _b = _a.privateParamMap,
1932
- privateParamMap = _b === void 0 ? {} : _b,
1933
- itemKey = _a.itemKey;
1934
- var business = _this.initConfig.business;
1935
- var pageWidth = window.innerWidth;
1936
- var pageHeight = window.innerHeight;
1937
- var screenWidth = window.screen.width;
1938
- var screenHeight = window.screen.height; // 过滤敏感数据
1939
-
1940
- var filteredBusiness = _this.filterSensitiveData(business || {});
1941
-
1942
- var filteredUserInfo = _this.filterSensitiveData(_this.userInfo || {});
1943
-
1944
- var filteredPrivateParamMap = _this.filterSensitiveData(privateParamMap || {});
1945
-
1946
- var filteredUrlParams = _this.filterSensitiveData(_this.getQueryValue() || {}); // 创建私有参数对象
1947
-
1948
-
1949
- var privateParamMapData = {
1950
- currentUrl: filteredPrivateParamMap.currentUrl || _this.currentUrl,
1951
- business: Object.assign({}, filteredBusiness, filteredPrivateParamMap.business || {}),
1952
- pageWidth: pageWidth,
1953
- pageHeight: pageHeight,
1954
- screenWidth: screenWidth,
1955
- screenHeight: screenHeight,
1956
- sdkVersion: _this.sdkVersion,
1957
- systemsInfo: _this.systemsInfo,
1958
- urlParams: filteredUrlParams,
1959
- userInfo: filteredUserInfo,
1960
- deviceId: _this.deviceId // 添加设备ID
1993
+ _this.init = function (initParams) {
1994
+ _this.preset(initParams);
1961
1995
 
1962
- }; // 添加其他可能的属性
1996
+ var pathname = window.location.pathname;
1997
+ _this.currentUrl = window.location.href;
1963
1998
 
1964
- if (filteredPrivateParamMap.targetEle) {
1965
- privateParamMapData.targetEle = filteredPrivateParamMap.targetEle;
1999
+ if (initParams.pageKey) {
2000
+ _this.pageKey = initParams.pageKey;
2001
+ _this.useCustomPageKey = true;
2002
+ } else {
2003
+ _this.pageKey = pathname.replace(/\//g, "_").substring(1);
2004
+ _this.useCustomPageKey = false;
1966
2005
  }
1967
2006
 
1968
- if (filteredPrivateParamMap.targetUrl) {
1969
- privateParamMapData.targetUrl = filteredPrivateParamMap.targetUrl;
1970
- }
2007
+ _this.systemsInfo = _this.getSystemsInfo(initParams.platform);
2008
+ _this.deviceId = _this.deviceManager.getDeviceId();
1971
2009
 
1972
- if (filteredPrivateParamMap.pointerType) {
1973
- privateParamMapData.pointerType = filteredPrivateParamMap.pointerType;
2010
+ if (initParams.userInfo && _this.isObject(initParams.userInfo)) {
2011
+ _this.userInfo = initParams.userInfo;
1974
2012
  }
1975
2013
 
1976
- if (filteredPrivateParamMap.elementSelector) {
1977
- privateParamMapData.elementSelector = filteredPrivateParamMap.elementSelector;
1978
- }
2014
+ _this.setCookie("retainedStartTime", _this.getTimeStamp());
1979
2015
 
1980
- if (filteredPrivateParamMap.retainedDuration) {
1981
- privateParamMapData.retainedDuration = filteredPrivateParamMap.retainedDuration;
2016
+ if (_this.initConfig.batchSend) {
2017
+ _this.batchSender.restoreQueue();
1982
2018
  }
1983
2019
 
1984
- return {
1985
- event: event,
1986
- desc: desc,
1987
- itemKey: itemKey || _this.getItemKey(),
1988
- requestTime: _this.getTimeStamp(),
1989
- privateParamMap: privateParamMapData
1990
- };
1991
- };
1992
- /**
1993
- * 数据采样判断
1994
- * @returns 是否应该采样
1995
- */
2020
+ _this.pendingRequestsManager.restoreQueue();
1996
2021
 
2022
+ _this.pendingRequestsManager.setupUnloadListener();
1997
2023
 
1998
- _this.shouldSample = function () {
1999
- var sampleRate = _this.initConfig.sampleRate;
2000
- if (sampleRate >= 1) return true;
2001
- if (sampleRate <= 0) return false;
2002
- return Math.random() < sampleRate;
2003
- };
2004
- /**
2005
- * 批量发送数据
2006
- */
2007
-
2008
-
2009
- _this.flushBatchQueue = function () {
2010
- var batchQueue = _this.getBatchQueueFromStorage();
2011
-
2012
- if (batchQueue.length === 0) return;
2013
-
2014
- var currentTime = _this.getTimeStamp(); // 过滤出可以发送的数据(未到重试时间的不发送)
2015
-
2016
-
2017
- var readyToSend = batchQueue.filter(function (item) {
2018
- if (!item._nextRetryTime) {
2019
- return true;
2020
- }
2024
+ var autoTrackConfig = _this.getAutoTrackConfig();
2021
2025
 
2022
- return item._nextRetryTime <= currentTime;
2023
- });
2026
+ _this.currentAutoTrackConfig = autoTrackConfig;
2024
2027
 
2025
- if (readyToSend.length === 0) {
2026
- if (_this.initConfig.showLog) {
2027
- _this.printLog("\u6279\u91CF\u961F\u5217\u4E2D\u6709 " + batchQueue.length + " \u6761\u6570\u636E\u7B49\u5F85\u91CD\u8BD5");
2028
- }
2029
-
2030
- return;
2031
- } // 从队列中移除已准备发送的数据
2028
+ if (autoTrackConfig.retained) {
2029
+ _this.pageDurationTracker.start();
2030
+ }
2032
2031
 
2032
+ if (autoTrackConfig.exposure) {
2033
+ _this.exposureTracker.init();
2034
+ }
2035
+ };
2033
2036
 
2034
- var remainingQueue = batchQueue.filter(function (item) {
2035
- if (!item._nextRetryTime) {
2036
- return false;
2037
+ _this.preset = function (presetParams) {
2038
+ if (presetParams instanceof Object) {
2039
+ if (presetParams.pageKey !== undefined) {
2040
+ if (presetParams.pageKey === null || presetParams.pageKey === "") {
2041
+ _this.useCustomPageKey = false;
2042
+ var pathname = window.location.pathname;
2043
+ _this.pageKey = pathname.replace(/\//g, "_").substring(1);
2044
+ } else {
2045
+ _this.pageKey = presetParams.pageKey;
2046
+ _this.useCustomPageKey = true;
2047
+ }
2037
2048
  }
2038
2049
 
2039
- return item._nextRetryTime > currentTime;
2040
- }); // 保存剩余的队列
2041
-
2042
- _this.saveBatchQueueToStorage(remainingQueue); // 发送批量数据
2043
-
2044
-
2045
- _this.sendBatchData(readyToSend);
2046
- };
2047
- /**
2048
- * 发送批量数据
2049
- * @param data 批量数据
2050
- */
2051
-
2050
+ _this.each(presetParams, function (val, key) {
2051
+ if (key === "pageKey") return;
2052
2052
 
2053
- _this.sendBatchData = function (data) {
2054
- var _a = _this.initConfig,
2055
- serverUrl = _a.serverUrl,
2056
- contentType = _a.contentType,
2057
- showLog = _a.showLog,
2058
- sendMethod = _a.sendMethod,
2059
- initHeader = _a.header;
2053
+ if (key === "autoTrack" && _typeof(val) === "object" && val !== null) {
2054
+ var newAutoTrackConfig = val;
2055
+ _this.initConfig.autoTrack = __assign(__assign({}, _this.currentAutoTrackConfig), newAutoTrackConfig);
2056
+ _this.currentAutoTrackConfig = _this.getAutoTrackConfig();
2057
+ return;
2058
+ }
2060
2059
 
2061
- if (showLog) {
2062
- _this.printLog("\u6279\u91CF\u53D1\u9001 " + data.length + " \u6761\u6570\u636E");
2060
+ if (_this.initConfig.hasOwnProperty(key)) {
2061
+ var validationResult = _this.validateConfigParam(String(key), val);
2063
2062
 
2064
- data.forEach(function (item) {
2065
- return _this.printLog(item);
2063
+ if (validationResult.valid) {
2064
+ _this.initConfig[key] = val;
2065
+ } else {
2066
+ console.warn("\u914D\u7F6E\u53C2\u6570\u9A8C\u8BC1\u5931\u8D25: " + String(key) + " = " + val + ", \u539F\u56E0: " + validationResult.message);
2067
+ }
2068
+ }
2066
2069
  });
2067
- } // 判断是否使用 sendBeacon
2068
-
2070
+ }
2069
2071
 
2070
- var shouldUseBeacon = _this.shouldUseBeacon(sendMethod, undefined, initHeader); // 如果使用 sendBeacon
2072
+ if (!/^(((ht|f)tps?):\/\/)?[\w\-]+(\.[\w\-]+)+([\w\-\.,@?^=%&:/~+#\(\)]*[\w\-\.,@?^=%&/~+#\(\)])?$/.test(_this.initConfig["serverUrl"])) {
2073
+ console.warn("当前 server_url 为空或不正确,只在控制台打印日志,network 中不会发数据,请配置正确的 server_url!");
2074
+ }
2071
2075
 
2076
+ if (!!_this.initConfig["autoTrack"] || !!_this.initConfig["trackPartKeyClick"]) {
2077
+ _this.listener();
2078
+ } else {
2079
+ _this.unlistener();
2080
+ }
2072
2081
 
2073
- if (shouldUseBeacon) {
2074
- try {
2075
- var blob = new Blob([JSON.stringify(data)], {
2076
- type: contentType || "application/json"
2077
- });
2078
- var sent = navigator.sendBeacon(serverUrl, blob);
2082
+ _this.batchSender.updateConfig({
2083
+ batchSend: _this.initConfig.batchSend,
2084
+ batchInterval: _this.initConfig.batchInterval,
2085
+ batchMaxSize: _this.initConfig.batchMaxSize,
2086
+ sendTimeout: _this.initConfig.sendTimeout,
2087
+ serverUrl: _this.initConfig.serverUrl,
2088
+ contentType: _this.initConfig.contentType,
2089
+ showLog: _this.initConfig.showLog,
2090
+ sendMethod: _this.initConfig.sendMethod,
2091
+ header: _this.initConfig.header
2092
+ });
2079
2093
 
2080
- if (sent) {
2081
- // 发送成功,确保 LocalStorage 已清空
2082
- _this.saveBatchQueueToStorage([]);
2094
+ _this.pendingRequestsManager.updateConfig({
2095
+ pendingRequestsMaxSize: _this.initConfig.pendingRequestsMaxSize,
2096
+ sendTimeout: _this.initConfig.sendTimeout,
2097
+ serverUrl: _this.initConfig.serverUrl,
2098
+ contentType: _this.initConfig.contentType,
2099
+ showLog: _this.initConfig.showLog,
2100
+ header: _this.initConfig.header
2101
+ });
2083
2102
 
2084
- if (showLog) {
2085
- _this.printLog("\u6279\u91CF\u53D1\u9001\u6210\u529F: " + data.length + " \u6761\u6570\u636E");
2086
- }
2087
- } else {
2088
- // sendBeacon 返回 false,重新加入队列以便重试
2089
- _this.printLog("\u6279\u91CF\u53D1\u9001\u5931\u8D25: sendBeacon \u8FD4\u56DE false\uFF0C\u6570\u636E\u5DF2\u91CD\u65B0\u52A0\u5165\u961F\u5217");
2103
+ var autoTrackConfig = _this.getAutoTrackConfig();
2090
2104
 
2091
- _this.retryBatchData(data);
2092
- }
2093
- } catch (e) {
2094
- // sendBeacon 失败,重新加入队列以便重试
2095
- _this.printLog("\u6279\u91CF\u53D1\u9001\u5931\u8D25: " + e + "\uFF0C\u6570\u636E\u5DF2\u91CD\u65B0\u52A0\u5165\u961F\u5217");
2105
+ if (autoTrackConfig.retained) {
2106
+ _this.pageDurationTracker.updateConfig({
2107
+ pageDurationInterval: _this.initConfig.pageDurationInterval,
2108
+ showLog: _this.initConfig.showLog
2109
+ });
2096
2110
 
2097
- _this.retryBatchData(data);
2098
- }
2111
+ _this.pageDurationTracker.start();
2099
2112
  } else {
2100
- // 使用 XMLHttpRequest 发送
2101
- _this.ajax({
2102
- url: serverUrl,
2103
- type: "POST",
2104
- data: JSON.stringify({
2105
- events: data
2106
- }),
2107
- contentType: contentType,
2108
- credentials: false,
2109
- timeout: _this.initConfig.sendTimeout,
2110
- cors: true,
2111
- success: function success() {
2112
- // 批量发送成功,确保 LocalStorage 已清空
2113
- // flushBatchQueue 在发送前已清空 LocalStorage,这里再次确认
2114
- _this.saveBatchQueueToStorage([]);
2115
-
2116
- if (_this.initConfig.showLog) {
2117
- _this.printLog("\u6279\u91CF\u53D1\u9001\u6210\u529F: " + data.length + " \u6761\u6570\u636E");
2118
- }
2119
- },
2120
- error: function error(err) {
2121
- // 批量发送失败,重新加入队列以便重试
2122
- _this.printLog("\u6279\u91CF\u53D1\u9001\u5931\u8D25: " + err + "\uFF0C\u6570\u636E\u5DF2\u91CD\u65B0\u52A0\u5165\u961F\u5217");
2113
+ _this.pageDurationTracker.stop();
2114
+ }
2123
2115
 
2124
- _this.retryBatchData(data);
2125
- }
2116
+ if (autoTrackConfig.exposure) {
2117
+ _this.exposureTracker.updateConfig({
2118
+ autoTrackExposure: true,
2119
+ exposureThreshold: _this.initConfig.exposureThreshold,
2120
+ exposureTime: _this.initConfig.exposureTime,
2121
+ exposureNum: _this.initConfig.exposureNum,
2122
+ showLog: _this.initConfig.showLog
2126
2123
  });
2124
+
2125
+ _this.exposureTracker.init();
2126
+ } else {
2127
+ _this.exposureTracker.stop();
2127
2128
  }
2128
2129
  };
2129
- /**
2130
- * @description 批量数据重试逻辑
2131
- * @param data 批量数据
2132
- */
2133
-
2134
-
2135
- _this.retryBatchData = function (data) {
2136
- // 获取当前队列
2137
- var currentQueue = _this.getBatchQueueFromStorage(); // 去重:基于事件类型、itemKey、requestTime 生成唯一键
2138
2130
 
2131
+ _this.validateConfigParam = function (key, value) {
2132
+ switch (key) {
2133
+ case "sampleRate":
2134
+ if (typeof value !== "number" || value < 0 || value > 1) {
2135
+ return {
2136
+ valid: false,
2137
+ message: "sampleRate 必须是 0-1 之间的数字"
2138
+ };
2139
+ }
2139
2140
 
2140
- var getEventKey = function getEventKey(item) {
2141
- return item.event + "_" + item.itemKey + "_" + item.requestTime;
2142
- };
2143
-
2144
- var existingKeys = new Set(currentQueue.map(getEventKey));
2145
- var maxRetryCount = 3; // 最大重试次数
2141
+ break;
2146
2142
 
2147
- var currentTime = _this.getTimeStamp(); // 过滤并更新重试信息
2143
+ case "sendTimeout":
2144
+ if (typeof value !== "number" || value <= 0) {
2145
+ return {
2146
+ valid: false,
2147
+ message: "sendTimeout 必须是大于 0 的数字"
2148
+ };
2149
+ }
2148
2150
 
2151
+ break;
2149
2152
 
2150
- var retryData = data.filter(function (item) {
2151
- var key = getEventKey(item); // 检查是否已存在
2153
+ case "batchInterval":
2154
+ if (typeof value !== "number" || value <= 0) {
2155
+ return {
2156
+ valid: false,
2157
+ message: "batchInterval 必须是大于 0 的数字"
2158
+ };
2159
+ }
2152
2160
 
2153
- if (existingKeys.has(key)) {
2154
- return false;
2155
- } // 检查重试次数
2161
+ break;
2156
2162
 
2163
+ case "batchMaxSize":
2164
+ if (typeof value !== "number" || value <= 0 || !Number.isInteger(value)) {
2165
+ return {
2166
+ valid: false,
2167
+ message: "batchMaxSize 必须是大于 0 的整数"
2168
+ };
2169
+ }
2157
2170
 
2158
- var retryCount = (item._retryCount || 0) + 1;
2171
+ break;
2159
2172
 
2160
- if (retryCount > maxRetryCount) {
2161
- if (_this.initConfig.showLog) {
2162
- _this.printLog("\u6570\u636E\u5DF2\u8FBE\u5230\u6700\u5927\u91CD\u8BD5\u6B21\u6570\uFF0C\u653E\u5F03\u91CD\u8BD5: " + key);
2173
+ case "pendingRequestsMaxSize":
2174
+ if (typeof value !== "number" || value <= 0 || !Number.isInteger(value)) {
2175
+ return {
2176
+ valid: false,
2177
+ message: "pendingRequestsMaxSize 必须是大于 0 的整数"
2178
+ };
2163
2179
  }
2164
2180
 
2165
- return false;
2166
- } // 更新重试信息
2181
+ break;
2167
2182
 
2183
+ case "pageDurationInterval":
2184
+ if (typeof value !== "number" || value <= 0) {
2185
+ return {
2186
+ valid: false,
2187
+ message: "pageDurationInterval 必须是大于 0 的数字"
2188
+ };
2189
+ }
2168
2190
 
2169
- item._retryCount = retryCount; // 指数退避:2^retryCount * 1000ms
2191
+ break;
2170
2192
 
2171
- item._nextRetryTime = currentTime + Math.pow(2, retryCount) * 1000;
2172
- existingKeys.add(key);
2173
- return true;
2174
- }); // 将失败的数据重新加入队列(添加到队列前面,优先重试)
2193
+ case "sendMethod":
2194
+ if (typeof value !== "string" || !["auto", "xhr", "beacon"].includes(value)) {
2195
+ return {
2196
+ valid: false,
2197
+ message: "sendMethod 必须是 auto、xhr 或 beacon"
2198
+ };
2199
+ }
2175
2200
 
2176
- var retryQueue = __spreadArray(__spreadArray([], retryData), currentQueue); // 限制重试队列大小,避免内存溢出
2201
+ break;
2177
2202
 
2203
+ case "exposureThreshold":
2204
+ if (typeof value !== "number" || value < 0 || value > 1) {
2205
+ return {
2206
+ valid: false,
2207
+ message: "exposureThreshold 必须是 0-1 之间的数字"
2208
+ };
2209
+ }
2178
2210
 
2179
- var maxSize = _this.initConfig.batchMaxSize * 2;
2180
- var trimmedQueue = retryQueue.length > maxSize ? retryQueue.slice(0, maxSize) : retryQueue; // 保存失败的数据到 LocalStorage,确保数据不丢失
2211
+ break;
2181
2212
 
2182
- _this.saveBatchQueueToStorage(trimmedQueue);
2213
+ case "exposureTime":
2214
+ if (typeof value !== "number" || value <= 0) {
2215
+ return {
2216
+ valid: false,
2217
+ message: "exposureTime 必须是大于 0 的数字"
2218
+ };
2219
+ }
2183
2220
 
2184
- if (_this.initConfig.showLog) {
2185
- _this.printLog("\u5DF2\u5C06 " + retryData.length + " \u6761\u6570\u636E\u52A0\u5165\u91CD\u8BD5\u961F\u5217");
2186
- }
2187
- };
2188
- /**
2189
- * 添加到批量队列
2190
- * @param params 数据参数
2191
- */
2221
+ break;
2192
2222
 
2223
+ case "exposureNum":
2224
+ if (value !== undefined && (typeof value !== "number" || value <= 0 || !Number.isInteger(value))) {
2225
+ return {
2226
+ valid: false,
2227
+ message: "exposureNum 必须是大于 0 的整数或不限制"
2228
+ };
2229
+ }
2193
2230
 
2194
- _this.addToBatchQueue = function (params) {
2195
- var _a = _this.initConfig,
2196
- batchInterval = _a.batchInterval,
2197
- batchMaxSize = _a.batchMaxSize; // 数据采样判断(在添加到队列前判断)
2231
+ break;
2198
2232
 
2199
- if (!_this.shouldSample()) {
2200
- if (_this.initConfig.showLog) {
2201
- _this.printLog("数据已采样跳过(批量模式)");
2202
- }
2233
+ case "showLog":
2234
+ case "isTrackSinglePage":
2235
+ case "batchSend":
2236
+ if (typeof value !== "boolean") {
2237
+ return {
2238
+ valid: false,
2239
+ message: key + " \u5FC5\u987B\u662F\u5E03\u5C14\u503C"
2240
+ };
2241
+ }
2203
2242
 
2204
- return;
2205
- } // 从 LocalStorage 获取当前队列
2243
+ break;
2206
2244
 
2245
+ case "autoTrack":
2246
+ if (typeof value !== "boolean" && _typeof(value) !== "object") {
2247
+ return {
2248
+ valid: false,
2249
+ message: "autoTrack 必须是布尔值或对象"
2250
+ };
2251
+ }
2207
2252
 
2208
- var currentQueue = _this.getBatchQueueFromStorage(); // 添加新数据
2253
+ if (_typeof(value) === "object" && value !== null) {
2254
+ var autoTrackConfig = value;
2255
+ var validKeys = ["view", "click", "exposure", "retained"];
2209
2256
 
2257
+ for (var k in autoTrackConfig) {
2258
+ if (!validKeys.includes(k)) {
2259
+ return {
2260
+ valid: false,
2261
+ message: "autoTrack \u5BF9\u8C61\u4E2D\u5305\u542B\u65E0\u6548\u7684\u5C5E\u6027: " + k
2262
+ };
2263
+ }
2210
2264
 
2211
- currentQueue.push(params); // 保存到 LocalStorage
2265
+ if (typeof autoTrackConfig[k] !== "boolean") {
2266
+ return {
2267
+ valid: false,
2268
+ message: "autoTrack." + k + " \u5FC5\u987B\u662F\u5E03\u5C14\u503C"
2269
+ };
2270
+ }
2271
+ }
2272
+ }
2212
2273
 
2213
- _this.saveBatchQueueToStorage(currentQueue); // 如果队列达到最大数量,立即发送
2274
+ break;
2214
2275
 
2276
+ case "business":
2277
+ case "header":
2278
+ if (value !== null && _typeof(value) !== "object") {
2279
+ return {
2280
+ valid: false,
2281
+ message: key + " \u5FC5\u987B\u662F\u5BF9\u8C61\u6216 null"
2282
+ };
2283
+ }
2215
2284
 
2216
- if (currentQueue.length >= batchMaxSize) {
2217
- _this.flushBatchQueue();
2285
+ break;
2218
2286
 
2219
- return;
2220
- } // 设置定时发送
2287
+ case "contentType":
2288
+ if (value !== "application/json" && value !== "application/x-www-form-urlencoded") {
2289
+ return {
2290
+ valid: false,
2291
+ message: "contentType 必须是 application/json 或 application/x-www-form-urlencoded"
2292
+ };
2293
+ }
2221
2294
 
2295
+ break;
2222
2296
 
2223
- if (!_this.batchTimer) {
2224
- _this.batchTimer = window.setTimeout(function () {
2225
- _this.flushBatchQueue();
2297
+ case "platform":
2298
+ if (typeof value !== "string") {
2299
+ return {
2300
+ valid: false,
2301
+ message: "platform 必须是字符串"
2302
+ };
2303
+ }
2226
2304
 
2227
- _this.batchTimer = null;
2228
- }, batchInterval);
2305
+ break;
2229
2306
  }
2307
+
2308
+ return {
2309
+ valid: true
2310
+ };
2230
2311
  };
2231
- /**
2232
- * 从 LocalStorage 恢复批量队列
2233
- */
2234
2312
 
2313
+ _this.getAutoTrackConfig = function () {
2314
+ var _a, _b, _c, _d;
2235
2315
 
2236
- _this.restoreBatchQueueFromStorage = function () {
2237
- var batchQueue = _this.getBatchQueueFromStorage();
2316
+ var autoTrack = _this.initConfig.autoTrack;
2238
2317
 
2239
- if (batchQueue.length > 0) {
2240
- if (_this.initConfig.showLog) {
2241
- _this.printLog("\u4ECE LocalStorage \u6062\u590D " + batchQueue.length + " \u6761\u5F85\u53D1\u9001\u6570\u636E");
2242
- } // 恢复后立即尝试发送(如果达到条件)
2318
+ if (typeof autoTrack === "boolean") {
2319
+ return {
2320
+ view: autoTrack,
2321
+ click: autoTrack,
2322
+ exposure: autoTrack,
2323
+ retained: autoTrack
2324
+ };
2325
+ }
2243
2326
 
2327
+ if (_typeof(autoTrack) === "object" && autoTrack !== null) {
2328
+ return {
2329
+ view: (_a = autoTrack.view) !== null && _a !== void 0 ? _a : false,
2330
+ click: (_b = autoTrack.click) !== null && _b !== void 0 ? _b : false,
2331
+ exposure: (_c = autoTrack.exposure) !== null && _c !== void 0 ? _c : false,
2332
+ retained: (_d = autoTrack.retained) !== null && _d !== void 0 ? _d : false
2333
+ };
2334
+ }
2244
2335
 
2245
- var batchMaxSize = _this.initConfig.batchMaxSize;
2336
+ return {
2337
+ view: false,
2338
+ click: false,
2339
+ exposure: false,
2340
+ retained: false
2341
+ };
2342
+ };
2246
2343
 
2247
- if (batchQueue.length >= batchMaxSize) {
2248
- _this.flushBatchQueue();
2249
- } else {
2250
- // 设置定时发送
2251
- var batchInterval = _this.initConfig.batchInterval;
2344
+ _this.login = function (userInfo) {
2345
+ if (_this.isObject(userInfo)) _this.userInfo = userInfo;
2346
+ };
2252
2347
 
2253
- if (!_this.batchTimer) {
2254
- _this.batchTimer = window.setTimeout(function () {
2255
- _this.flushBatchQueue();
2348
+ _this.getDeviceId = function () {
2349
+ return _this.deviceManager.getDeviceId();
2350
+ };
2256
2351
 
2257
- _this.batchTimer = null;
2258
- }, batchInterval);
2259
- }
2260
- }
2261
- }
2352
+ _this.resetDeviceId = function () {
2353
+ return _this.deviceManager.resetDeviceId();
2262
2354
  };
2263
- /**
2264
- * 添加到待发送请求队列
2265
- * @param params 数据参数
2266
- */
2267
2355
 
2356
+ _this.track = function (_a) {
2357
+ var desc = _a.desc,
2358
+ pageKey = _a.pageKey,
2359
+ partKey = _a.partKey,
2360
+ business = _a.business,
2361
+ header = _a.header;
2268
2362
 
2269
- _this.addToPendingRequests = function (params) {
2270
- // 从 LocalStorage 获取当前队列
2271
- var currentRequests = _this.getPendingRequestsFromStorage(); // 添加新数据
2363
+ var params = _this.getParams({
2364
+ desc: desc,
2365
+ event: "CustomTrack",
2366
+ itemKey: _this.getItemKey(partKey, pageKey),
2367
+ privateParamMap: {
2368
+ business: business
2369
+ }
2370
+ });
2272
2371
 
2372
+ return _this.sendData(params, header);
2373
+ };
2273
2374
 
2274
- currentRequests.push(params); // 限制队列大小,防止内存溢出
2375
+ _this.listener = function () {
2376
+ _this.unlistener();
2275
2377
 
2276
- var maxSize = _this.initConfig.pendingRequestsMaxSize || _this.DEFAULT_PENDING_REQUESTS_MAX_SIZE;
2378
+ var autoTrackConfig = _this.getAutoTrackConfig();
2277
2379
 
2278
- if (currentRequests.length > maxSize) {
2279
- var trimmedRequests = currentRequests.slice(-maxSize);
2380
+ if (autoTrackConfig.view) {
2381
+ if (!!_this.initConfig.isTrackSinglePage) {
2382
+ _this.rewriteHistory();
2280
2383
 
2281
- if (_this.initConfig.showLog) {
2282
- _this.printLog("\u5F85\u53D1\u9001\u8BF7\u6C42\u961F\u5217\u5DF2\u6EE1\uFF0C\u5DF2\u79FB\u9664\u6700\u65E7\u7684\u6570\u636E\uFF08\u6700\u5927\u9650\u5236: " + maxSize + "\uFF09");
2384
+ _this.addSinglePageEvent(_this.onPageViewCallback);
2283
2385
  }
2284
2386
 
2285
- _this.savePendingRequestsToStorage(trimmedRequests);
2286
- } else {
2287
- _this.savePendingRequestsToStorage(currentRequests);
2387
+ _this.each(["load", "beforeunload"], function (historyType) {
2388
+ _this.addEventListener(window, historyType, _this.onPageViewCallback);
2389
+ });
2390
+ }
2391
+
2392
+ if (autoTrackConfig.click) {
2393
+ _this.addEventListener(window, "click", _this.onClickCallback);
2288
2394
  }
2289
2395
  };
2290
- /**
2291
- * 从 LocalStorage 恢复待发送请求
2292
- */
2293
2396
 
2397
+ _this.unlistener = function () {
2398
+ if (!!_this.initConfig.isTrackSinglePage) {
2399
+ var historyPushState = window.history.pushState;
2400
+ var singlePageEvent = !!historyPushState ? "popstate" : "hashchange";
2401
+
2402
+ _this.each(["pushState", "replaceState", singlePageEvent], function (historyName) {
2403
+ _this.removeEventListener(window, historyName, _this.onPageViewCallback);
2404
+ });
2405
+ }
2406
+
2407
+ _this.each(["load", "beforeunload"], function (historyType) {
2408
+ _this.removeEventListener(window, historyType, _this.onPageViewCallback);
2409
+ });
2294
2410
 
2295
- _this.restorePendingRequestsFromStorage = function () {
2296
- var pendingRequests = _this.getPendingRequestsFromStorage();
2411
+ _this.removeEventListener(window, "click", _this.onClickCallback);
2297
2412
 
2298
- if (pendingRequests.length > 0) {
2299
- if (_this.initConfig.showLog) {
2300
- _this.printLog("\u4ECE LocalStorage \u6062\u590D " + pendingRequests.length + " \u6761\u5F85\u53D1\u9001\u8BF7\u6C42");
2301
- } // 注意:恢复后不立即发送,避免重复
2302
- // 数据会留在 LocalStorage 中,等待下次正常发送或页面卸载时发送
2303
- // 这样可以避免与批量队列冲突
2413
+ _this.batchSender.clearTimer();
2304
2414
 
2305
- }
2415
+ _this.pageDurationTracker.stop();
2306
2416
  };
2307
- /**
2308
- * 检查页面是否即将卸载
2309
- * @returns 如果页面即将卸载返回 true,否则返回 false
2310
- */
2311
2417
 
2418
+ _this.clearBatchTimer = function () {
2419
+ _this.batchSender.clearTimer();
2420
+ };
2312
2421
 
2313
- _this.isPageUnloading = function () {
2314
- return document.visibilityState === "hidden";
2422
+ _this.clearBatchQueue = function () {
2423
+ _this.batchSender.clearQueue();
2315
2424
  };
2316
- /**
2317
- * 使用 sendBeacon 发送数据(页面卸载时的备用方案)
2318
- * @param params 数据参数
2319
- * @param serverUrl 服务器地址
2320
- * @param contentType 内容类型
2321
- * @returns 是否发送成功
2322
- */
2323
2425
 
2426
+ _this.setPageKey = function (pageKey, autoUpdate) {
2427
+ if (autoUpdate === void 0) {
2428
+ autoUpdate = false;
2429
+ }
2430
+
2431
+ if (pageKey === null || pageKey === "") {
2432
+ _this.useCustomPageKey = false;
2433
+ var pathname = window.location.pathname;
2434
+ _this.pageKey = pathname.replace(/\//g, "_").substring(1);
2324
2435
 
2325
- _this.sendWithBeacon = function (params, serverUrl, contentType) {
2326
- try {
2327
- var blob = new Blob([JSON.stringify(params)], {
2328
- type: contentType || "application/json"
2329
- });
2330
- return navigator.sendBeacon(serverUrl, blob);
2331
- } catch (e) {
2332
2436
  if (_this.initConfig.showLog) {
2333
- _this.printLog("sendBeacon \u53D1\u9001\u5931\u8D25: " + e);
2437
+ _this.printLog("\u9875\u9762\u6807\u8BC6\u5DF2\u6062\u590D\u81EA\u52A8\u751F\u6210: " + _this.pageKey);
2334
2438
  }
2439
+ } else {
2440
+ _this.pageKey = pageKey;
2441
+ _this.useCustomPageKey = !autoUpdate;
2335
2442
 
2336
- return false;
2443
+ if (_this.initConfig.showLog) {
2444
+ _this.printLog("\u9875\u9762\u6807\u8BC6\u5DF2\u8BBE\u7F6E\u4E3A: " + pageKey + ", \u81EA\u52A8\u66F4\u65B0: " + autoUpdate);
2445
+ }
2337
2446
  }
2338
2447
  };
2339
- /**
2340
- * 刷新待发送的单个请求(正常情况下的发送)
2341
- * 注意:这个方法会直接使用 ajax 发送,避免通过 sendData 导致重复
2342
- */
2343
2448
 
2449
+ _this.getPageKey = function () {
2450
+ return _this.pageKey;
2451
+ };
2344
2452
 
2345
- _this.flushPendingRequests = function () {
2346
- var pendingRequests = _this.getPendingRequestsFromStorage();
2453
+ _this.onClickCallback = function (e) {
2454
+ var _a;
2347
2455
 
2348
- if (pendingRequests.length === 0) {
2349
- return;
2350
- } // 清除 LocalStorage 中的待发送请求
2456
+ var target = e.target;
2457
+ if (!((_a = target === null || target === void 0 ? void 0 : target.dataset) === null || _a === void 0 ? void 0 : _a.partKey)) return;
2458
+ var position = [e.pageX, e.pageY];
2459
+ var id = target.id;
2460
+ var className = target.className;
2351
2461
 
2462
+ var business = _this.extractDataAttributes(target);
2352
2463
 
2353
- _this.savePendingRequestsToStorage([]); // 直接使用 ajax 发送每个请求,避免通过 sendData 导致重复
2464
+ var desc = target.getAttribute("data-desc") || _this.eventDescMap["WebClick"];
2354
2465
 
2466
+ var partKey = target.getAttribute("data-part-key");
2467
+ var pageKey = target.getAttribute("data-page-key") || undefined;
2355
2468
 
2356
- var _a = _this.initConfig,
2357
- serverUrl = _a.serverUrl,
2358
- sendTimeout = _a.sendTimeout,
2359
- contentType = _a.contentType,
2360
- showLog = _a.showLog,
2361
- initHeader = _a.header;
2362
- pendingRequests.forEach(function (params) {
2363
- // 数据采样判断
2364
- if (!_this.shouldSample()) {
2365
- if (showLog) {
2366
- _this.printLog("待发送请求已采样跳过");
2469
+ var params = _this.getParams({
2470
+ event: "WebClick",
2471
+ desc: desc,
2472
+ itemKey: _this.getItemKey(partKey, pageKey),
2473
+ privateParamMap: {
2474
+ business: business,
2475
+ targetEle: {
2476
+ nodeName: target.nodeName,
2477
+ id: id,
2478
+ className: className,
2479
+ position: position
2367
2480
  }
2368
-
2369
- return;
2370
2481
  }
2371
-
2372
- if (showLog) {
2373
- _this.printLog(params);
2374
- } // 直接使用 ajax 发送
2375
-
2376
-
2377
- _this.ajax({
2378
- header: initHeader,
2379
- url: serverUrl,
2380
- type: "POST",
2381
- data: JSON.stringify(params),
2382
- contentType: contentType,
2383
- credentials: false,
2384
- timeout: sendTimeout,
2385
- cors: true,
2386
- success: function success() {
2387
- if (showLog) {
2388
- _this.printLog("待发送请求发送成功");
2389
- }
2390
- },
2391
- error: function error(err) {
2392
- if (showLog) {
2393
- _this.printLog("\u5F85\u53D1\u9001\u8BF7\u6C42\u53D1\u9001\u5931\u8D25\uFF08\u4E0D\u518D\u91CD\u8BD5\uFF09: " + err);
2394
- }
2395
- }
2396
- });
2397
2482
  });
2398
- };
2399
- /**
2400
- * 设置页面卸载监听器,确保数据发送
2401
- */
2402
2483
 
2484
+ _this.sendData(params);
2485
+ };
2403
2486
 
2404
- _this.setupBeforeUnloadListener = function () {
2405
- // 避免重复设置监听器
2406
- if (_this.isUnloadListenerSetup) {
2407
- return;
2408
- }
2487
+ _this.onPageViewCallback = function (e) {
2488
+ var _a, _b;
2409
2489
 
2410
- _this.isUnloadListenerSetup = true; // 使用 visibilitychange 事件(更可靠,支持页面跳转、切换标签页等场景)
2490
+ var ORGIN = window.location.origin;
2411
2491
 
2412
- document.addEventListener("visibilitychange", function () {
2413
- if (_this.isPageUnloading()) {
2414
- _this.flushPendingData();
2492
+ var params = _this.getParams({
2493
+ event: "PageView",
2494
+ desc: _this.eventDescMap["PageView"],
2495
+ privateParamMap: {
2496
+ currentUrl: _this.currentUrl,
2497
+ targetUrl: ((_a = e.arguments) === null || _a === void 0 ? void 0 : _a[2]) ? ORGIN + ((_b = e.arguments) === null || _b === void 0 ? void 0 : _b[2]) : null
2415
2498
  }
2416
- }); // 使用 beforeunload 事件作为备用(页面关闭/刷新)
2417
-
2418
- window.addEventListener("beforeunload", function () {
2419
- _this.flushPendingData();
2420
- }); // 使用 pagehide 事件(更可靠,支持移动端)
2421
-
2422
- window.addEventListener("pagehide", function () {
2423
- _this.flushPendingData();
2424
2499
  });
2425
- }; // 标记是否正在刷新待发送数据,避免重复发送
2426
-
2427
2500
 
2428
- _this.isFlushingPendingData = false;
2429
- /**
2430
- * 刷新待发送数据(在页面卸载/跳转时调用)
2431
- */
2501
+ _this.currentUrl = window.location.href;
2432
2502
 
2433
- _this.flushPendingData = function () {
2434
- // 如果正在刷新,避免重复执行
2435
- if (_this.isFlushingPendingData) {
2436
- return;
2437
- } // 页面卸载时停止定时器
2503
+ if (!_this.useCustomPageKey) {
2504
+ _this.pageKey = window.location.pathname.replace(/\//g, "_").substring(1);
2505
+ }
2438
2506
 
2507
+ var autoTrackConfig = _this.getAutoTrackConfig();
2439
2508
 
2440
- _this.stopPageDurationTimer(); // 收集所有待发送的数据
2509
+ if (autoTrackConfig.retained) {
2510
+ _this.pageDurationTracker.stop();
2441
2511
 
2512
+ _this.pageDurationTracker.start();
2513
+ }
2442
2514
 
2443
- var allPendingData = []; // 如果有批量队列,添加到待发送列表
2515
+ _this.sendData(params);
2516
+ };
2444
2517
 
2445
- var batchQueue = _this.getBatchQueueFromStorage();
2518
+ _this.getParams = function (_a) {
2519
+ var event = _a.event,
2520
+ desc = _a.desc,
2521
+ _b = _a.privateParamMap,
2522
+ privateParamMap = _b === void 0 ? {} : _b,
2523
+ itemKey = _a.itemKey;
2524
+ var business = _this.initConfig.business;
2525
+ var pageWidth = window.innerWidth;
2526
+ var pageHeight = window.innerHeight;
2527
+ var screenWidth = window.screen.width;
2528
+ var screenHeight = window.screen.height;
2446
2529
 
2447
- if (batchQueue.length > 0) {
2448
- allPendingData.push.apply(allPendingData, batchQueue);
2449
- } // 如果有待发送的单个请求,也添加到列表
2530
+ var filteredBusiness = _this.filterSensitiveData(business || {});
2450
2531
 
2532
+ var filteredUserInfo = _this.filterSensitiveData(_this.userInfo || {});
2451
2533
 
2452
- var pendingRequests = _this.getPendingRequestsFromStorage();
2534
+ var filteredPrivateParamMap = _this.filterSensitiveData(privateParamMap || {});
2453
2535
 
2454
- if (pendingRequests.length > 0) {
2455
- allPendingData.push.apply(allPendingData, pendingRequests);
2456
- }
2536
+ var filteredUrlParams = _this.filterSensitiveData(_this.getQueryValue() || {});
2457
2537
 
2458
- if (allPendingData.length === 0) {
2459
- return;
2460
- } // 标记正在刷新
2538
+ var privateParamMapData = {
2539
+ currentUrl: filteredPrivateParamMap.currentUrl || _this.currentUrl,
2540
+ business: Object.assign({}, filteredBusiness, filteredPrivateParamMap.business || {}),
2541
+ pageWidth: pageWidth,
2542
+ pageHeight: pageHeight,
2543
+ screenWidth: screenWidth,
2544
+ screenHeight: screenHeight,
2545
+ sdkVersion: _this.sdkVersion,
2546
+ systemsInfo: _this.systemsInfo,
2547
+ urlParams: filteredUrlParams,
2548
+ userInfo: filteredUserInfo,
2549
+ deviceId: _this.deviceId
2550
+ };
2461
2551
 
2552
+ if (filteredPrivateParamMap.targetEle) {
2553
+ privateParamMapData.targetEle = filteredPrivateParamMap.targetEle;
2554
+ }
2462
2555
 
2463
- _this.isFlushingPendingData = true; // 先保存到 LocalStorage,确保数据不丢失(在发送前保存)
2556
+ if (filteredPrivateParamMap.targetUrl) {
2557
+ privateParamMapData.targetUrl = filteredPrivateParamMap.targetUrl;
2558
+ }
2464
2559
 
2465
- try {
2466
- if (_this.initConfig.batchSend) {
2467
- _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, JSON.stringify(allPendingData));
2468
- } else {
2469
- _this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, JSON.stringify(allPendingData));
2470
- }
2471
- } catch (e) {
2472
- if (_this.initConfig.showLog) {
2473
- _this.printLog("\u4FDD\u5B58\u5F85\u53D1\u9001\u8BF7\u6C42\u5230 LocalStorage \u5931\u8D25: " + e);
2474
- }
2475
- } // 使用 sendBeacon 发送数据(最可靠的方式)
2560
+ if (filteredPrivateParamMap.pointerType) {
2561
+ privateParamMapData.pointerType = filteredPrivateParamMap.pointerType;
2562
+ }
2476
2563
 
2564
+ if (filteredPrivateParamMap.elementSelector) {
2565
+ privateParamMapData.elementSelector = filteredPrivateParamMap.elementSelector;
2566
+ }
2477
2567
 
2478
- if (navigator.sendBeacon && _this.initConfig.serverUrl) {
2479
- try {
2480
- // 如果只有一条数据,直接发送;否则批量发送
2481
- var dataToSend = allPendingData.length === 1 ? allPendingData[0] : allPendingData;
2482
- var blob = new Blob([JSON.stringify(dataToSend)], {
2483
- type: _this.initConfig.contentType || "application/json"
2484
- });
2485
- var sent = navigator.sendBeacon(_this.initConfig.serverUrl, blob);
2568
+ if (filteredPrivateParamMap.retainedDuration) {
2569
+ privateParamMapData.retainedDuration = filteredPrivateParamMap.retainedDuration;
2570
+ }
2486
2571
 
2487
- if (sent) {
2488
- // 发送成功,清除所有 LocalStorage
2489
- _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
2572
+ if (filteredPrivateParamMap.exposureScreenNo !== undefined) {
2573
+ privateParamMapData.exposureScreenNo = filteredPrivateParamMap.exposureScreenNo;
2574
+ }
2490
2575
 
2491
- _this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, "[]");
2576
+ return {
2577
+ event: event,
2578
+ desc: desc,
2579
+ itemKey: itemKey || _this.getItemKey(),
2580
+ requestTime: _this.getTimeStamp(),
2581
+ privateParamMap: privateParamMapData
2582
+ };
2583
+ };
2492
2584
 
2493
- if (_this.initConfig.showLog) {
2494
- _this.printLog("\u9875\u9762\u5378\u8F7D\u65F6\u6210\u529F\u53D1\u9001 " + allPendingData.length + " \u6761\u6570\u636E");
2495
- }
2496
- } else {
2497
- // sendBeacon 返回 false,数据已在 LocalStorage 中,等待下次恢复
2498
- if (_this.initConfig.showLog) {
2499
- _this.printLog("sendBeacon \u8FD4\u56DE false\uFF0C\u6570\u636E\u5DF2\u4FDD\u5B58\u5230 LocalStorage \u7B49\u5F85\u4E0B\u6B21\u6062\u590D");
2500
- }
2501
- }
2502
- } catch (e) {
2503
- // sendBeacon 失败,数据已在 LocalStorage 中,等待下次恢复
2504
- if (_this.initConfig.showLog) {
2505
- _this.printLog("\u9875\u9762\u5378\u8F7D\u65F6\u53D1\u9001\u6570\u636E\u5931\u8D25: " + e + "\uFF0C\u6570\u636E\u5DF2\u4FDD\u5B58\u5230 LocalStorage");
2506
- }
2507
- } finally {
2508
- // 重置标记
2509
- _this.isFlushingPendingData = false;
2510
- }
2511
- } else {
2512
- // 不支持 sendBeacon,数据已在 LocalStorage 中,等待下次恢复
2513
- if (_this.initConfig.showLog) {
2514
- _this.printLog("\u4E0D\u652F\u6301 sendBeacon\uFF0C\u6570\u636E\u5DF2\u4FDD\u5B58\u5230 LocalStorage \u7B49\u5F85\u4E0B\u6B21\u6062\u590D");
2515
- } // 重置标记
2585
+ _this.shouldSample = function () {
2586
+ var sampleRate = _this.initConfig.sampleRate;
2587
+ if (sampleRate >= 1) return true;
2588
+ if (sampleRate <= 0) return false;
2589
+ return Math.random() < sampleRate;
2590
+ };
2516
2591
 
2592
+ _this.shouldUseBeacon = function (sendMethod, header, initHeader) {
2593
+ if (sendMethod === "beacon") return true;
2594
+ if (sendMethod === "xhr") return false;
2517
2595
 
2518
- _this.isFlushingPendingData = false;
2596
+ if (header || initHeader) {
2597
+ return false;
2519
2598
  }
2520
- };
2521
- /**
2522
- * 发送数据通用函数
2523
- */
2524
2599
 
2600
+ return typeof navigator.sendBeacon === "function";
2601
+ };
2525
2602
 
2526
2603
  _this.sendData = function (params, header) {
2527
- // 数据采样判断
2528
- if (!_this.shouldSample()) {
2529
- return Promise.resolve({
2530
- success: true,
2531
- message: "数据已采样跳过"
2532
- });
2533
- }
2604
+ return new Promise(function (resolve, reject) {
2605
+ if (!_this.shouldSample()) {
2606
+ if (_this.initConfig.showLog) {
2607
+ _this.printLog("数据已采样跳过");
2608
+ }
2534
2609
 
2535
- var _a = _this.initConfig,
2536
- serverUrl = _a.serverUrl,
2537
- sendTimeout = _a.sendTimeout,
2538
- contentType = _a.contentType,
2539
- showLog = _a.showLog,
2540
- initHeader = _a.header,
2541
- batchSend = _a.batchSend,
2542
- sendMethod = _a.sendMethod;
2543
- if (!!showLog) _this.printLog(params); // 如果启用批量发送
2610
+ resolve({
2611
+ success: false,
2612
+ message: "数据已采样跳过"
2613
+ });
2614
+ return;
2615
+ }
2544
2616
 
2545
- if (batchSend) {
2546
- _this.addToBatchQueue(params);
2617
+ if (_this.initConfig.showLog) {
2618
+ _this.printLog(params);
2619
+ }
2547
2620
 
2548
- return Promise.resolve({
2549
- success: true,
2550
- message: "已添加到批量队列"
2551
- });
2552
- } // 判断是否使用 sendBeacon
2621
+ if (_this.initConfig.batchSend) {
2622
+ _this.batchSender.addToQueue(params);
2553
2623
 
2624
+ resolve({
2625
+ success: true,
2626
+ message: "数据已加入批量队列"
2627
+ });
2628
+ return;
2629
+ }
2554
2630
 
2555
- var shouldUseBeacon = _this.shouldUseBeacon(sendMethod, header, initHeader); // 如果使用 sendBeacon
2631
+ var _a = _this.initConfig,
2632
+ serverUrl = _a.serverUrl,
2633
+ sendTimeout = _a.sendTimeout,
2634
+ contentType = _a.contentType,
2635
+ showLog = _a.showLog,
2636
+ initHeader = _a.header;
2556
2637
 
2638
+ var useBeacon = _this.shouldUseBeacon(_this.initConfig.sendMethod, header, initHeader);
2557
2639
 
2558
- if (shouldUseBeacon) {
2559
- // 检查页面是否即将卸载,如果是,直接使用 sendBeacon 发送,避免被取消
2560
- if (_this.isPageUnloading()) {
2640
+ if (useBeacon) {
2561
2641
  var sent = _this.sendWithBeacon(params, serverUrl, contentType);
2562
2642
 
2563
2643
  if (sent) {
2564
- return Promise.resolve({
2644
+ resolve({
2565
2645
  success: true,
2566
- message: "页面卸载时发送成功"
2646
+ message: "数据发送成功"
2567
2647
  });
2568
2648
  } else {
2569
- // sendBeacon 返回 false,添加到待发送队列
2570
- _this.addToPendingRequests(params);
2649
+ _this.pendingRequestsManager.addToQueue(params);
2571
2650
 
2572
- return Promise.resolve({
2573
- success: true,
2574
- message: "已添加到待发送队列"
2651
+ resolve({
2652
+ success: false,
2653
+ message: "sendBeacon 发送失败,数据已加入待发送队列"
2575
2654
  });
2576
2655
  }
2577
- } // 正常情况使用 sendBeacon
2578
-
2579
-
2580
- return _this.sendBeacon({
2581
- contentType: contentType,
2582
- url: serverUrl,
2583
- data: params
2584
- }).catch(function (err) {
2585
- // sendBeacon 失败,添加到待发送队列,避免数据丢失
2586
- _this.addToPendingRequests(params);
2587
-
2588
- return Promise.resolve({
2589
- success: true,
2590
- message: "sendBeacon 失败,已添加到待发送队列"
2591
- });
2592
- });
2593
- } else {
2594
- // 使用 XMLHttpRequest 发送
2595
- return new Promise(function (resolve, reject) {
2596
- // 如果页面即将卸载且配置为 auto,尝试使用 sendBeacon 作为备用
2597
- if (_this.isPageUnloading() && sendMethod === 'auto' && _this.isSupportBeaconSend() && !header && !initHeader) {
2598
- var sent = _this.sendWithBeacon(params, serverUrl, contentType);
2599
-
2600
- if (sent) {
2601
- resolve({
2602
- success: true,
2603
- message: "页面卸载时使用 sendBeacon 发送成功"
2604
- });
2605
- return;
2606
- } // sendBeacon 失败,继续使用 XMLHttpRequest
2607
-
2608
- }
2609
-
2656
+ } else {
2610
2657
  _this.ajax({
2611
- header: header || initHeader,
2612
2658
  url: serverUrl,
2613
2659
  type: "POST",
2614
2660
  data: JSON.stringify(params),
2615
2661
  contentType: contentType,
2662
+ header: header || initHeader,
2616
2663
  credentials: false,
2617
2664
  timeout: sendTimeout,
2618
2665
  cors: true,
2619
- success: function success(res) {
2620
- return resolve({
2666
+ success: function success(data) {
2667
+ if (showLog) {
2668
+ _this.printLog("数据发送成功", data);
2669
+ }
2670
+
2671
+ resolve({
2621
2672
  success: true,
2622
- data: res
2673
+ data: data
2623
2674
  });
2624
2675
  },
2625
- error: function error(err, status) {
2626
- // 如果请求失败且页面即将卸载且配置为 auto,尝试使用 sendBeacon
2627
- if (_this.isPageUnloading() && sendMethod === 'auto' && _this.isSupportBeaconSend() && !header && !initHeader) {
2628
- var sent = _this.sendWithBeacon(params, serverUrl, contentType);
2629
-
2630
- if (sent) {
2631
- resolve({
2632
- success: true,
2633
- message: "XMLHttpRequest 失败,已使用 sendBeacon 发送"
2634
- });
2635
- return;
2636
- }
2676
+ error: function error(err) {
2677
+ if (showLog) {
2678
+ _this.printLog("数据发送失败", err);
2637
2679
  }
2638
2680
 
2681
+ _this.pendingRequestsManager.addToQueue(params);
2682
+
2639
2683
  reject({
2640
2684
  success: false,
2641
- message: String(err),
2642
- code: status
2685
+ message: "数据发送失败",
2686
+ error: err
2643
2687
  });
2644
2688
  }
2645
2689
  });
2646
- });
2647
- }
2648
- };
2649
- /**
2650
- * @description 判断是否应该使用 sendBeacon
2651
- * @param sendMethod 配置的发送方式
2652
- * @param header 自定义 header
2653
- * @param initHeader 初始化配置的 header
2654
- * @returns 是否使用 sendBeacon
2655
- */
2656
-
2657
-
2658
- _this.shouldUseBeacon = function (sendMethod, header, initHeader) {
2659
- // 如果配置为 xhr,不使用 beacon
2660
- if (sendMethod === 'xhr') {
2661
- return false;
2662
- } // 如果配置为 beacon,检查是否支持
2663
-
2664
-
2665
- if (sendMethod === 'beacon') {
2666
- return _this.isSupportBeaconSend() === true;
2667
- } // 如果配置为 auto(默认),使用原有逻辑
2668
- // 只有在支持 sendBeacon 且没有自定义 header 时才使用
2669
-
2670
-
2671
- return _this.isSupportBeaconSend() === true && !header && !initHeader;
2672
- };
2673
- /**
2674
- * @description 留存时长上报
2675
- * @param type
2676
- */
2677
-
2678
-
2679
- _this.sendRetained = function (type) {
2680
- var params = _this.getParams({
2681
- event: "PageRetained",
2682
- desc: _this.eventDescMap["PageRetained"]
2690
+ }
2683
2691
  });
2692
+ };
2684
2693
 
2685
- if (["beforeunload", "pushState", "replaceState", "hashchange", "popstate"].indexOf(type) >= 0) {
2686
- var __time = _this.getCookie("retainedStartTime");
2687
-
2688
- var retainedStartTime = __time ? +__time : _this.getTimeStamp();
2689
-
2690
- var retainedData = __assign(__assign({}, params), {
2691
- privateParamMap: __assign(__assign({}, params.privateParamMap), {
2692
- retainedDuration: Math.max(params.requestTime - retainedStartTime, 0)
2693
- })
2694
+ _this.sendWithBeacon = function (params, serverUrl, contentType) {
2695
+ try {
2696
+ var blob = new Blob([JSON.stringify(params)], {
2697
+ type: contentType || "application/json"
2694
2698
  });
2699
+ return navigator.sendBeacon(serverUrl, blob);
2700
+ } catch (e) {
2701
+ if (_this.initConfig.showLog) {
2702
+ _this.printLog("sendBeacon \u53D1\u9001\u5931\u8D25: " + e);
2703
+ }
2695
2704
 
2696
- _this.sendData(retainedData);
2697
-
2698
- _this.setCookie("retainedStartTime", _this.getTimeStamp());
2705
+ return false;
2699
2706
  }
2700
2707
  };
2701
- /**
2702
- * @description 用户主动上报页面停留时长
2703
- * @param duration 自定义停留时长(毫秒),如果不传则自动计算从页面加载(或上次调用)到当前的时长
2704
- * @param options 可选参数,包括自定义描述、业务参数等
2705
- * @param resetStartTime 是否重置起始时间,默认 true(手动上报后重置,定时上报不重置)
2706
- * @returns Promise<TrackingResponse> 上报结果
2707
- */
2708
-
2709
2708
 
2710
2709
  _this.trackPageDuration = function (duration, options, resetStartTime) {
2711
2710
  if (resetStartTime === void 0) {
2712
2711
  resetStartTime = true;
2713
- } // 计算停留时长
2714
-
2715
-
2716
- var retainedDuration;
2717
-
2718
- if (duration !== undefined && duration !== null) {
2719
- // 使用自定义时长
2720
- retainedDuration = Math.max(duration, 0);
2721
- } else {
2722
- // 自动计算时长
2723
- var __time = _this.getCookie("retainedStartTime");
2724
-
2725
- var retainedStartTime = __time ? +__time : _this.getTimeStamp();
2726
-
2727
- var currentTime = _this.getTimeStamp();
2728
-
2729
- retainedDuration = Math.max(currentTime - retainedStartTime, 0);
2730
- } // 构建参数
2712
+ }
2731
2713
 
2714
+ var retainedDuration = _this.pageDurationTracker.calculateDuration(duration);
2732
2715
 
2733
2716
  var desc = (options === null || options === void 0 ? void 0 : options.desc) || _this.eventDescMap["PageRetained"];
2734
2717
  var pageKey = (options === null || options === void 0 ? void 0 : options.pageKey) || _this.pageKey;
@@ -2743,12 +2726,9 @@ function (_super) {
2743
2726
  business: business,
2744
2727
  retainedDuration: retainedDuration
2745
2728
  }
2746
- }); // 上报数据
2747
-
2748
-
2749
- var result = _this.sendData(params, header); // 根据 resetStartTime 参数决定是否重置起始时间
2750
- // 手动上报后重置起始时间,定时上报不重置(累积计算)
2729
+ });
2751
2730
 
2731
+ var result = _this.sendData(params, header);
2752
2732
 
2753
2733
  if (resetStartTime) {
2754
2734
  _this.setCookie("retainedStartTime", _this.getTimeStamp());
@@ -2756,230 +2736,41 @@ function (_super) {
2756
2736
 
2757
2737
  return result;
2758
2738
  };
2759
- /**
2760
- * @description 启动定时上报页面停留时长的定时器
2761
- */
2762
-
2763
-
2764
- _this.startPageDurationTimer = function () {
2765
- // 先停止现有的定时器(避免重复启动)
2766
- _this.stopPageDurationTimer();
2767
-
2768
- var interval = _this.initConfig.pageDurationInterval || 30000; // 检查间隔时间是否有效
2769
-
2770
- if (interval <= 0) {
2771
- if (_this.initConfig.showLog) {
2772
- _this.printLog("定时上报间隔时间无效,已禁用定时上报");
2773
- }
2774
-
2775
- return;
2776
- } // 启动定时器
2777
-
2778
-
2779
- _this.pageDurationTimer = window.setInterval(function () {
2780
- // 只在页面可见时上报(避免后台上报)
2781
- if (document.visibilityState === "visible") {
2782
- // 定时上报:retainedDuration 直接使用上报间隔时间,数据工程师会清洗数据
2783
- _this.trackPageDuration(interval, {
2784
- desc: "定时上报页面停留时长",
2785
- business: {
2786
- reportType: "interval",
2787
- interval: interval
2788
- }
2789
- }, true).catch(function (err) {
2790
- if (_this.initConfig.showLog) {
2791
- _this.printLog("\u5B9A\u65F6\u4E0A\u62A5\u9875\u9762\u505C\u7559\u65F6\u957F\u5931\u8D25: " + err);
2792
- }
2793
- });
2794
- }
2795
- }, interval);
2796
-
2797
- if (_this.initConfig.showLog) {
2798
- _this.printLog("\u5B9A\u65F6\u4E0A\u62A5\u9875\u9762\u505C\u7559\u65F6\u957F\u5DF2\u542F\u52A8\uFF0C\u95F4\u9694: " + interval + "ms");
2799
- }
2800
- };
2801
- /**
2802
- * @description 停止定时上报页面停留时长的定时器
2803
- */
2804
2739
 
2740
+ _this.getItemKey = function (partKey, pageKey) {
2741
+ var _pageKey = pageKey !== undefined ? pageKey : _this.pageKey;
2805
2742
 
2806
- _this.stopPageDurationTimer = function () {
2807
- if (_this.pageDurationTimer !== null) {
2808
- clearInterval(_this.pageDurationTimer);
2809
- _this.pageDurationTimer = null;
2743
+ var _partKey = partKey !== undefined ? partKey : "";
2810
2744
 
2811
- if (_this.initConfig.showLog) {
2812
- _this.printLog("定时上报页面停留时长已停止");
2813
- }
2745
+ if (_partKey) {
2746
+ return _this.initConfig.appKey + "." + _pageKey + "." + _partKey;
2814
2747
  }
2815
- };
2816
- /**
2817
- * @description 获取 itemKey
2818
- * @param {[string]} partkey [控件/自定义事件的唯一标识]
2819
- * @return {[string]}
2820
- */
2821
-
2822
2748
 
2823
- _this.getItemKey = function (partkey, pageKey) {
2824
- var appKey = _this.initConfig.appKey;
2825
- var keys = [appKey, (pageKey || _this.pageKey).toString(), partkey ? partkey.toString() : undefined].filter(function (key) {
2826
- return !!key;
2827
- });
2828
- return keys.reduce(function (str, key) {
2829
- return str + ("" + (str.length ? "." : "")) + key;
2830
- }, "");
2749
+ return _this.initConfig.appKey + "." + _pageKey;
2831
2750
  };
2832
- /**
2833
- * @description 从元素或其祖先节点提取 data-* 属性
2834
- * @param element 目标元素
2835
- * @returns 提取的业务参数对象
2836
- */
2837
-
2838
2751
 
2839
2752
  _this.extractDataAttributes = function (element) {
2840
2753
  var business = {};
2841
- var currentElement = element;
2842
-
2843
- while (currentElement) {
2844
- var attributes = currentElement.attributes;
2845
-
2846
- for (var i = 0; i < attributes.length; i++) {
2847
- var attr = attributes[i];
2848
- var name_1 = attr.name;
2849
-
2850
- if (name_1.startsWith('data-') && name_1 !== 'data-exposure' && name_1 !== 'data-part-key' && name_1 !== 'data-desc') {
2851
- var value = attr.value;
2852
-
2853
- if (value) {
2854
- var camelCaseKey = name_1.replace(/^data-/, '').split('-').map(function (part, index) {
2855
- return index === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1);
2856
- }).join('');
2857
- business[camelCaseKey] = value;
2858
- }
2859
- }
2860
- }
2861
2754
 
2862
- currentElement = currentElement.parentElement;
2755
+ for (var i = 0; i < element.attributes.length; i++) {
2756
+ var name_1 = element.attributes[i].name;
2863
2757
 
2864
- if (currentElement && currentElement.tagName === 'BODY') {
2865
- break;
2758
+ if (name_1.startsWith("data-") && name_1 !== "data-exposure" && name_1 !== "data-part-key" && name_1 !== "data-desc" && name_1 !== "data-page-key") {
2759
+ var key = name_1.replace("data-", "").replace(/-([a-z])/g, function (_, letter) {
2760
+ return letter.toUpperCase();
2761
+ });
2762
+ business[key] = element.getAttribute(name_1);
2866
2763
  }
2867
2764
  }
2868
2765
 
2869
2766
  return business;
2870
2767
  };
2871
- /**
2872
- * @description 初始化曝光监听
2873
- */
2874
-
2875
-
2876
- _this.initExposureObserver = function () {
2877
- if (!_this.initConfig.autoTrackExposure) {
2878
- return;
2879
- }
2880
-
2881
- if (!('IntersectionObserver' in window)) {
2882
- if (_this.initConfig.showLog) {
2883
- _this.printLog('当前浏览器不支持 IntersectionObserver,无法启用曝光埋点');
2884
- }
2885
-
2886
- return;
2887
- }
2888
-
2889
- var threshold = _this.initConfig.exposureThreshold || 0.5;
2890
- _this.exposureObserver = new IntersectionObserver(function (entries) {
2891
- entries.forEach(function (entry) {
2892
- var element = entry.target;
2893
-
2894
- var elementInfo = _this.exposureElementsMap.get(element);
2895
-
2896
- if (!elementInfo) {
2897
- return;
2898
- }
2899
-
2900
- var exposureTime = _this.initConfig.exposureTime || 500;
2901
-
2902
- if (entry.isIntersecting) {
2903
- elementInfo.isVisible = true;
2904
- elementInfo.visibleStartTime = _this.getTimeStamp();
2905
-
2906
- if (elementInfo.exposureTimer) {
2907
- clearTimeout(elementInfo.exposureTimer);
2908
- }
2909
-
2910
- elementInfo.exposureTimer = window.setTimeout(function () {
2911
- if (elementInfo.isVisible) {
2912
- _this.reportExposure(element);
2913
- }
2914
- }, exposureTime);
2915
- } else {
2916
- elementInfo.isVisible = false;
2917
-
2918
- if (elementInfo.exposureTimer) {
2919
- clearTimeout(elementInfo.exposureTimer);
2920
- elementInfo.exposureTimer = null;
2921
- }
2922
- }
2923
- });
2924
- }, {
2925
- threshold: threshold
2926
- });
2927
-
2928
- _this.observeExposureElements();
2929
-
2930
- _this.initMutationObserver();
2931
- };
2932
- /**
2933
- * @description 添加单个曝光元素到监听
2934
- * @param element 曝光元素
2935
- */
2936
-
2937
-
2938
- _this.addExposureElement = function (element) {
2939
- if (!_this.exposureElementsMap.has(element)) {
2940
- _this.exposureElementsMap.set(element, {
2941
- element: element,
2942
- visibleStartTime: 0,
2943
- exposureCount: 0,
2944
- isVisible: false,
2945
- exposureTimer: null
2946
- });
2947
-
2948
- if (_this.exposureObserver) {
2949
- _this.exposureObserver.observe(element);
2950
- }
2951
- }
2952
- };
2953
- /**
2954
- * @description 监听页面上的曝光元素
2955
- */
2956
-
2957
-
2958
- _this.observeExposureElements = function () {
2959
- if (!_this.exposureObserver) {
2960
- return;
2961
- }
2962
-
2963
- var elements = document.querySelectorAll('[data-exposure="true"]');
2964
- elements.forEach(function (element) {
2965
- _this.addExposureElement(element);
2966
- });
2967
-
2968
- if (_this.initConfig.showLog && elements.length > 0) {
2969
- _this.printLog("\u5DF2\u76D1\u542C " + elements.length + " \u4E2A\u66DD\u5149\u5143\u7D20");
2970
- }
2971
- };
2972
- /**
2973
- * @description 上报曝光事件
2974
- * @param element 曝光元素
2975
- */
2976
-
2977
2768
 
2978
- _this.reportExposure = function (element) {
2979
- var elementInfo = _this.exposureElementsMap.get(element);
2769
+ _this.handleExposureReport = function (element, exposureScreenIndex) {
2770
+ var elementInfo = _this.exposureTracker.getElementInfo(element);
2980
2771
 
2981
2772
  if (!elementInfo) {
2982
- return;
2773
+ return Promise.resolve();
2983
2774
  }
2984
2775
 
2985
2776
  var exposureNum = _this.initConfig.exposureNum;
@@ -2989,34 +2780,33 @@ function (_super) {
2989
2780
  _this.printLog("\u5143\u7D20\u5DF2\u8FBE\u5230\u6700\u5927\u66DD\u5149\u6B21\u6570\u9650\u5236: " + exposureNum);
2990
2781
  }
2991
2782
 
2992
- return;
2783
+ return Promise.resolve();
2993
2784
  }
2994
2785
 
2995
2786
  var business = _this.extractDataAttributes(element);
2996
2787
 
2997
- var desc = element.getAttribute('data-desc') || _this.eventDescMap['WebExposure'];
2788
+ var desc = element.getAttribute("data-desc") || _this.eventDescMap["WebExposure"];
2998
2789
 
2999
- var partkey = element.getAttribute('data-part-key') || 'exposure';
2790
+ var partKey = element.getAttribute("data-part-key") || "exposure";
2791
+ var pageKey = element.getAttribute("data-page-key") || undefined;
3000
2792
 
3001
2793
  var params = _this.getParams({
3002
- event: 'WebExposure',
2794
+ event: "WebExposure",
3003
2795
  desc: desc,
3004
- itemKey: _this.getItemKey(partkey),
2796
+ itemKey: _this.getItemKey(partKey, pageKey),
3005
2797
  privateParamMap: {
3006
- business: business
2798
+ business: business,
2799
+ exposureScreenNo: exposureScreenIndex
3007
2800
  }
3008
2801
  });
3009
2802
 
3010
- _this.sendData(params).then(function () {
3011
- elementInfo.exposureCount++;
2803
+ return _this.sendData(params).then(function () {
2804
+ _this.exposureTracker.incrementExposureCount(element);
3012
2805
 
3013
- if (elementInfo.exposureTimer) {
3014
- clearTimeout(elementInfo.exposureTimer);
3015
- elementInfo.exposureTimer = null;
3016
- }
2806
+ _this.exposureTracker.clearExposureTimer(element);
3017
2807
 
3018
2808
  if (_this.initConfig.showLog) {
3019
- _this.printLog("\u66DD\u5149\u4E0A\u62A5\u6210\u529F\uFF0C\u5F53\u524D\u66DD\u5149\u6B21\u6570: " + elementInfo.exposureCount);
2809
+ _this.printLog("\u66DD\u5149\u4E0A\u62A5\u6210\u529F\uFF0C\u5F53\u524D\u66DD\u5149\u6B21\u6570: " + (elementInfo.exposureCount + 1));
3020
2810
  }
3021
2811
  }).catch(function (err) {
3022
2812
  if (_this.initConfig.showLog) {
@@ -3024,81 +2814,8 @@ function (_super) {
3024
2814
  }
3025
2815
  });
3026
2816
  };
3027
- /**
3028
- * @description 初始化 MutationObserver 监听动态添加的元素
3029
- */
3030
-
3031
-
3032
- _this.initMutationObserver = function () {
3033
- if (!('MutationObserver' in window)) {
3034
- if (_this.initConfig.showLog) {
3035
- _this.printLog('当前浏览器不支持 MutationObserver,无法监听动态添加的曝光元素');
3036
- }
3037
-
3038
- return;
3039
- }
3040
-
3041
- _this.mutationObserver = new MutationObserver(function (mutations) {
3042
- mutations.forEach(function (mutation) {
3043
- mutation.addedNodes.forEach(function (node) {
3044
- if (node.nodeType === Node.ELEMENT_NODE) {
3045
- var element = node;
3046
-
3047
- if (element.hasAttribute('data-exposure') && element.getAttribute('data-exposure') === 'true') {
3048
- _this.addExposureElement(element);
3049
- } else {
3050
- var exposureElements = element.querySelectorAll('[data-exposure="true"]');
3051
- exposureElements.forEach(function (exposureElement) {
3052
- _this.addExposureElement(exposureElement);
3053
- });
3054
- }
3055
- }
3056
- });
3057
- });
3058
- });
3059
-
3060
- _this.mutationObserver.observe(document.body, {
3061
- childList: true,
3062
- subtree: true
3063
- });
3064
-
3065
- if (_this.initConfig.showLog) {
3066
- _this.printLog('MutationObserver 已启动,监听动态添加的曝光元素');
3067
- }
3068
- };
3069
- /**
3070
- * @description 停止曝光监听
3071
- */
3072
-
3073
-
3074
- _this.stopExposureObserver = function () {
3075
- if (_this.exposureObserver) {
3076
- _this.exposureObserver.disconnect();
3077
-
3078
- _this.exposureObserver = null;
3079
-
3080
- _this.exposureElementsMap.forEach(function (elementInfo) {
3081
- if (elementInfo.exposureTimer) {
3082
- clearTimeout(elementInfo.exposureTimer);
3083
- }
3084
- });
3085
-
3086
- _this.exposureElementsMap.clear();
3087
- }
3088
-
3089
- if (_this.mutationObserver) {
3090
- _this.mutationObserver.disconnect();
3091
-
3092
- _this.mutationObserver = null;
3093
- }
3094
-
3095
- if (_this.initConfig.showLog) {
3096
- _this.printLog('曝光监听已停止');
3097
- }
3098
- };
3099
-
3100
- _this.sdkVersion = "1.2.4"; // sdk版本
3101
2817
 
2818
+ _this.sdkVersion = "1.2.7";
3102
2819
  _this.initConfig = {
3103
2820
  appKey: "",
3104
2821
  platform: undefined,
@@ -3114,26 +2831,93 @@ function (_super) {
3114
2831
  batchSend: false,
3115
2832
  batchInterval: 5000,
3116
2833
  batchMaxSize: 10,
3117
- trackPartKeyClick: false,
3118
2834
  pendingRequestsMaxSize: 50,
3119
- autoTrackPageDurationInterval: false,
3120
2835
  pageDurationInterval: 30000,
3121
2836
  sendMethod: "auto",
3122
- autoTrackExposure: false,
3123
2837
  exposureThreshold: 0.5,
3124
2838
  exposureTime: 500,
3125
- exposureNum: undefined // 同一元素允许上报的最大曝光次数,不限制
3126
-
3127
- }; // 系统信息
3128
-
2839
+ exposureNum: undefined
2840
+ };
3129
2841
  _this.systemsInfo = {};
2842
+ _this.deviceManager = new DeviceManager({
2843
+ getCookie: _this.getCookie.bind(_this),
2844
+ setCookie: _this.setCookie.bind(_this),
2845
+ getLocalStorage: _this.getLocalStorage.bind(_this),
2846
+ setLocalStorage: _this.setLocalStorage.bind(_this),
2847
+ collectFingerprint: _this.collectFingerprint.bind(_this),
2848
+ hashFingerprint: _this.hashFingerprint.bind(_this)
2849
+ });
2850
+ _this.batchSender = new BatchSender({
2851
+ batchSend: false,
2852
+ batchInterval: 5000,
2853
+ batchMaxSize: 10,
2854
+ sendTimeout: 3000,
2855
+ serverUrl: "",
2856
+ contentType: "application/json",
2857
+ showLog: false,
2858
+ sendMethod: "auto",
2859
+ header: undefined
2860
+ }, {
2861
+ getLocalStorage: _this.getLocalStorage.bind(_this),
2862
+ setLocalStorage: _this.setLocalStorage.bind(_this),
2863
+ getTimeStamp: _this.getTimeStamp.bind(_this),
2864
+ ajax: _this.ajax.bind(_this),
2865
+ shouldSample: _this.shouldSample.bind(_this),
2866
+ shouldUseBeacon: _this.shouldUseBeacon.bind(_this),
2867
+ printLog: _this.printLog.bind(_this)
2868
+ });
2869
+ _this.pendingRequestsManager = new PendingRequestsManager({
2870
+ pendingRequestsMaxSize: 50,
2871
+ sendTimeout: 3000,
2872
+ serverUrl: "",
2873
+ contentType: "application/json",
2874
+ showLog: false,
2875
+ header: undefined
2876
+ }, {
2877
+ getLocalStorage: _this.getLocalStorage.bind(_this),
2878
+ setLocalStorage: _this.setLocalStorage.bind(_this),
2879
+ shouldSample: _this.shouldSample.bind(_this),
2880
+ ajax: _this.ajax.bind(_this),
2881
+ printLog: _this.printLog.bind(_this)
2882
+ });
2883
+ _this.pageDurationTracker = new PageDurationTracker({
2884
+ pageDurationInterval: 30000,
2885
+ showLog: false
2886
+ }, {
2887
+ getCookie: _this.getCookie.bind(_this),
2888
+ setCookie: _this.setCookie.bind(_this),
2889
+ getTimeStamp: _this.getTimeStamp.bind(_this),
2890
+ track: _this.trackPageDuration.bind(_this),
2891
+ printLog: _this.printLog.bind(_this)
2892
+ });
2893
+ _this.exposureTracker = new ExposureTracker({
2894
+ autoTrackExposure: false,
2895
+ exposureThreshold: 0.5,
2896
+ exposureTime: 500,
2897
+ exposureNum: undefined,
2898
+ showLog: false
2899
+ }, {
2900
+ reportExposure: _this.handleExposureReport.bind(_this),
2901
+ printLog: _this.printLog.bind(_this),
2902
+ getTimeStamp: _this.getTimeStamp.bind(_this),
2903
+ extractDataAttributes: _this.extractDataAttributes.bind(_this)
2904
+ });
3130
2905
  return _this;
3131
2906
  }
3132
- /**
3133
- * @description 添加单页面监听事件
3134
- * @param callback
3135
- */
3136
2907
 
2908
+ WebTracking.prototype.printLog = function () {
2909
+ var rest = [];
2910
+
2911
+ for (var _i = 0; _i < arguments.length; _i++) {
2912
+ rest[_i] = arguments[_i];
2913
+ }
2914
+
2915
+ if (!this.initConfig.showLog) {
2916
+ return;
2917
+ }
2918
+
2919
+ _super.prototype.printLog.apply(this, rest);
2920
+ };
3137
2921
 
3138
2922
  WebTracking.prototype.addSinglePageEvent = function (callback) {
3139
2923
  var _this = this;