@fle-sdk/event-tracking-web 1.2.1 → 1.2.3
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/README.md +246 -2
- package/lib/index.esm.js +1002 -217
- package/lib/index.esm.min.js +1 -1
- package/lib/index.js +2395 -1610
- package/lib/index.min.js +1 -1
- package/lib/types/index.d.ts +94 -7
- package/lib/types/tools.d.ts +2 -0
- package/lib/types/type.d.ts +61 -0
- package/package.json +2 -3
package/lib/index.esm.js
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
function _typeof(obj) {
|
|
2
|
+
"@babel/helpers - typeof";
|
|
3
|
+
|
|
4
|
+
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
|
|
5
|
+
_typeof = function (obj) {
|
|
6
|
+
return typeof obj;
|
|
7
|
+
};
|
|
8
|
+
} else {
|
|
9
|
+
_typeof = function (obj) {
|
|
10
|
+
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return _typeof(obj);
|
|
15
|
+
}
|
|
16
|
+
|
|
1
17
|
/*! *****************************************************************************
|
|
2
18
|
Copyright (c) Microsoft Corporation.
|
|
3
19
|
|
|
@@ -46,22 +62,6 @@ function __spreadArray(to, from) {
|
|
|
46
62
|
return to;
|
|
47
63
|
}
|
|
48
64
|
|
|
49
|
-
function _typeof(obj) {
|
|
50
|
-
"@babel/helpers - typeof";
|
|
51
|
-
|
|
52
|
-
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
|
|
53
|
-
_typeof = function (obj) {
|
|
54
|
-
return typeof obj;
|
|
55
|
-
};
|
|
56
|
-
} else {
|
|
57
|
-
_typeof = function (obj) {
|
|
58
|
-
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return _typeof(obj);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
65
|
var WebTrackingTools =
|
|
66
66
|
/** @class */
|
|
67
67
|
function () {
|
|
@@ -444,7 +444,7 @@ function () {
|
|
|
444
444
|
|
|
445
445
|
this.filterSensitiveData = function (obj, sensitiveKeys) {
|
|
446
446
|
if (sensitiveKeys === void 0) {
|
|
447
|
-
sensitiveKeys = [
|
|
447
|
+
sensitiveKeys = ["password", "token", "secret", "key"];
|
|
448
448
|
}
|
|
449
449
|
|
|
450
450
|
if (!_this.isObject(obj)) return obj;
|
|
@@ -453,11 +453,11 @@ function () {
|
|
|
453
453
|
_this.each(obj, function (value, key) {
|
|
454
454
|
// 检查是否是敏感字段
|
|
455
455
|
var isSensitive = sensitiveKeys.some(function (sensitiveKey) {
|
|
456
|
-
return typeof key ===
|
|
456
|
+
return typeof key === "string" && key.toLowerCase().includes(sensitiveKey.toLowerCase());
|
|
457
457
|
});
|
|
458
458
|
|
|
459
459
|
if (isSensitive) {
|
|
460
|
-
filteredObj[key] =
|
|
460
|
+
filteredObj[key] = "***";
|
|
461
461
|
} else if (_this.isObject(value)) {
|
|
462
462
|
// 递归过滤嵌套对象
|
|
463
463
|
filteredObj[key] = _this.filterSensitiveData(value, sensitiveKeys);
|
|
@@ -476,9 +476,9 @@ function () {
|
|
|
476
476
|
|
|
477
477
|
|
|
478
478
|
this.xssFilter = function (str) {
|
|
479
|
-
if (!str) return
|
|
480
|
-
if (typeof str !==
|
|
481
|
-
return str.replace(/</g,
|
|
479
|
+
if (!str) return "";
|
|
480
|
+
if (typeof str !== "string") return str.toString();
|
|
481
|
+
return str.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\//g, "/");
|
|
482
482
|
};
|
|
483
483
|
/**
|
|
484
484
|
* 获取url中的参数
|
|
@@ -518,7 +518,7 @@ function () {
|
|
|
518
518
|
return {};
|
|
519
519
|
}
|
|
520
520
|
|
|
521
|
-
if (typeof data ===
|
|
521
|
+
if (typeof data === "string") {
|
|
522
522
|
try {
|
|
523
523
|
return JSON.parse(data);
|
|
524
524
|
} catch (e) {
|
|
@@ -526,7 +526,7 @@ function () {
|
|
|
526
526
|
}
|
|
527
527
|
}
|
|
528
528
|
|
|
529
|
-
if (_typeof(data) ===
|
|
529
|
+
if (_typeof(data) === "object") {
|
|
530
530
|
return data;
|
|
531
531
|
}
|
|
532
532
|
|
|
@@ -817,7 +817,7 @@ function () {
|
|
|
817
817
|
|
|
818
818
|
this.getDeviceId = function () {
|
|
819
819
|
// 获取已存储的设备ID
|
|
820
|
-
var storedDeviceId = _this.getCookie(
|
|
820
|
+
var storedDeviceId = _this.getCookie("device_id") || _this.getLocalStorage("device_id");
|
|
821
821
|
|
|
822
822
|
if (storedDeviceId) {
|
|
823
823
|
return storedDeviceId;
|
|
@@ -830,10 +830,10 @@ function () {
|
|
|
830
830
|
var deviceId = _this.hashFingerprint(fingerprint); // 存储设备ID
|
|
831
831
|
|
|
832
832
|
|
|
833
|
-
_this.setCookie(
|
|
833
|
+
_this.setCookie("device_id", deviceId, 365 * 2); // 存储2年
|
|
834
834
|
|
|
835
835
|
|
|
836
|
-
_this.setLocalStorage(
|
|
836
|
+
_this.setLocalStorage("device_id", deviceId);
|
|
837
837
|
|
|
838
838
|
return deviceId;
|
|
839
839
|
};
|
|
@@ -890,44 +890,50 @@ function () {
|
|
|
890
890
|
|
|
891
891
|
this.getWebGLFingerprint = function () {
|
|
892
892
|
try {
|
|
893
|
-
var canvas = document.createElement(
|
|
894
|
-
var gl = canvas.getContext(
|
|
895
|
-
if (!gl) return
|
|
896
|
-
var debugInfo = gl.getExtension(
|
|
897
|
-
var vendor = debugInfo ? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) :
|
|
898
|
-
var renderer = debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) :
|
|
893
|
+
var canvas = document.createElement("canvas");
|
|
894
|
+
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
|
895
|
+
if (!gl) return "not-supported";
|
|
896
|
+
var debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
|
|
897
|
+
var vendor = debugInfo ? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) : "unknown";
|
|
898
|
+
var renderer = debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : "unknown";
|
|
899
899
|
return vendor + "|" + renderer;
|
|
900
900
|
} catch (e) {
|
|
901
|
-
return
|
|
901
|
+
return "error";
|
|
902
902
|
}
|
|
903
903
|
};
|
|
904
904
|
/**
|
|
905
905
|
* 获取Canvas指纹
|
|
906
|
+
* 注意:使用固定的尺寸和绘制参数,确保在不同时间生成一致的指纹
|
|
906
907
|
* @returns Canvas指纹字符串
|
|
907
908
|
*/
|
|
908
909
|
|
|
909
910
|
|
|
910
911
|
this.getCanvasFingerprint = function () {
|
|
911
912
|
try {
|
|
912
|
-
|
|
913
|
-
var
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
ctx
|
|
917
|
-
ctx
|
|
918
|
-
|
|
913
|
+
// 使用固定的 canvas 尺寸,确保一致性
|
|
914
|
+
var canvas = document.createElement("canvas");
|
|
915
|
+
canvas.width = 200;
|
|
916
|
+
canvas.height = 50;
|
|
917
|
+
var ctx = canvas.getContext("2d");
|
|
918
|
+
if (!ctx) return "not-supported"; // 使用固定的绘制参数,确保每次生成一致
|
|
919
|
+
|
|
920
|
+
ctx.textBaseline = "top";
|
|
921
|
+
ctx.font = "14px Arial";
|
|
922
|
+
ctx.fillStyle = "#f60";
|
|
919
923
|
ctx.fillRect(125, 1, 62, 20);
|
|
920
|
-
ctx.fillStyle =
|
|
921
|
-
ctx.fillText(
|
|
922
|
-
ctx.fillStyle =
|
|
923
|
-
ctx.fillText(
|
|
924
|
-
|
|
924
|
+
ctx.fillStyle = "#069";
|
|
925
|
+
ctx.fillText("Canvas fingerprint", 2, 15);
|
|
926
|
+
ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
|
|
927
|
+
ctx.fillText("Canvas fingerprint", 4, 17); // 取后50个字符作为指纹
|
|
928
|
+
|
|
929
|
+
return canvas.toDataURL().slice(-50);
|
|
925
930
|
} catch (e) {
|
|
926
|
-
return
|
|
931
|
+
return "error";
|
|
927
932
|
}
|
|
928
933
|
};
|
|
929
934
|
/**
|
|
930
935
|
* 获取音频上下文指纹
|
|
936
|
+
* 注意:只使用稳定的 sampleRate,不使用 currentTime(会随时间变化)
|
|
931
937
|
* @returns 音频指纹字符串
|
|
932
938
|
*/
|
|
933
939
|
|
|
@@ -935,26 +941,14 @@ function () {
|
|
|
935
941
|
this.getAudioFingerprint = function () {
|
|
936
942
|
try {
|
|
937
943
|
var AudioContextClass = window.AudioContext || window.webkitAudioContext;
|
|
938
|
-
if (!AudioContextClass) return
|
|
939
|
-
var context = new AudioContextClass();
|
|
940
|
-
|
|
941
|
-
var
|
|
942
|
-
var gain = context.createGain();
|
|
943
|
-
var scriptProcessor = context.createScriptProcessor(4096, 1, 1);
|
|
944
|
-
oscillator.type = 'triangle';
|
|
945
|
-
oscillator.frequency.value = 10000;
|
|
946
|
-
gain.gain.value = 0;
|
|
947
|
-
oscillator.connect(analyser);
|
|
948
|
-
analyser.connect(scriptProcessor);
|
|
949
|
-
scriptProcessor.connect(gain);
|
|
950
|
-
gain.connect(context.destination);
|
|
951
|
-
oscillator.start(0);
|
|
952
|
-
var fingerprint = context.sampleRate + '|' + context.currentTime;
|
|
953
|
-
oscillator.stop();
|
|
944
|
+
if (!AudioContextClass) return "not-supported";
|
|
945
|
+
var context = new AudioContextClass(); // 只使用稳定的 sampleRate,不使用 currentTime(会随时间变化导致指纹不一致)
|
|
946
|
+
|
|
947
|
+
var fingerprint = String(context.sampleRate || 0);
|
|
954
948
|
context.close();
|
|
955
949
|
return fingerprint;
|
|
956
950
|
} catch (e) {
|
|
957
|
-
return
|
|
951
|
+
return "error";
|
|
958
952
|
}
|
|
959
953
|
};
|
|
960
954
|
/**
|
|
@@ -965,15 +959,15 @@ function () {
|
|
|
965
959
|
|
|
966
960
|
this.getFontFingerprint = function () {
|
|
967
961
|
try {
|
|
968
|
-
var baseFonts_1 = [
|
|
969
|
-
var testString_1 =
|
|
970
|
-
var testSize_1 =
|
|
971
|
-
var canvas = document.createElement(
|
|
972
|
-
var ctx_1 = canvas.getContext(
|
|
973
|
-
if (!ctx_1) return
|
|
962
|
+
var baseFonts_1 = ["monospace", "sans-serif", "serif"];
|
|
963
|
+
var testString_1 = "mmmmmmmmmmlli";
|
|
964
|
+
var testSize_1 = "72px";
|
|
965
|
+
var canvas = document.createElement("canvas");
|
|
966
|
+
var ctx_1 = canvas.getContext("2d");
|
|
967
|
+
if (!ctx_1) return "not-supported";
|
|
974
968
|
var detectedFonts_1 = []; // 测试字体列表
|
|
975
969
|
|
|
976
|
-
var fonts = [
|
|
970
|
+
var fonts = ["Arial", "Arial Black", "Comic Sans MS", "Courier New", "Georgia", "Helvetica", "Impact", "Times New Roman", "Trebuchet MS", "Verdana"]; // 获取基准宽度
|
|
977
971
|
|
|
978
972
|
var baseWidths_1 = {};
|
|
979
973
|
baseFonts_1.forEach(function (font) {
|
|
@@ -996,9 +990,9 @@ function () {
|
|
|
996
990
|
detectedFonts_1.push(font);
|
|
997
991
|
}
|
|
998
992
|
});
|
|
999
|
-
return detectedFonts_1.join(
|
|
993
|
+
return detectedFonts_1.join(",");
|
|
1000
994
|
} catch (e) {
|
|
1001
|
-
return
|
|
995
|
+
return "error";
|
|
1002
996
|
}
|
|
1003
997
|
};
|
|
1004
998
|
/**
|
|
@@ -1021,9 +1015,9 @@ function () {
|
|
|
1021
1015
|
}
|
|
1022
1016
|
}
|
|
1023
1017
|
|
|
1024
|
-
return plugins.join(
|
|
1018
|
+
return plugins.join(";");
|
|
1025
1019
|
} catch (e) {
|
|
1026
|
-
return
|
|
1020
|
+
return "error";
|
|
1027
1021
|
}
|
|
1028
1022
|
};
|
|
1029
1023
|
/**
|
|
@@ -1034,7 +1028,7 @@ function () {
|
|
|
1034
1028
|
|
|
1035
1029
|
this.hasLocalStorage = function () {
|
|
1036
1030
|
try {
|
|
1037
|
-
var test =
|
|
1031
|
+
var test = "__test__";
|
|
1038
1032
|
localStorage.setItem(test, test);
|
|
1039
1033
|
localStorage.removeItem(test);
|
|
1040
1034
|
return true;
|
|
@@ -1050,7 +1044,7 @@ function () {
|
|
|
1050
1044
|
|
|
1051
1045
|
this.hasSessionStorage = function () {
|
|
1052
1046
|
try {
|
|
1053
|
-
var test =
|
|
1047
|
+
var test = "__test__";
|
|
1054
1048
|
sessionStorage.setItem(test, test);
|
|
1055
1049
|
sessionStorage.removeItem(test);
|
|
1056
1050
|
return true;
|
|
@@ -1065,7 +1059,7 @@ function () {
|
|
|
1065
1059
|
|
|
1066
1060
|
|
|
1067
1061
|
this.hasIndexedDB = function () {
|
|
1068
|
-
return
|
|
1062
|
+
return "indexedDB" in window && indexedDB !== null;
|
|
1069
1063
|
};
|
|
1070
1064
|
/**
|
|
1071
1065
|
* 获取网络连接指纹
|
|
@@ -1076,10 +1070,11 @@ function () {
|
|
|
1076
1070
|
this.getConnectionFingerprint = function () {
|
|
1077
1071
|
try {
|
|
1078
1072
|
var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
1079
|
-
if (!connection) return
|
|
1080
|
-
|
|
1073
|
+
if (!connection) return "not-supported"; // 只使用稳定的 effectiveType,不使用 downlink 和 rtt(会随网络状态变化导致指纹不一致)
|
|
1074
|
+
|
|
1075
|
+
return connection.effectiveType || "unknown";
|
|
1081
1076
|
} catch (e) {
|
|
1082
|
-
return
|
|
1077
|
+
return "error";
|
|
1083
1078
|
}
|
|
1084
1079
|
};
|
|
1085
1080
|
/**
|
|
@@ -1102,12 +1097,14 @@ function () {
|
|
|
1102
1097
|
hash1 = (hash1 << 5) + hash1 + char; // hash1 * 33 + char
|
|
1103
1098
|
|
|
1104
1099
|
hash2 = (hash2 << 5) + hash2 + char; // hash2 * 33 + char
|
|
1105
|
-
} //
|
|
1100
|
+
} // 将两个32位哈希值转换为十六进制字符串并拼接,保持完整长度
|
|
1101
|
+
|
|
1106
1102
|
|
|
1103
|
+
var hash1Hex = (hash1 >>> 0).toString(16).padStart(8, '0'); // 32位 = 8个十六进制字符
|
|
1107
1104
|
|
|
1108
|
-
var
|
|
1105
|
+
var hash2Hex = (hash2 >>> 0).toString(16).padStart(8, '0'); // 32位 = 8个十六进制字符
|
|
1109
1106
|
|
|
1110
|
-
var deviceId =
|
|
1107
|
+
var deviceId = "fp_" + hash1Hex + hash2Hex;
|
|
1111
1108
|
return deviceId;
|
|
1112
1109
|
};
|
|
1113
1110
|
}
|
|
@@ -1239,16 +1236,24 @@ function (_super) {
|
|
|
1239
1236
|
__extends(WebTracking, _super);
|
|
1240
1237
|
|
|
1241
1238
|
function WebTracking() {
|
|
1242
|
-
var _this = _super.call(this) || this; //
|
|
1239
|
+
var _this = _super.call(this) || this; // 批量发送定时器
|
|
1243
1240
|
|
|
1244
1241
|
|
|
1245
|
-
_this.batchQueue = []; // 批量发送定时器
|
|
1246
|
-
|
|
1247
1242
|
_this.batchTimer = null; // LocalStorage 存储 key
|
|
1248
1243
|
|
|
1249
1244
|
_this.BATCH_QUEUE_STORAGE_KEY = "web_tracking_batch_queue"; // 是否使用自定义 pageKey(如果为 true,路由变化时不会自动更新 pageKey)
|
|
1250
1245
|
|
|
1251
|
-
_this.useCustomPageKey = false; //
|
|
1246
|
+
_this.useCustomPageKey = false; // 页面卸载监听器是否已设置
|
|
1247
|
+
|
|
1248
|
+
_this.isUnloadListenerSetup = false; // 定时上报页面停留时长的定时器
|
|
1249
|
+
|
|
1250
|
+
_this.pageDurationTimer = null; // LocalStorage 存储 key(待发送请求)
|
|
1251
|
+
|
|
1252
|
+
_this.PENDING_REQUESTS_STORAGE_KEY = "web_tracking_pending_requests"; // 待发送请求队列最大大小(默认值,可通过配置覆盖)
|
|
1253
|
+
|
|
1254
|
+
_this.DEFAULT_PENDING_REQUESTS_MAX_SIZE = 50; // LocalStorage 最大大小限制(4MB)
|
|
1255
|
+
|
|
1256
|
+
_this.MAX_STORAGE_SIZE = 4 * 1024 * 1024; // 用户信息
|
|
1252
1257
|
|
|
1253
1258
|
_this.userInfo = null; // 当前路由
|
|
1254
1259
|
|
|
@@ -1285,20 +1290,31 @@ function (_super) {
|
|
|
1285
1290
|
|
|
1286
1291
|
_this.systemsInfo = _this.getSystemsInfo(initParams.platform); // 获取设备ID
|
|
1287
1292
|
|
|
1288
|
-
_this.deviceId = _this.getDeviceId();
|
|
1293
|
+
_this.deviceId = _this.getDeviceId(); // 如果传入了 userInfo,设置用户信息
|
|
1294
|
+
|
|
1295
|
+
if (initParams.userInfo && _this.isObject(initParams.userInfo)) {
|
|
1296
|
+
_this.userInfo = initParams.userInfo;
|
|
1297
|
+
}
|
|
1289
1298
|
|
|
1290
1299
|
_this.setCookie("retainedStartTime", _this.getTimeStamp()); // 如果启用了批量发送,从 LocalStorage 恢复队列
|
|
1291
1300
|
|
|
1292
1301
|
|
|
1293
1302
|
if (_this.initConfig.batchSend) {
|
|
1294
|
-
_this.restoreBatchQueueFromStorage();
|
|
1303
|
+
_this.restoreBatchQueueFromStorage();
|
|
1304
|
+
} // 恢复待发送的单个请求
|
|
1305
|
+
|
|
1295
1306
|
|
|
1307
|
+
_this.restorePendingRequestsFromStorage(); // 无论是否启用批量发送,都需要监听页面卸载事件,确保数据发送
|
|
1296
1308
|
|
|
1297
|
-
|
|
1309
|
+
|
|
1310
|
+
_this.setupBeforeUnloadListener(); // 如果启用了定时上报,启动定时器
|
|
1311
|
+
|
|
1312
|
+
|
|
1313
|
+
if (_this.initConfig.autoTrackPageDurationInterval) {
|
|
1314
|
+
_this.startPageDurationTimer();
|
|
1298
1315
|
}
|
|
1299
1316
|
};
|
|
1300
1317
|
/**
|
|
1301
|
-
* TODO: 需要判断有哪些不能被预制的参数
|
|
1302
1318
|
* @description 预置参数
|
|
1303
1319
|
* @param {object} PresetParams [预置参数]
|
|
1304
1320
|
*/
|
|
@@ -1324,8 +1340,14 @@ function (_super) {
|
|
|
1324
1340
|
if (key === 'pageKey') return;
|
|
1325
1341
|
|
|
1326
1342
|
if (_this.initConfig.hasOwnProperty(key)) {
|
|
1327
|
-
//
|
|
1328
|
-
_this.
|
|
1343
|
+
// 参数验证
|
|
1344
|
+
var validationResult = _this.validateConfigParam(String(key), val);
|
|
1345
|
+
|
|
1346
|
+
if (validationResult.valid) {
|
|
1347
|
+
_this.initConfig[key] = val;
|
|
1348
|
+
} else {
|
|
1349
|
+
_this.printLog("\u914D\u7F6E\u53C2\u6570\u9A8C\u8BC1\u5931\u8D25: " + String(key) + " = " + val + ", \u539F\u56E0: " + validationResult.message);
|
|
1350
|
+
}
|
|
1329
1351
|
}
|
|
1330
1352
|
});
|
|
1331
1353
|
}
|
|
@@ -1334,17 +1356,155 @@ function (_super) {
|
|
|
1334
1356
|
_this.printLog("当前 server_url 为空或不正确,只在控制台打印日志,network 中不会发数据,请配置正确的 server_url!");
|
|
1335
1357
|
|
|
1336
1358
|
_this.initConfig["showLog"] = true;
|
|
1337
|
-
} //
|
|
1359
|
+
} // 如果启用了全埋点或启用了 data-part-key 点击追踪
|
|
1338
1360
|
|
|
1339
1361
|
|
|
1340
|
-
if (!!_this.initConfig["autoTrack"]) {
|
|
1362
|
+
if (!!_this.initConfig["autoTrack"] || !!_this.initConfig["trackPartKeyClick"]) {
|
|
1341
1363
|
// 启用监听
|
|
1342
1364
|
_this.listener();
|
|
1343
1365
|
} else {
|
|
1344
1366
|
// 取消监听
|
|
1345
1367
|
_this.unlistener();
|
|
1368
|
+
} // 处理定时上报配置
|
|
1369
|
+
|
|
1370
|
+
|
|
1371
|
+
if (_this.initConfig.autoTrackPageDurationInterval) {
|
|
1372
|
+
_this.startPageDurationTimer();
|
|
1373
|
+
} else {
|
|
1374
|
+
_this.stopPageDurationTimer();
|
|
1346
1375
|
}
|
|
1347
1376
|
};
|
|
1377
|
+
/**
|
|
1378
|
+
* @description 验证配置参数
|
|
1379
|
+
* @param key 参数名
|
|
1380
|
+
* @param value 参数值
|
|
1381
|
+
* @returns 验证结果
|
|
1382
|
+
*/
|
|
1383
|
+
|
|
1384
|
+
|
|
1385
|
+
_this.validateConfigParam = function (key, value) {
|
|
1386
|
+
switch (key) {
|
|
1387
|
+
case 'sampleRate':
|
|
1388
|
+
if (typeof value !== 'number' || value < 0 || value > 1) {
|
|
1389
|
+
return {
|
|
1390
|
+
valid: false,
|
|
1391
|
+
message: 'sampleRate 必须是 0-1 之间的数字'
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
break;
|
|
1396
|
+
|
|
1397
|
+
case 'sendTimeout':
|
|
1398
|
+
if (typeof value !== 'number' || value <= 0) {
|
|
1399
|
+
return {
|
|
1400
|
+
valid: false,
|
|
1401
|
+
message: 'sendTimeout 必须是大于 0 的数字'
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
break;
|
|
1406
|
+
|
|
1407
|
+
case 'batchInterval':
|
|
1408
|
+
if (typeof value !== 'number' || value <= 0) {
|
|
1409
|
+
return {
|
|
1410
|
+
valid: false,
|
|
1411
|
+
message: 'batchInterval 必须是大于 0 的数字'
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
break;
|
|
1416
|
+
|
|
1417
|
+
case 'batchMaxSize':
|
|
1418
|
+
if (typeof value !== 'number' || value <= 0 || !Number.isInteger(value)) {
|
|
1419
|
+
return {
|
|
1420
|
+
valid: false,
|
|
1421
|
+
message: 'batchMaxSize 必须是大于 0 的整数'
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
break;
|
|
1426
|
+
|
|
1427
|
+
case 'pendingRequestsMaxSize':
|
|
1428
|
+
if (typeof value !== 'number' || value <= 0 || !Number.isInteger(value)) {
|
|
1429
|
+
return {
|
|
1430
|
+
valid: false,
|
|
1431
|
+
message: 'pendingRequestsMaxSize 必须是大于 0 的整数'
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
break;
|
|
1436
|
+
|
|
1437
|
+
case 'pageDurationInterval':
|
|
1438
|
+
if (typeof value !== 'number' || value <= 0) {
|
|
1439
|
+
return {
|
|
1440
|
+
valid: false,
|
|
1441
|
+
message: 'pageDurationInterval 必须是大于 0 的数字'
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
break;
|
|
1446
|
+
|
|
1447
|
+
case 'sendMethod':
|
|
1448
|
+
if (typeof value !== 'string' || !['auto', 'xhr', 'beacon'].includes(value)) {
|
|
1449
|
+
return {
|
|
1450
|
+
valid: false,
|
|
1451
|
+
message: 'sendMethod 必须是 auto、xhr 或 beacon'
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
break;
|
|
1456
|
+
|
|
1457
|
+
case 'showLog':
|
|
1458
|
+
case 'autoTrack':
|
|
1459
|
+
case 'isTrackSinglePage':
|
|
1460
|
+
case 'batchSend':
|
|
1461
|
+
case 'trackPartKeyClick':
|
|
1462
|
+
case 'autoTrackPageDurationInterval':
|
|
1463
|
+
if (typeof value !== 'boolean') {
|
|
1464
|
+
return {
|
|
1465
|
+
valid: false,
|
|
1466
|
+
message: key + " \u5FC5\u987B\u662F\u5E03\u5C14\u503C"
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
break;
|
|
1471
|
+
|
|
1472
|
+
case 'business':
|
|
1473
|
+
case 'header':
|
|
1474
|
+
if (value !== null && _typeof(value) !== 'object') {
|
|
1475
|
+
return {
|
|
1476
|
+
valid: false,
|
|
1477
|
+
message: key + " \u5FC5\u987B\u662F\u5BF9\u8C61\u6216 null"
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
break;
|
|
1482
|
+
|
|
1483
|
+
case 'contentType':
|
|
1484
|
+
if (value !== 'application/json' && value !== 'application/x-www-form-urlencoded') {
|
|
1485
|
+
return {
|
|
1486
|
+
valid: false,
|
|
1487
|
+
message: 'contentType 必须是 application/json 或 application/x-www-form-urlencoded'
|
|
1488
|
+
};
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
break;
|
|
1492
|
+
|
|
1493
|
+
case 'platform':
|
|
1494
|
+
if (typeof value !== 'string') {
|
|
1495
|
+
return {
|
|
1496
|
+
valid: false,
|
|
1497
|
+
message: 'platform 必须是字符串'
|
|
1498
|
+
};
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
break;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
return {
|
|
1505
|
+
valid: true
|
|
1506
|
+
};
|
|
1507
|
+
};
|
|
1348
1508
|
/**
|
|
1349
1509
|
* 用户登录
|
|
1350
1510
|
*/
|
|
@@ -1437,17 +1597,26 @@ function (_super) {
|
|
|
1437
1597
|
|
|
1438
1598
|
|
|
1439
1599
|
_this.listener = function () {
|
|
1440
|
-
|
|
1441
|
-
|
|
1600
|
+
// 先移除旧的监听器,避免重复绑定
|
|
1601
|
+
_this.unlistener(); // 如果启用了全埋点,监听页面浏览事件
|
|
1442
1602
|
|
|
1443
|
-
_this.addSinglePageEvent(_this.onPageViewCallback);
|
|
1444
|
-
}
|
|
1445
1603
|
|
|
1446
|
-
_this.
|
|
1447
|
-
|
|
1448
|
-
|
|
1604
|
+
if (!!_this.initConfig.autoTrack) {
|
|
1605
|
+
if (!!_this.initConfig.isTrackSinglePage) {
|
|
1606
|
+
_this.rewriteHistory();
|
|
1449
1607
|
|
|
1450
|
-
|
|
1608
|
+
_this.addSinglePageEvent(_this.onPageViewCallback);
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
_this.each(["load", "beforeunload"], function (historyType) {
|
|
1612
|
+
_this.addEventListener(window, historyType, _this.onPageViewCallback);
|
|
1613
|
+
});
|
|
1614
|
+
} // 如果启用了全埋点或启用了 data-part-key 点击追踪,监听点击事件
|
|
1615
|
+
|
|
1616
|
+
|
|
1617
|
+
if (!!_this.initConfig.autoTrack || !!_this.initConfig.trackPartKeyClick) {
|
|
1618
|
+
_this.addEventListener(window, "click", _this.onClickCallback);
|
|
1619
|
+
}
|
|
1451
1620
|
};
|
|
1452
1621
|
/**
|
|
1453
1622
|
* @description 取消全埋点事件
|
|
@@ -1471,7 +1640,10 @@ function (_super) {
|
|
|
1471
1640
|
_this.removeEventListener(window, "click", _this.onClickCallback); // 清理批量发送定时器
|
|
1472
1641
|
|
|
1473
1642
|
|
|
1474
|
-
_this.clearBatchTimer();
|
|
1643
|
+
_this.clearBatchTimer(); // 清理定时上报定时器
|
|
1644
|
+
|
|
1645
|
+
|
|
1646
|
+
_this.stopPageDurationTimer();
|
|
1475
1647
|
};
|
|
1476
1648
|
/**
|
|
1477
1649
|
* @description 清理批量发送定时器
|
|
@@ -1490,14 +1662,104 @@ function (_super) {
|
|
|
1490
1662
|
|
|
1491
1663
|
|
|
1492
1664
|
_this.clearBatchQueue = function () {
|
|
1493
|
-
_this.batchQueue = [];
|
|
1494
|
-
|
|
1495
1665
|
_this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
|
|
1496
1666
|
|
|
1497
1667
|
if (_this.initConfig.showLog) {
|
|
1498
1668
|
_this.printLog("批量队列已清空");
|
|
1499
1669
|
}
|
|
1500
1670
|
};
|
|
1671
|
+
/**
|
|
1672
|
+
* @description 从 LocalStorage 获取批量队列
|
|
1673
|
+
* @returns 批量队列数组
|
|
1674
|
+
*/
|
|
1675
|
+
|
|
1676
|
+
|
|
1677
|
+
_this.getBatchQueueFromStorage = function () {
|
|
1678
|
+
try {
|
|
1679
|
+
var storedQueue = _this.getLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY);
|
|
1680
|
+
|
|
1681
|
+
if (storedQueue) {
|
|
1682
|
+
var parsedQueue = JSON.parse(storedQueue);
|
|
1683
|
+
|
|
1684
|
+
if (Array.isArray(parsedQueue)) {
|
|
1685
|
+
return parsedQueue;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
} catch (e) {
|
|
1689
|
+
_this.printLog("\u8BFB\u53D6\u6279\u91CF\u961F\u5217\u5931\u8D25: " + e); // 如果解析失败,清除损坏的数据
|
|
1690
|
+
|
|
1691
|
+
|
|
1692
|
+
_this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
return [];
|
|
1696
|
+
};
|
|
1697
|
+
/**
|
|
1698
|
+
* @description 保存批量队列到 LocalStorage
|
|
1699
|
+
* @param queue 批量队列数组
|
|
1700
|
+
*/
|
|
1701
|
+
|
|
1702
|
+
|
|
1703
|
+
_this.saveBatchQueueToStorage = function (queue) {
|
|
1704
|
+
try {
|
|
1705
|
+
var queueString = JSON.stringify(queue); // 检查存储大小,避免超出 LocalStorage 限制
|
|
1706
|
+
|
|
1707
|
+
if (queueString.length > _this.MAX_STORAGE_SIZE) {
|
|
1708
|
+
var maxItems = Math.floor(queue.length * 0.8); // 保留 80%
|
|
1709
|
+
|
|
1710
|
+
var trimmedQueue = queue.slice(-maxItems);
|
|
1711
|
+
|
|
1712
|
+
_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");
|
|
1713
|
+
|
|
1714
|
+
_this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, JSON.stringify(trimmedQueue));
|
|
1715
|
+
} else {
|
|
1716
|
+
_this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, queueString);
|
|
1717
|
+
}
|
|
1718
|
+
} catch (e) {
|
|
1719
|
+
// LocalStorage 可能已满或不可用
|
|
1720
|
+
_this.printLog("\u4FDD\u5B58\u6279\u91CF\u961F\u5217\u5230 LocalStorage \u5931\u8D25: " + e);
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
/**
|
|
1724
|
+
* @description 从 LocalStorage 获取待发送请求队列
|
|
1725
|
+
* @returns 待发送请求队列数组
|
|
1726
|
+
*/
|
|
1727
|
+
|
|
1728
|
+
|
|
1729
|
+
_this.getPendingRequestsFromStorage = function () {
|
|
1730
|
+
try {
|
|
1731
|
+
var storedRequests = _this.getLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY);
|
|
1732
|
+
|
|
1733
|
+
if (storedRequests) {
|
|
1734
|
+
var parsedRequests = JSON.parse(storedRequests);
|
|
1735
|
+
|
|
1736
|
+
if (Array.isArray(parsedRequests)) {
|
|
1737
|
+
return parsedRequests;
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
} catch (e) {
|
|
1741
|
+
_this.printLog("\u8BFB\u53D6\u5F85\u53D1\u9001\u8BF7\u6C42\u5931\u8D25: " + e); // 如果解析失败,清除损坏的数据
|
|
1742
|
+
|
|
1743
|
+
|
|
1744
|
+
_this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, "[]");
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
return [];
|
|
1748
|
+
};
|
|
1749
|
+
/**
|
|
1750
|
+
* @description 保存待发送请求队列到 LocalStorage
|
|
1751
|
+
* @param requests 待发送请求队列数组
|
|
1752
|
+
*/
|
|
1753
|
+
|
|
1754
|
+
|
|
1755
|
+
_this.savePendingRequestsToStorage = function (requests) {
|
|
1756
|
+
try {
|
|
1757
|
+
_this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, JSON.stringify(requests));
|
|
1758
|
+
} catch (e) {
|
|
1759
|
+
// LocalStorage 可能已满或不可用
|
|
1760
|
+
_this.printLog("\u4FDD\u5B58\u5F85\u53D1\u9001\u8BF7\u6C42\u5230 LocalStorage \u5931\u8D25: " + e);
|
|
1761
|
+
}
|
|
1762
|
+
};
|
|
1501
1763
|
/**
|
|
1502
1764
|
* @description 设置自定义页面唯一标识
|
|
1503
1765
|
* @param pageKey 页面唯一标识,如果传入 null 或空字符串,则恢复自动生成
|
|
@@ -1566,7 +1828,7 @@ function (_super) {
|
|
|
1566
1828
|
}
|
|
1567
1829
|
});
|
|
1568
1830
|
|
|
1569
|
-
|
|
1831
|
+
_this.sendData(params);
|
|
1570
1832
|
};
|
|
1571
1833
|
/**
|
|
1572
1834
|
* @description 路由触发事件
|
|
@@ -1574,7 +1836,16 @@ function (_super) {
|
|
|
1574
1836
|
|
|
1575
1837
|
|
|
1576
1838
|
_this.onPageViewCallback = function (e) {
|
|
1577
|
-
var _a, _b;
|
|
1839
|
+
var _a, _b; // 在路由变化前,先发送待发送的数据(避免被取消)
|
|
1840
|
+
|
|
1841
|
+
|
|
1842
|
+
var pendingRequests = _this.getPendingRequestsFromStorage();
|
|
1843
|
+
|
|
1844
|
+
var batchQueue = _this.initConfig.batchSend ? _this.getBatchQueueFromStorage() : [];
|
|
1845
|
+
|
|
1846
|
+
if (pendingRequests.length > 0 || batchQueue.length > 0) {
|
|
1847
|
+
_this.flushPendingData();
|
|
1848
|
+
}
|
|
1578
1849
|
|
|
1579
1850
|
var ORGIN = window.location.origin;
|
|
1580
1851
|
|
|
@@ -1591,6 +1862,13 @@ function (_super) {
|
|
|
1591
1862
|
|
|
1592
1863
|
if (!_this.useCustomPageKey) {
|
|
1593
1864
|
_this.pageKey = window.location.pathname.replace(/\//g, "_").substring(1);
|
|
1865
|
+
} // 路由变化时,如果启用了定时上报,需要重启定时器
|
|
1866
|
+
|
|
1867
|
+
|
|
1868
|
+
if (_this.initConfig.autoTrackPageDurationInterval) {
|
|
1869
|
+
_this.stopPageDurationTimer();
|
|
1870
|
+
|
|
1871
|
+
_this.startPageDurationTimer();
|
|
1594
1872
|
}
|
|
1595
1873
|
|
|
1596
1874
|
_this.sendRetained(e.type);
|
|
@@ -1680,16 +1958,42 @@ function (_super) {
|
|
|
1680
1958
|
|
|
1681
1959
|
|
|
1682
1960
|
_this.flushBatchQueue = function () {
|
|
1683
|
-
|
|
1961
|
+
var batchQueue = _this.getBatchQueueFromStorage();
|
|
1962
|
+
|
|
1963
|
+
if (batchQueue.length === 0) return;
|
|
1964
|
+
|
|
1965
|
+
var currentTime = _this.getTimeStamp(); // 过滤出可以发送的数据(未到重试时间的不发送)
|
|
1966
|
+
|
|
1967
|
+
|
|
1968
|
+
var readyToSend = batchQueue.filter(function (item) {
|
|
1969
|
+
if (!item._nextRetryTime) {
|
|
1970
|
+
return true;
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
return item._nextRetryTime <= currentTime;
|
|
1974
|
+
});
|
|
1684
1975
|
|
|
1685
|
-
|
|
1976
|
+
if (readyToSend.length === 0) {
|
|
1977
|
+
if (_this.initConfig.showLog) {
|
|
1978
|
+
_this.printLog("\u6279\u91CF\u961F\u5217\u4E2D\u6709 " + batchQueue.length + " \u6761\u6570\u636E\u7B49\u5F85\u91CD\u8BD5");
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
return;
|
|
1982
|
+
} // 从队列中移除已准备发送的数据
|
|
1983
|
+
|
|
1984
|
+
|
|
1985
|
+
var remainingQueue = batchQueue.filter(function (item) {
|
|
1986
|
+
if (!item._nextRetryTime) {
|
|
1987
|
+
return false;
|
|
1988
|
+
}
|
|
1686
1989
|
|
|
1687
|
-
|
|
1990
|
+
return item._nextRetryTime > currentTime;
|
|
1991
|
+
}); // 保存剩余的队列
|
|
1688
1992
|
|
|
1689
|
-
_this.saveBatchQueueToStorage(); // 发送批量数据
|
|
1993
|
+
_this.saveBatchQueueToStorage(remainingQueue); // 发送批量数据
|
|
1690
1994
|
|
|
1691
1995
|
|
|
1692
|
-
_this.sendBatchData(
|
|
1996
|
+
_this.sendBatchData(readyToSend);
|
|
1693
1997
|
};
|
|
1694
1998
|
/**
|
|
1695
1999
|
* 发送批量数据
|
|
@@ -1701,7 +2005,9 @@ function (_super) {
|
|
|
1701
2005
|
var _a = _this.initConfig,
|
|
1702
2006
|
serverUrl = _a.serverUrl,
|
|
1703
2007
|
contentType = _a.contentType,
|
|
1704
|
-
showLog = _a.showLog
|
|
2008
|
+
showLog = _a.showLog,
|
|
2009
|
+
sendMethod = _a.sendMethod,
|
|
2010
|
+
initHeader = _a.header;
|
|
1705
2011
|
|
|
1706
2012
|
if (showLog) {
|
|
1707
2013
|
_this.printLog("\u6279\u91CF\u53D1\u9001 " + data.length + " \u6761\u6570\u636E");
|
|
@@ -1709,44 +2015,126 @@ function (_super) {
|
|
|
1709
2015
|
data.forEach(function (item) {
|
|
1710
2016
|
return _this.printLog(item);
|
|
1711
2017
|
});
|
|
1712
|
-
} //
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
2018
|
+
} // 判断是否使用 sendBeacon
|
|
2019
|
+
|
|
2020
|
+
|
|
2021
|
+
var shouldUseBeacon = _this.shouldUseBeacon(sendMethod, undefined, initHeader); // 如果使用 sendBeacon
|
|
2022
|
+
|
|
2023
|
+
|
|
2024
|
+
if (shouldUseBeacon) {
|
|
2025
|
+
try {
|
|
2026
|
+
var blob = new Blob([JSON.stringify(data)], {
|
|
2027
|
+
type: contentType || "application/json"
|
|
2028
|
+
});
|
|
2029
|
+
var sent = navigator.sendBeacon(serverUrl, blob);
|
|
2030
|
+
|
|
2031
|
+
if (sent) {
|
|
2032
|
+
// 发送成功,确保 LocalStorage 已清空
|
|
2033
|
+
_this.saveBatchQueueToStorage([]);
|
|
2034
|
+
|
|
2035
|
+
if (showLog) {
|
|
2036
|
+
_this.printLog("\u6279\u91CF\u53D1\u9001\u6210\u529F: " + data.length + " \u6761\u6570\u636E");
|
|
2037
|
+
}
|
|
2038
|
+
} else {
|
|
2039
|
+
// sendBeacon 返回 false,重新加入队列以便重试
|
|
2040
|
+
_this.printLog("\u6279\u91CF\u53D1\u9001\u5931\u8D25: sendBeacon \u8FD4\u56DE false\uFF0C\u6570\u636E\u5DF2\u91CD\u65B0\u52A0\u5165\u961F\u5217");
|
|
2041
|
+
|
|
2042
|
+
_this.retryBatchData(data);
|
|
2043
|
+
}
|
|
2044
|
+
} catch (e) {
|
|
2045
|
+
// sendBeacon 失败,重新加入队列以便重试
|
|
2046
|
+
_this.printLog("\u6279\u91CF\u53D1\u9001\u5931\u8D25: " + e + "\uFF0C\u6570\u636E\u5DF2\u91CD\u65B0\u52A0\u5165\u961F\u5217");
|
|
2047
|
+
|
|
2048
|
+
_this.retryBatchData(data);
|
|
2049
|
+
}
|
|
2050
|
+
} else {
|
|
2051
|
+
// 使用 XMLHttpRequest 发送
|
|
2052
|
+
_this.ajax({
|
|
2053
|
+
url: serverUrl,
|
|
2054
|
+
type: "POST",
|
|
2055
|
+
data: JSON.stringify({
|
|
2056
|
+
events: data
|
|
2057
|
+
}),
|
|
2058
|
+
contentType: contentType,
|
|
2059
|
+
credentials: false,
|
|
2060
|
+
timeout: _this.initConfig.sendTimeout,
|
|
2061
|
+
cors: true,
|
|
2062
|
+
success: function success() {
|
|
2063
|
+
// 批量发送成功,确保 LocalStorage 已清空
|
|
2064
|
+
// flushBatchQueue 在发送前已清空 LocalStorage,这里再次确认
|
|
2065
|
+
_this.saveBatchQueueToStorage([]);
|
|
2066
|
+
|
|
2067
|
+
if (_this.initConfig.showLog) {
|
|
2068
|
+
_this.printLog("\u6279\u91CF\u53D1\u9001\u6210\u529F: " + data.length + " \u6761\u6570\u636E");
|
|
2069
|
+
}
|
|
2070
|
+
},
|
|
2071
|
+
error: function error(err) {
|
|
2072
|
+
// 批量发送失败,重新加入队列以便重试
|
|
2073
|
+
_this.printLog("\u6279\u91CF\u53D1\u9001\u5931\u8D25: " + err + "\uFF0C\u6570\u636E\u5DF2\u91CD\u65B0\u52A0\u5165\u961F\u5217");
|
|
2074
|
+
|
|
2075
|
+
_this.retryBatchData(data);
|
|
2076
|
+
}
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
};
|
|
2080
|
+
/**
|
|
2081
|
+
* @description 批量数据重试逻辑
|
|
2082
|
+
* @param data 批量数据
|
|
2083
|
+
*/
|
|
2084
|
+
|
|
2085
|
+
|
|
2086
|
+
_this.retryBatchData = function (data) {
|
|
2087
|
+
// 获取当前队列
|
|
2088
|
+
var currentQueue = _this.getBatchQueueFromStorage(); // 去重:基于事件类型、itemKey、requestTime 生成唯一键
|
|
2089
|
+
|
|
2090
|
+
|
|
2091
|
+
var getEventKey = function getEventKey(item) {
|
|
2092
|
+
return item.event + "_" + item.itemKey + "_" + item.requestTime;
|
|
2093
|
+
};
|
|
2094
|
+
|
|
2095
|
+
var existingKeys = new Set(currentQueue.map(getEventKey));
|
|
2096
|
+
var maxRetryCount = 3; // 最大重试次数
|
|
2097
|
+
|
|
2098
|
+
var currentTime = _this.getTimeStamp(); // 过滤并更新重试信息
|
|
2099
|
+
|
|
2100
|
+
|
|
2101
|
+
var retryData = data.filter(function (item) {
|
|
2102
|
+
var key = getEventKey(item); // 检查是否已存在
|
|
2103
|
+
|
|
2104
|
+
if (existingKeys.has(key)) {
|
|
2105
|
+
return false;
|
|
2106
|
+
} // 检查重试次数
|
|
2107
|
+
|
|
2108
|
+
|
|
2109
|
+
var retryCount = (item._retryCount || 0) + 1;
|
|
2110
|
+
|
|
2111
|
+
if (retryCount > maxRetryCount) {
|
|
1728
2112
|
if (_this.initConfig.showLog) {
|
|
1729
|
-
_this.printLog("\
|
|
2113
|
+
_this.printLog("\u6570\u636E\u5DF2\u8FBE\u5230\u6700\u5927\u91CD\u8BD5\u6B21\u6570\uFF0C\u653E\u5F03\u91CD\u8BD5: " + key);
|
|
1730
2114
|
}
|
|
1731
|
-
},
|
|
1732
|
-
error: function error(err) {
|
|
1733
|
-
var _a; // 批量发送失败,重新加入队列以便重试
|
|
1734
2115
|
|
|
2116
|
+
return false;
|
|
2117
|
+
} // 更新重试信息
|
|
1735
2118
|
|
|
1736
|
-
_this.printLog("\u6279\u91CF\u53D1\u9001\u5931\u8D25: " + err + "\uFF0C\u6570\u636E\u5DF2\u91CD\u65B0\u52A0\u5165\u961F\u5217"); // 将失败的数据重新加入队列,避免数据丢失
|
|
1737
2119
|
|
|
2120
|
+
item._retryCount = retryCount; // 指数退避:2^retryCount * 1000ms
|
|
1738
2121
|
|
|
1739
|
-
|
|
2122
|
+
item._nextRetryTime = currentTime + Math.pow(2, retryCount) * 1000;
|
|
2123
|
+
existingKeys.add(key);
|
|
2124
|
+
return true;
|
|
2125
|
+
}); // 将失败的数据重新加入队列(添加到队列前面,优先重试)
|
|
1740
2126
|
|
|
2127
|
+
var retryQueue = __spreadArray(__spreadArray([], retryData), currentQueue); // 限制重试队列大小,避免内存溢出
|
|
1741
2128
|
|
|
1742
|
-
if (_this.batchQueue.length > _this.initConfig.batchMaxSize * 2) {
|
|
1743
|
-
_this.batchQueue = _this.batchQueue.slice(0, _this.initConfig.batchMaxSize);
|
|
1744
|
-
} // 保存失败的数据到 LocalStorage
|
|
1745
2129
|
|
|
2130
|
+
var maxSize = _this.initConfig.batchMaxSize * 2;
|
|
2131
|
+
var trimmedQueue = retryQueue.length > maxSize ? retryQueue.slice(0, maxSize) : retryQueue; // 保存失败的数据到 LocalStorage,确保数据不丢失
|
|
1746
2132
|
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
2133
|
+
_this.saveBatchQueueToStorage(trimmedQueue);
|
|
2134
|
+
|
|
2135
|
+
if (_this.initConfig.showLog) {
|
|
2136
|
+
_this.printLog("\u5DF2\u5C06 " + retryData.length + " \u6761\u6570\u636E\u52A0\u5165\u91CD\u8BD5\u961F\u5217");
|
|
2137
|
+
}
|
|
1750
2138
|
};
|
|
1751
2139
|
/**
|
|
1752
2140
|
* 添加到批量队列
|
|
@@ -1757,15 +2145,26 @@ function (_super) {
|
|
|
1757
2145
|
_this.addToBatchQueue = function (params) {
|
|
1758
2146
|
var _a = _this.initConfig,
|
|
1759
2147
|
batchInterval = _a.batchInterval,
|
|
1760
|
-
batchMaxSize = _a.batchMaxSize;
|
|
2148
|
+
batchMaxSize = _a.batchMaxSize; // 数据采样判断(在添加到队列前判断)
|
|
2149
|
+
|
|
2150
|
+
if (!_this.shouldSample()) {
|
|
2151
|
+
if (_this.initConfig.showLog) {
|
|
2152
|
+
_this.printLog("数据已采样跳过(批量模式)");
|
|
2153
|
+
}
|
|
2154
|
+
|
|
2155
|
+
return;
|
|
2156
|
+
} // 从 LocalStorage 获取当前队列
|
|
2157
|
+
|
|
1761
2158
|
|
|
1762
|
-
_this.
|
|
2159
|
+
var currentQueue = _this.getBatchQueueFromStorage(); // 添加新数据
|
|
1763
2160
|
|
|
1764
2161
|
|
|
1765
|
-
|
|
2162
|
+
currentQueue.push(params); // 保存到 LocalStorage
|
|
1766
2163
|
|
|
2164
|
+
_this.saveBatchQueueToStorage(currentQueue); // 如果队列达到最大数量,立即发送
|
|
1767
2165
|
|
|
1768
|
-
|
|
2166
|
+
|
|
2167
|
+
if (currentQueue.length >= batchMaxSize) {
|
|
1769
2168
|
_this.flushBatchQueue();
|
|
1770
2169
|
|
|
1771
2170
|
return;
|
|
@@ -1786,109 +2185,289 @@ function (_super) {
|
|
|
1786
2185
|
|
|
1787
2186
|
|
|
1788
2187
|
_this.restoreBatchQueueFromStorage = function () {
|
|
1789
|
-
|
|
1790
|
-
var storedQueue = _this.getLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY);
|
|
1791
|
-
|
|
1792
|
-
if (storedQueue) {
|
|
1793
|
-
var parsedQueue = JSON.parse(storedQueue);
|
|
2188
|
+
var batchQueue = _this.getBatchQueueFromStorage();
|
|
1794
2189
|
|
|
1795
|
-
|
|
1796
|
-
|
|
2190
|
+
if (batchQueue.length > 0) {
|
|
2191
|
+
if (_this.initConfig.showLog) {
|
|
2192
|
+
_this.printLog("\u4ECE LocalStorage \u6062\u590D " + batchQueue.length + " \u6761\u5F85\u53D1\u9001\u6570\u636E");
|
|
2193
|
+
} // 恢复后立即尝试发送(如果达到条件)
|
|
1797
2194
|
|
|
1798
|
-
if (_this.initConfig.showLog) {
|
|
1799
|
-
_this.printLog("\u4ECE LocalStorage \u6062\u590D " + parsedQueue.length + " \u6761\u5F85\u53D1\u9001\u6570\u636E");
|
|
1800
|
-
} // 恢复后立即尝试发送(如果达到条件)
|
|
1801
2195
|
|
|
2196
|
+
var batchMaxSize = _this.initConfig.batchMaxSize;
|
|
1802
2197
|
|
|
1803
|
-
|
|
2198
|
+
if (batchQueue.length >= batchMaxSize) {
|
|
2199
|
+
_this.flushBatchQueue();
|
|
2200
|
+
} else {
|
|
2201
|
+
// 设置定时发送
|
|
2202
|
+
var batchInterval = _this.initConfig.batchInterval;
|
|
1804
2203
|
|
|
1805
|
-
|
|
2204
|
+
if (!_this.batchTimer) {
|
|
2205
|
+
_this.batchTimer = window.setTimeout(function () {
|
|
1806
2206
|
_this.flushBatchQueue();
|
|
1807
|
-
} else {
|
|
1808
|
-
// 设置定时发送
|
|
1809
|
-
var batchInterval = _this.initConfig.batchInterval;
|
|
1810
|
-
|
|
1811
|
-
if (!_this.batchTimer) {
|
|
1812
|
-
_this.batchTimer = window.setTimeout(function () {
|
|
1813
|
-
_this.flushBatchQueue();
|
|
1814
2207
|
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
}
|
|
1818
|
-
}
|
|
2208
|
+
_this.batchTimer = null;
|
|
2209
|
+
}, batchInterval);
|
|
1819
2210
|
}
|
|
1820
2211
|
}
|
|
1821
|
-
}
|
|
1822
|
-
|
|
2212
|
+
}
|
|
2213
|
+
};
|
|
2214
|
+
/**
|
|
2215
|
+
* 添加到待发送请求队列
|
|
2216
|
+
* @param params 数据参数
|
|
2217
|
+
*/
|
|
1823
2218
|
|
|
1824
2219
|
|
|
1825
|
-
|
|
2220
|
+
_this.addToPendingRequests = function (params) {
|
|
2221
|
+
// 从 LocalStorage 获取当前队列
|
|
2222
|
+
var currentRequests = _this.getPendingRequestsFromStorage(); // 添加新数据
|
|
2223
|
+
|
|
2224
|
+
|
|
2225
|
+
currentRequests.push(params); // 限制队列大小,防止内存溢出
|
|
2226
|
+
|
|
2227
|
+
var maxSize = _this.initConfig.pendingRequestsMaxSize || _this.DEFAULT_PENDING_REQUESTS_MAX_SIZE;
|
|
2228
|
+
|
|
2229
|
+
if (currentRequests.length > maxSize) {
|
|
2230
|
+
var trimmedRequests = currentRequests.slice(-maxSize);
|
|
2231
|
+
|
|
2232
|
+
if (_this.initConfig.showLog) {
|
|
2233
|
+
_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");
|
|
2234
|
+
}
|
|
2235
|
+
|
|
2236
|
+
_this.savePendingRequestsToStorage(trimmedRequests);
|
|
2237
|
+
} else {
|
|
2238
|
+
_this.savePendingRequestsToStorage(currentRequests);
|
|
1826
2239
|
}
|
|
1827
2240
|
};
|
|
1828
2241
|
/**
|
|
1829
|
-
*
|
|
2242
|
+
* 从 LocalStorage 恢复待发送请求
|
|
1830
2243
|
*/
|
|
1831
2244
|
|
|
1832
2245
|
|
|
1833
|
-
_this.
|
|
1834
|
-
|
|
1835
|
-
var queueString = JSON.stringify(_this.batchQueue); // 检查存储大小,避免超出 LocalStorage 限制(通常 5-10MB)
|
|
1836
|
-
// 如果队列过大,只保留最新的数据
|
|
2246
|
+
_this.restorePendingRequestsFromStorage = function () {
|
|
2247
|
+
var pendingRequests = _this.getPendingRequestsFromStorage();
|
|
1837
2248
|
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
2249
|
+
if (pendingRequests.length > 0) {
|
|
2250
|
+
if (_this.initConfig.showLog) {
|
|
2251
|
+
_this.printLog("\u4ECE LocalStorage \u6062\u590D " + pendingRequests.length + " \u6761\u5F85\u53D1\u9001\u8BF7\u6C42");
|
|
2252
|
+
} // 注意:恢复后不立即发送,避免重复
|
|
2253
|
+
// 数据会留在 LocalStorage 中,等待下次正常发送或页面卸载时发送
|
|
2254
|
+
// 这样可以避免与批量队列冲突
|
|
1841
2255
|
|
|
1842
|
-
|
|
2256
|
+
}
|
|
2257
|
+
};
|
|
2258
|
+
/**
|
|
2259
|
+
* 检查页面是否即将卸载
|
|
2260
|
+
* @returns 如果页面即将卸载返回 true,否则返回 false
|
|
2261
|
+
*/
|
|
1843
2262
|
|
|
1844
|
-
_this.printLog("\u961F\u5217\u8FC7\u5927\uFF0C\u5DF2\u622A\u65AD\u4FDD\u7559\u6700\u65B0 " + maxItems + " \u6761\u6570\u636E");
|
|
1845
|
-
}
|
|
1846
2263
|
|
|
1847
|
-
|
|
2264
|
+
_this.isPageUnloading = function () {
|
|
2265
|
+
return document.visibilityState === "hidden";
|
|
2266
|
+
};
|
|
2267
|
+
/**
|
|
2268
|
+
* 使用 sendBeacon 发送数据(页面卸载时的备用方案)
|
|
2269
|
+
* @param params 数据参数
|
|
2270
|
+
* @param serverUrl 服务器地址
|
|
2271
|
+
* @param contentType 内容类型
|
|
2272
|
+
* @returns 是否发送成功
|
|
2273
|
+
*/
|
|
2274
|
+
|
|
2275
|
+
|
|
2276
|
+
_this.sendWithBeacon = function (params, serverUrl, contentType) {
|
|
2277
|
+
try {
|
|
2278
|
+
var blob = new Blob([JSON.stringify(params)], {
|
|
2279
|
+
type: contentType || "application/json"
|
|
2280
|
+
});
|
|
2281
|
+
return navigator.sendBeacon(serverUrl, blob);
|
|
1848
2282
|
} catch (e) {
|
|
1849
|
-
|
|
1850
|
-
|
|
2283
|
+
if (_this.initConfig.showLog) {
|
|
2284
|
+
_this.printLog("sendBeacon \u53D1\u9001\u5931\u8D25: " + e);
|
|
2285
|
+
}
|
|
2286
|
+
|
|
2287
|
+
return false;
|
|
1851
2288
|
}
|
|
1852
2289
|
};
|
|
1853
2290
|
/**
|
|
1854
|
-
*
|
|
2291
|
+
* 刷新待发送的单个请求(正常情况下的发送)
|
|
2292
|
+
* 注意:这个方法会直接使用 ajax 发送,避免通过 sendData 导致重复
|
|
2293
|
+
*/
|
|
2294
|
+
|
|
2295
|
+
|
|
2296
|
+
_this.flushPendingRequests = function () {
|
|
2297
|
+
var pendingRequests = _this.getPendingRequestsFromStorage();
|
|
2298
|
+
|
|
2299
|
+
if (pendingRequests.length === 0) {
|
|
2300
|
+
return;
|
|
2301
|
+
} // 清除 LocalStorage 中的待发送请求
|
|
2302
|
+
|
|
2303
|
+
|
|
2304
|
+
_this.savePendingRequestsToStorage([]); // 直接使用 ajax 发送每个请求,避免通过 sendData 导致重复
|
|
2305
|
+
|
|
2306
|
+
|
|
2307
|
+
var _a = _this.initConfig,
|
|
2308
|
+
serverUrl = _a.serverUrl,
|
|
2309
|
+
sendTimeout = _a.sendTimeout,
|
|
2310
|
+
contentType = _a.contentType,
|
|
2311
|
+
showLog = _a.showLog,
|
|
2312
|
+
initHeader = _a.header;
|
|
2313
|
+
pendingRequests.forEach(function (params) {
|
|
2314
|
+
// 数据采样判断
|
|
2315
|
+
if (!_this.shouldSample()) {
|
|
2316
|
+
if (showLog) {
|
|
2317
|
+
_this.printLog("待发送请求已采样跳过");
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
return;
|
|
2321
|
+
}
|
|
2322
|
+
|
|
2323
|
+
if (showLog) {
|
|
2324
|
+
_this.printLog(params);
|
|
2325
|
+
} // 直接使用 ajax 发送
|
|
2326
|
+
|
|
2327
|
+
|
|
2328
|
+
_this.ajax({
|
|
2329
|
+
header: initHeader,
|
|
2330
|
+
url: serverUrl,
|
|
2331
|
+
type: "POST",
|
|
2332
|
+
data: JSON.stringify(params),
|
|
2333
|
+
contentType: contentType,
|
|
2334
|
+
credentials: false,
|
|
2335
|
+
timeout: sendTimeout,
|
|
2336
|
+
cors: true,
|
|
2337
|
+
success: function success() {
|
|
2338
|
+
if (showLog) {
|
|
2339
|
+
_this.printLog("待发送请求发送成功");
|
|
2340
|
+
}
|
|
2341
|
+
},
|
|
2342
|
+
error: function error(err) {
|
|
2343
|
+
if (showLog) {
|
|
2344
|
+
_this.printLog("\u5F85\u53D1\u9001\u8BF7\u6C42\u53D1\u9001\u5931\u8D25\uFF08\u4E0D\u518D\u91CD\u8BD5\uFF09: " + err);
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
});
|
|
2348
|
+
});
|
|
2349
|
+
};
|
|
2350
|
+
/**
|
|
2351
|
+
* 设置页面卸载监听器,确保数据发送
|
|
1855
2352
|
*/
|
|
1856
2353
|
|
|
1857
2354
|
|
|
1858
2355
|
_this.setupBeforeUnloadListener = function () {
|
|
1859
|
-
//
|
|
2356
|
+
// 避免重复设置监听器
|
|
2357
|
+
if (_this.isUnloadListenerSetup) {
|
|
2358
|
+
return;
|
|
2359
|
+
}
|
|
2360
|
+
|
|
2361
|
+
_this.isUnloadListenerSetup = true; // 使用 visibilitychange 事件(更可靠,支持页面跳转、切换标签页等场景)
|
|
2362
|
+
|
|
1860
2363
|
document.addEventListener("visibilitychange", function () {
|
|
1861
|
-
if (
|
|
1862
|
-
_this.
|
|
2364
|
+
if (_this.isPageUnloading()) {
|
|
2365
|
+
_this.flushPendingData();
|
|
1863
2366
|
}
|
|
1864
|
-
}); // 使用 beforeunload
|
|
2367
|
+
}); // 使用 beforeunload 事件作为备用(页面关闭/刷新)
|
|
1865
2368
|
|
|
1866
2369
|
window.addEventListener("beforeunload", function () {
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
2370
|
+
_this.flushPendingData();
|
|
2371
|
+
}); // 使用 pagehide 事件(更可靠,支持移动端)
|
|
2372
|
+
|
|
2373
|
+
window.addEventListener("pagehide", function () {
|
|
2374
|
+
_this.flushPendingData();
|
|
2375
|
+
});
|
|
2376
|
+
}; // 标记是否正在刷新待发送数据,避免重复发送
|
|
2377
|
+
|
|
2378
|
+
|
|
2379
|
+
_this.isFlushingPendingData = false;
|
|
2380
|
+
/**
|
|
2381
|
+
* 刷新待发送数据(在页面卸载/跳转时调用)
|
|
2382
|
+
*/
|
|
2383
|
+
|
|
2384
|
+
_this.flushPendingData = function () {
|
|
2385
|
+
// 如果正在刷新,避免重复执行
|
|
2386
|
+
if (_this.isFlushingPendingData) {
|
|
2387
|
+
return;
|
|
2388
|
+
} // 页面卸载时停止定时器
|
|
2389
|
+
|
|
2390
|
+
|
|
2391
|
+
_this.stopPageDurationTimer(); // 收集所有待发送的数据
|
|
2392
|
+
|
|
2393
|
+
|
|
2394
|
+
var allPendingData = []; // 如果有批量队列,添加到待发送列表
|
|
2395
|
+
|
|
2396
|
+
var batchQueue = _this.getBatchQueueFromStorage();
|
|
2397
|
+
|
|
2398
|
+
if (batchQueue.length > 0) {
|
|
2399
|
+
allPendingData.push.apply(allPendingData, batchQueue);
|
|
2400
|
+
} // 如果有待发送的单个请求,也添加到列表
|
|
2401
|
+
|
|
2402
|
+
|
|
2403
|
+
var pendingRequests = _this.getPendingRequestsFromStorage();
|
|
2404
|
+
|
|
2405
|
+
if (pendingRequests.length > 0) {
|
|
2406
|
+
allPendingData.push.apply(allPendingData, pendingRequests);
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2409
|
+
if (allPendingData.length === 0) {
|
|
2410
|
+
return;
|
|
2411
|
+
} // 标记正在刷新
|
|
2412
|
+
|
|
2413
|
+
|
|
2414
|
+
_this.isFlushingPendingData = true; // 先保存到 LocalStorage,确保数据不丢失(在发送前保存)
|
|
2415
|
+
|
|
2416
|
+
try {
|
|
2417
|
+
if (_this.initConfig.batchSend) {
|
|
2418
|
+
_this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, JSON.stringify(allPendingData));
|
|
2419
|
+
} else {
|
|
2420
|
+
_this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, JSON.stringify(allPendingData));
|
|
2421
|
+
}
|
|
2422
|
+
} catch (e) {
|
|
2423
|
+
if (_this.initConfig.showLog) {
|
|
2424
|
+
_this.printLog("\u4FDD\u5B58\u5F85\u53D1\u9001\u8BF7\u6C42\u5230 LocalStorage \u5931\u8D25: " + e);
|
|
2425
|
+
}
|
|
2426
|
+
} // 使用 sendBeacon 发送数据(最可靠的方式)
|
|
2427
|
+
|
|
2428
|
+
|
|
2429
|
+
if (navigator.sendBeacon && _this.initConfig.serverUrl) {
|
|
2430
|
+
try {
|
|
2431
|
+
// 如果只有一条数据,直接发送;否则批量发送
|
|
2432
|
+
var dataToSend = allPendingData.length === 1 ? allPendingData[0] : allPendingData;
|
|
2433
|
+
var blob = new Blob([JSON.stringify(dataToSend)], {
|
|
2434
|
+
type: _this.initConfig.contentType || "application/json"
|
|
2435
|
+
});
|
|
2436
|
+
var sent = navigator.sendBeacon(_this.initConfig.serverUrl, blob);
|
|
2437
|
+
|
|
2438
|
+
if (sent) {
|
|
2439
|
+
// 发送成功,清除所有 LocalStorage
|
|
2440
|
+
_this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
|
|
1878
2441
|
|
|
1879
|
-
|
|
2442
|
+
_this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, "[]");
|
|
1880
2443
|
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
// sendBeacon 失败,保存到 LocalStorage
|
|
1884
|
-
_this.saveBatchQueueToStorage();
|
|
2444
|
+
if (_this.initConfig.showLog) {
|
|
2445
|
+
_this.printLog("\u9875\u9762\u5378\u8F7D\u65F6\u6210\u529F\u53D1\u9001 " + allPendingData.length + " \u6761\u6570\u636E");
|
|
1885
2446
|
}
|
|
1886
2447
|
} else {
|
|
1887
|
-
//
|
|
1888
|
-
_this.
|
|
2448
|
+
// sendBeacon 返回 false,数据已在 LocalStorage 中,等待下次恢复
|
|
2449
|
+
if (_this.initConfig.showLog) {
|
|
2450
|
+
_this.printLog("sendBeacon \u8FD4\u56DE false\uFF0C\u6570\u636E\u5DF2\u4FDD\u5B58\u5230 LocalStorage \u7B49\u5F85\u4E0B\u6B21\u6062\u590D");
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
} catch (e) {
|
|
2454
|
+
// sendBeacon 失败,数据已在 LocalStorage 中,等待下次恢复
|
|
2455
|
+
if (_this.initConfig.showLog) {
|
|
2456
|
+
_this.printLog("\u9875\u9762\u5378\u8F7D\u65F6\u53D1\u9001\u6570\u636E\u5931\u8D25: " + e + "\uFF0C\u6570\u636E\u5DF2\u4FDD\u5B58\u5230 LocalStorage");
|
|
1889
2457
|
}
|
|
2458
|
+
} finally {
|
|
2459
|
+
// 重置标记
|
|
2460
|
+
_this.isFlushingPendingData = false;
|
|
1890
2461
|
}
|
|
1891
|
-
}
|
|
2462
|
+
} else {
|
|
2463
|
+
// 不支持 sendBeacon,数据已在 LocalStorage 中,等待下次恢复
|
|
2464
|
+
if (_this.initConfig.showLog) {
|
|
2465
|
+
_this.printLog("\u4E0D\u652F\u6301 sendBeacon\uFF0C\u6570\u636E\u5DF2\u4FDD\u5B58\u5230 LocalStorage \u7B49\u5F85\u4E0B\u6B21\u6062\u590D");
|
|
2466
|
+
} // 重置标记
|
|
2467
|
+
|
|
2468
|
+
|
|
2469
|
+
_this.isFlushingPendingData = false;
|
|
2470
|
+
}
|
|
1892
2471
|
};
|
|
1893
2472
|
/**
|
|
1894
2473
|
* 发送数据通用函数
|
|
@@ -1910,7 +2489,8 @@ function (_super) {
|
|
|
1910
2489
|
contentType = _a.contentType,
|
|
1911
2490
|
showLog = _a.showLog,
|
|
1912
2491
|
initHeader = _a.header,
|
|
1913
|
-
batchSend = _a.batchSend
|
|
2492
|
+
batchSend = _a.batchSend,
|
|
2493
|
+
sendMethod = _a.sendMethod;
|
|
1914
2494
|
if (!!showLog) _this.printLog(params); // 如果启用批量发送
|
|
1915
2495
|
|
|
1916
2496
|
if (batchSend) {
|
|
@@ -1920,16 +2500,64 @@ function (_super) {
|
|
|
1920
2500
|
success: true,
|
|
1921
2501
|
message: "已添加到批量队列"
|
|
1922
2502
|
});
|
|
1923
|
-
}
|
|
2503
|
+
} // 判断是否使用 sendBeacon
|
|
2504
|
+
|
|
2505
|
+
|
|
2506
|
+
var shouldUseBeacon = _this.shouldUseBeacon(sendMethod, header, initHeader); // 如果使用 sendBeacon
|
|
2507
|
+
|
|
2508
|
+
|
|
2509
|
+
if (shouldUseBeacon) {
|
|
2510
|
+
// 检查页面是否即将卸载,如果是,直接使用 sendBeacon 发送,避免被取消
|
|
2511
|
+
if (_this.isPageUnloading()) {
|
|
2512
|
+
var sent = _this.sendWithBeacon(params, serverUrl, contentType);
|
|
2513
|
+
|
|
2514
|
+
if (sent) {
|
|
2515
|
+
return Promise.resolve({
|
|
2516
|
+
success: true,
|
|
2517
|
+
message: "页面卸载时发送成功"
|
|
2518
|
+
});
|
|
2519
|
+
} else {
|
|
2520
|
+
// sendBeacon 返回 false,添加到待发送队列
|
|
2521
|
+
_this.addToPendingRequests(params);
|
|
2522
|
+
|
|
2523
|
+
return Promise.resolve({
|
|
2524
|
+
success: true,
|
|
2525
|
+
message: "已添加到待发送队列"
|
|
2526
|
+
});
|
|
2527
|
+
}
|
|
2528
|
+
} // 正常情况使用 sendBeacon
|
|
2529
|
+
|
|
1924
2530
|
|
|
1925
|
-
if (_this.isSupportBeaconSend() === true && !header && !initHeader) {
|
|
1926
2531
|
return _this.sendBeacon({
|
|
1927
2532
|
contentType: contentType,
|
|
1928
2533
|
url: serverUrl,
|
|
1929
2534
|
data: params
|
|
2535
|
+
}).catch(function (err) {
|
|
2536
|
+
// sendBeacon 失败,添加到待发送队列,避免数据丢失
|
|
2537
|
+
_this.addToPendingRequests(params);
|
|
2538
|
+
|
|
2539
|
+
return Promise.resolve({
|
|
2540
|
+
success: true,
|
|
2541
|
+
message: "sendBeacon 失败,已添加到待发送队列"
|
|
2542
|
+
});
|
|
1930
2543
|
});
|
|
1931
2544
|
} else {
|
|
2545
|
+
// 使用 XMLHttpRequest 发送
|
|
1932
2546
|
return new Promise(function (resolve, reject) {
|
|
2547
|
+
// 如果页面即将卸载且配置为 auto,尝试使用 sendBeacon 作为备用
|
|
2548
|
+
if (_this.isPageUnloading() && sendMethod === 'auto' && _this.isSupportBeaconSend() && !header && !initHeader) {
|
|
2549
|
+
var sent = _this.sendWithBeacon(params, serverUrl, contentType);
|
|
2550
|
+
|
|
2551
|
+
if (sent) {
|
|
2552
|
+
resolve({
|
|
2553
|
+
success: true,
|
|
2554
|
+
message: "页面卸载时使用 sendBeacon 发送成功"
|
|
2555
|
+
});
|
|
2556
|
+
return;
|
|
2557
|
+
} // sendBeacon 失败,继续使用 XMLHttpRequest
|
|
2558
|
+
|
|
2559
|
+
}
|
|
2560
|
+
|
|
1933
2561
|
_this.ajax({
|
|
1934
2562
|
header: header || initHeader,
|
|
1935
2563
|
url: serverUrl,
|
|
@@ -1946,7 +2574,20 @@ function (_super) {
|
|
|
1946
2574
|
});
|
|
1947
2575
|
},
|
|
1948
2576
|
error: function error(err, status) {
|
|
1949
|
-
|
|
2577
|
+
// 如果请求失败且页面即将卸载且配置为 auto,尝试使用 sendBeacon
|
|
2578
|
+
if (_this.isPageUnloading() && sendMethod === 'auto' && _this.isSupportBeaconSend() && !header && !initHeader) {
|
|
2579
|
+
var sent = _this.sendWithBeacon(params, serverUrl, contentType);
|
|
2580
|
+
|
|
2581
|
+
if (sent) {
|
|
2582
|
+
resolve({
|
|
2583
|
+
success: true,
|
|
2584
|
+
message: "XMLHttpRequest 失败,已使用 sendBeacon 发送"
|
|
2585
|
+
});
|
|
2586
|
+
return;
|
|
2587
|
+
}
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
reject({
|
|
1950
2591
|
success: false,
|
|
1951
2592
|
message: String(err),
|
|
1952
2593
|
code: status
|
|
@@ -1956,6 +2597,30 @@ function (_super) {
|
|
|
1956
2597
|
});
|
|
1957
2598
|
}
|
|
1958
2599
|
};
|
|
2600
|
+
/**
|
|
2601
|
+
* @description 判断是否应该使用 sendBeacon
|
|
2602
|
+
* @param sendMethod 配置的发送方式
|
|
2603
|
+
* @param header 自定义 header
|
|
2604
|
+
* @param initHeader 初始化配置的 header
|
|
2605
|
+
* @returns 是否使用 sendBeacon
|
|
2606
|
+
*/
|
|
2607
|
+
|
|
2608
|
+
|
|
2609
|
+
_this.shouldUseBeacon = function (sendMethod, header, initHeader) {
|
|
2610
|
+
// 如果配置为 xhr,不使用 beacon
|
|
2611
|
+
if (sendMethod === 'xhr') {
|
|
2612
|
+
return false;
|
|
2613
|
+
} // 如果配置为 beacon,检查是否支持
|
|
2614
|
+
|
|
2615
|
+
|
|
2616
|
+
if (sendMethod === 'beacon') {
|
|
2617
|
+
return _this.isSupportBeaconSend() === true;
|
|
2618
|
+
} // 如果配置为 auto(默认),使用原有逻辑
|
|
2619
|
+
// 只有在支持 sendBeacon 且没有自定义 header 时才使用
|
|
2620
|
+
|
|
2621
|
+
|
|
2622
|
+
return _this.isSupportBeaconSend() === true && !header && !initHeader;
|
|
2623
|
+
};
|
|
1959
2624
|
/**
|
|
1960
2625
|
* @description 留存时长上报
|
|
1961
2626
|
* @param type
|
|
@@ -1984,6 +2649,121 @@ function (_super) {
|
|
|
1984
2649
|
_this.setCookie("retainedStartTime", _this.getTimeStamp());
|
|
1985
2650
|
}
|
|
1986
2651
|
};
|
|
2652
|
+
/**
|
|
2653
|
+
* @description 用户主动上报页面停留时长
|
|
2654
|
+
* @param duration 自定义停留时长(毫秒),如果不传则自动计算从页面加载(或上次调用)到当前的时长
|
|
2655
|
+
* @param options 可选参数,包括自定义描述、业务参数等
|
|
2656
|
+
* @param resetStartTime 是否重置起始时间,默认 true(手动上报后重置,定时上报不重置)
|
|
2657
|
+
* @returns Promise<TrackingResponse> 上报结果
|
|
2658
|
+
*/
|
|
2659
|
+
|
|
2660
|
+
|
|
2661
|
+
_this.trackPageDuration = function (duration, options, resetStartTime) {
|
|
2662
|
+
if (resetStartTime === void 0) {
|
|
2663
|
+
resetStartTime = true;
|
|
2664
|
+
} // 计算停留时长
|
|
2665
|
+
|
|
2666
|
+
|
|
2667
|
+
var retainedDuration;
|
|
2668
|
+
|
|
2669
|
+
if (duration !== undefined && duration !== null) {
|
|
2670
|
+
// 使用自定义时长
|
|
2671
|
+
retainedDuration = Math.max(duration, 0);
|
|
2672
|
+
} else {
|
|
2673
|
+
// 自动计算时长
|
|
2674
|
+
var __time = _this.getCookie("retainedStartTime");
|
|
2675
|
+
|
|
2676
|
+
var retainedStartTime = __time ? +__time : _this.getTimeStamp();
|
|
2677
|
+
|
|
2678
|
+
var currentTime = _this.getTimeStamp();
|
|
2679
|
+
|
|
2680
|
+
retainedDuration = Math.max(currentTime - retainedStartTime, 0);
|
|
2681
|
+
} // 构建参数
|
|
2682
|
+
|
|
2683
|
+
|
|
2684
|
+
var desc = (options === null || options === void 0 ? void 0 : options.desc) || _this.eventDescMap["PageRetained"];
|
|
2685
|
+
var pageKey = (options === null || options === void 0 ? void 0 : options.pageKey) || _this.pageKey;
|
|
2686
|
+
var business = (options === null || options === void 0 ? void 0 : options.business) || {};
|
|
2687
|
+
var header = options === null || options === void 0 ? void 0 : options.header;
|
|
2688
|
+
|
|
2689
|
+
var params = _this.getParams({
|
|
2690
|
+
event: "PageRetained",
|
|
2691
|
+
desc: desc,
|
|
2692
|
+
itemKey: _this.getItemKey(undefined, pageKey),
|
|
2693
|
+
privateParamMap: {
|
|
2694
|
+
business: business,
|
|
2695
|
+
retainedDuration: retainedDuration
|
|
2696
|
+
}
|
|
2697
|
+
}); // 上报数据
|
|
2698
|
+
|
|
2699
|
+
|
|
2700
|
+
var result = _this.sendData(params, header); // 根据 resetStartTime 参数决定是否重置起始时间
|
|
2701
|
+
// 手动上报后重置起始时间,定时上报不重置(累积计算)
|
|
2702
|
+
|
|
2703
|
+
|
|
2704
|
+
if (resetStartTime) {
|
|
2705
|
+
_this.setCookie("retainedStartTime", _this.getTimeStamp());
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
return result;
|
|
2709
|
+
};
|
|
2710
|
+
/**
|
|
2711
|
+
* @description 启动定时上报页面停留时长的定时器
|
|
2712
|
+
*/
|
|
2713
|
+
|
|
2714
|
+
|
|
2715
|
+
_this.startPageDurationTimer = function () {
|
|
2716
|
+
// 先停止现有的定时器(避免重复启动)
|
|
2717
|
+
_this.stopPageDurationTimer();
|
|
2718
|
+
|
|
2719
|
+
var interval = _this.initConfig.pageDurationInterval || 30000; // 检查间隔时间是否有效
|
|
2720
|
+
|
|
2721
|
+
if (interval <= 0) {
|
|
2722
|
+
if (_this.initConfig.showLog) {
|
|
2723
|
+
_this.printLog("定时上报间隔时间无效,已禁用定时上报");
|
|
2724
|
+
}
|
|
2725
|
+
|
|
2726
|
+
return;
|
|
2727
|
+
} // 启动定时器
|
|
2728
|
+
|
|
2729
|
+
|
|
2730
|
+
_this.pageDurationTimer = window.setInterval(function () {
|
|
2731
|
+
// 只在页面可见时上报(避免后台上报)
|
|
2732
|
+
if (document.visibilityState === "visible") {
|
|
2733
|
+
// 定时上报:retainedDuration 直接使用上报间隔时间,数据工程师会清洗数据
|
|
2734
|
+
_this.trackPageDuration(interval, {
|
|
2735
|
+
desc: "定时上报页面停留时长",
|
|
2736
|
+
business: {
|
|
2737
|
+
reportType: "interval",
|
|
2738
|
+
interval: interval
|
|
2739
|
+
}
|
|
2740
|
+
}, true).catch(function (err) {
|
|
2741
|
+
if (_this.initConfig.showLog) {
|
|
2742
|
+
_this.printLog("\u5B9A\u65F6\u4E0A\u62A5\u9875\u9762\u505C\u7559\u65F6\u957F\u5931\u8D25: " + err);
|
|
2743
|
+
}
|
|
2744
|
+
});
|
|
2745
|
+
}
|
|
2746
|
+
}, interval);
|
|
2747
|
+
|
|
2748
|
+
if (_this.initConfig.showLog) {
|
|
2749
|
+
_this.printLog("\u5B9A\u65F6\u4E0A\u62A5\u9875\u9762\u505C\u7559\u65F6\u957F\u5DF2\u542F\u52A8\uFF0C\u95F4\u9694: " + interval + "ms");
|
|
2750
|
+
}
|
|
2751
|
+
};
|
|
2752
|
+
/**
|
|
2753
|
+
* @description 停止定时上报页面停留时长的定时器
|
|
2754
|
+
*/
|
|
2755
|
+
|
|
2756
|
+
|
|
2757
|
+
_this.stopPageDurationTimer = function () {
|
|
2758
|
+
if (_this.pageDurationTimer !== null) {
|
|
2759
|
+
clearInterval(_this.pageDurationTimer);
|
|
2760
|
+
_this.pageDurationTimer = null;
|
|
2761
|
+
|
|
2762
|
+
if (_this.initConfig.showLog) {
|
|
2763
|
+
_this.printLog("定时上报页面停留时长已停止");
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2766
|
+
};
|
|
1987
2767
|
/**
|
|
1988
2768
|
* @description 获取 itemKey
|
|
1989
2769
|
* @param {[string]} partkey [控件/自定义事件的唯一标识]
|
|
@@ -2001,7 +2781,7 @@ function (_super) {
|
|
|
2001
2781
|
}, "");
|
|
2002
2782
|
};
|
|
2003
2783
|
|
|
2004
|
-
_this.sdkVersion = "1.2.
|
|
2784
|
+
_this.sdkVersion = "1.2.3"; // sdk版本
|
|
2005
2785
|
|
|
2006
2786
|
_this.initConfig = {
|
|
2007
2787
|
appKey: "",
|
|
@@ -2017,7 +2797,12 @@ function (_super) {
|
|
|
2017
2797
|
sampleRate: 1,
|
|
2018
2798
|
batchSend: false,
|
|
2019
2799
|
batchInterval: 5000,
|
|
2020
|
-
batchMaxSize: 10
|
|
2800
|
+
batchMaxSize: 10,
|
|
2801
|
+
trackPartKeyClick: false,
|
|
2802
|
+
pendingRequestsMaxSize: 50,
|
|
2803
|
+
autoTrackPageDurationInterval: false,
|
|
2804
|
+
pageDurationInterval: 30000,
|
|
2805
|
+
sendMethod: "auto" // 数据发送方式:auto(自动选择)、xhr(XMLHttpRequest)、beacon(sendBeacon)
|
|
2021
2806
|
|
|
2022
2807
|
}; // 系统信息
|
|
2023
2808
|
|