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

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