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