@fle-sdk/event-tracking-web 1.2.2 → 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/lib/index.js CHANGED
@@ -1,2337 +1,2841 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
- typeof define === 'function' && define.amd ? define(factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.WebTracking = factory());
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
3
+ typeof define === 'function' && define.amd ? define(factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.WebTracking = factory());
5
5
  }(this, (function () { 'use strict';
6
6
 
7
- /*! *****************************************************************************
8
- Copyright (c) Microsoft Corporation.
7
+ function _typeof(obj) {
8
+ "@babel/helpers - typeof";
9
+
10
+ if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
11
+ _typeof = function (obj) {
12
+ return typeof obj;
13
+ };
14
+ } else {
15
+ _typeof = function (obj) {
16
+ return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
17
+ };
18
+ }
19
+
20
+ return _typeof(obj);
21
+ }
22
+
23
+ /*! *****************************************************************************
24
+ Copyright (c) Microsoft Corporation.
9
25
 
10
- Permission to use, copy, modify, and/or distribute this software for any
11
- purpose with or without fee is hereby granted.
26
+ Permission to use, copy, modify, and/or distribute this software for any
27
+ purpose with or without fee is hereby granted.
12
28
 
13
- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
14
- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
15
- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
16
- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
17
- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
18
- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
19
- PERFORMANCE OF THIS SOFTWARE.
20
- ***************************************************************************** */
21
- /* global Reflect, Promise */
29
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
30
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
31
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
32
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
33
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
34
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
35
+ PERFORMANCE OF THIS SOFTWARE.
36
+ ***************************************************************************** */
37
+ /* global Reflect, Promise */
22
38
 
23
- var extendStatics = function(d, b) {
24
- extendStatics = Object.setPrototypeOf ||
25
- ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
26
- function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
27
- return extendStatics(d, b);
28
- };
39
+ var extendStatics = function(d, b) {
40
+ extendStatics = Object.setPrototypeOf ||
41
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
42
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
43
+ return extendStatics(d, b);
44
+ };
29
45
 
30
- function __extends(d, b) {
31
- if (typeof b !== "function" && b !== null)
32
- throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
33
- extendStatics(d, b);
34
- function __() { this.constructor = d; }
35
- d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
36
- }
46
+ function __extends(d, b) {
47
+ if (typeof b !== "function" && b !== null)
48
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
49
+ extendStatics(d, b);
50
+ function __() { this.constructor = d; }
51
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
52
+ }
37
53
 
38
- var __assign = function() {
39
- __assign = Object.assign || function __assign(t) {
40
- for (var s, i = 1, n = arguments.length; i < n; i++) {
41
- s = arguments[i];
42
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
43
- }
44
- return t;
45
- };
46
- return __assign.apply(this, arguments);
47
- };
54
+ var __assign = function() {
55
+ __assign = Object.assign || function __assign(t) {
56
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
57
+ s = arguments[i];
58
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
59
+ }
60
+ return t;
61
+ };
62
+ return __assign.apply(this, arguments);
63
+ };
48
64
 
49
- function __spreadArray(to, from) {
50
- for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
51
- to[j] = from[i];
52
- return to;
53
- }
65
+ function __spreadArray(to, from) {
66
+ for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
67
+ to[j] = from[i];
68
+ return to;
69
+ }
70
+
71
+ var WebTrackingTools =
72
+ /** @class */
73
+ function () {
74
+ function WebTrackingTools() {
75
+ var _this = this;
76
+ /**
77
+ * 系统信息
78
+ */
54
79
 
55
- function _typeof(obj) {
56
- "@babel/helpers - typeof";
57
80
 
58
- if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
59
- _typeof = function (obj) {
60
- return typeof obj;
61
- };
62
- } else {
63
- _typeof = function (obj) {
64
- return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
65
- };
66
- }
81
+ this.getSystemsInfo = function (platform) {
82
+ // print system info
83
+ var ua = navigator.userAgent;
84
+ var language = navigator.language;
85
+ var _platform = "other";
86
+ var logMsg = [];
87
+ var systemsInfo = {
88
+ language: language
89
+ }; // wechat client version
90
+
91
+ var wxVersionMatch = ua.match(/MicroMessenger\/([\d\.]+)/i);
92
+ var wxVersion = wxVersionMatch && wxVersionMatch[1] ? wxVersionMatch[1] : null; // device & system
93
+
94
+ var ipod = ua.match(/(ipod).*\s([\d_]+)/i),
95
+ ipad = ua.match(/(ipad).*\s([\d_]+)/i),
96
+ iphone = ua.match(/(iphone)\sos\s([\d_]+)/i),
97
+ android = ua.match(/(android)\s([\d\.]+)/i),
98
+ windows = ua.match(/(Windows NT)\s([\d\.]+)/i),
99
+ mac = ua.match(/(Mac OS X)\s([\d_]+)/i);
100
+ logMsg = [];
101
+
102
+ if (android) {
103
+ logMsg.push("Android " + android[2]);
104
+ _platform = "h5";
105
+ } else if (iphone) {
106
+ logMsg.push("iPhone, iOS " + iphone[2].replace(/_/g, "."));
107
+ _platform = "h5";
108
+ } else if (ipad) {
109
+ logMsg.push("iPad, iOS " + ipad[2].replace(/_/g, "."));
110
+ _platform = "ipad";
111
+ } else if (ipod) {
112
+ logMsg.push("iPod, iOS " + ipod[2].replace(/_/g, "."));
113
+ _platform = "h5";
114
+ } else if (windows) {
115
+ logMsg.push("Windows " + windows[2].replace(/_/g, "."));
116
+ _platform = "pc";
117
+ } else if (mac) {
118
+ logMsg.push("Mac, MacOS " + mac[2].replace(/_/g, "."));
119
+ _platform = "pc";
120
+ }
121
+
122
+ if (wxVersion) {
123
+ logMsg.push("WeChat " + wxVersion);
124
+ }
125
+
126
+ systemsInfo["client"] = logMsg.length ? logMsg.join(", ") : "Unknown";
127
+ systemsInfo["platform"] = platform || _platform; // network type
128
+
129
+ var network = ua.toLowerCase().match(/ nettype\/([^ ]+)/g);
130
+
131
+ if (network && network[0]) {
132
+ // @ts-ignore
133
+ network = network[0].split("/");
134
+ logMsg = [network[1]];
135
+ systemsInfo["network"] = logMsg.length ? logMsg.join(", ") : "Unknown";
136
+ } // User Agent
137
+
138
+
139
+ systemsInfo["ua"] = ua;
140
+ return systemsInfo;
141
+ };
142
+ /**
143
+ * 监听事件
144
+ * @param {Window | Element} target
145
+ * @param {String} type
146
+ * @param {Function} handler
147
+ */
148
+
149
+
150
+ this.addEventListener = function (target, type, handler) {
151
+ if (target.addEventListener) {
152
+ target.addEventListener(type, handler, false);
153
+ } else {
154
+ target.attachEvent && target.attachEvent("on" + type, function (event) {
155
+ return handler.call(target, event);
156
+ }, false);
157
+ }
158
+ };
159
+ /**
160
+ * 移除监听事件
161
+ * @param {Element} target
162
+ * @param {String} type
163
+ * @param {Funtion} handler
164
+ */
165
+
166
+
167
+ this.removeEventListener = function (target, type, handler) {
168
+ if (target.removeEventListener) {
169
+ target.removeEventListener(type, handler);
170
+ } else {
171
+ target.detachEvent && target.detachEvent("on" + type, function (event) {
172
+ return handler.call(target, event);
173
+ }, true);
174
+ }
175
+ };
176
+ /**
177
+ * 重写history[pushState][replaceState]方法
178
+ */
179
+
180
+
181
+ this.rewriteHistory = function () {
182
+ var history = window.history;
183
+
184
+ var historyWrap = function historyWrap(type) {
185
+ var history = window.history;
186
+ var orig = history[type];
187
+ var e = new Event(type);
188
+ return function () {
189
+ var rv = orig.apply(history, arguments);
190
+ e.arguments = arguments;
191
+ window.dispatchEvent(e);
192
+ return rv;
193
+ };
194
+ }; // 重写
195
+
196
+
197
+ if (!!window.history.pushState) {
198
+ history.pushState = historyWrap("pushState");
199
+ history.replaceState = historyWrap("replaceState");
200
+ }
201
+ };
202
+
203
+ this.isArray = Array.isArray || function (obj) {
204
+ return toString.call(obj) === "[object Array]";
205
+ };
206
+ /**
207
+ * 将json序列化成json字符串
208
+ * @param obj
209
+ * @returns {[JsonString]} [json字符串]
210
+ */
67
211
 
68
- return _typeof(obj);
69
- }
70
212
 
71
- var WebTrackingTools =
72
- /** @class */
73
- function () {
74
- function WebTrackingTools() {
75
- var _this = this;
76
- /**
77
- * 系统信息
78
- */
79
-
80
-
81
- this.getSystemsInfo = function (platform) {
82
- // print system info
83
- var ua = navigator.userAgent;
84
- var language = navigator.language;
85
- var _platform = "other";
86
- var logMsg = [];
87
- var systemsInfo = {
88
- language: language
89
- }; // wechat client version
90
-
91
- var wxVersionMatch = ua.match(/MicroMessenger\/([\d\.]+)/i);
92
- var wxVersion = wxVersionMatch && wxVersionMatch[1] ? wxVersionMatch[1] : null; // device & system
93
-
94
- var ipod = ua.match(/(ipod).*\s([\d_]+)/i),
95
- ipad = ua.match(/(ipad).*\s([\d_]+)/i),
96
- iphone = ua.match(/(iphone)\sos\s([\d_]+)/i),
97
- android = ua.match(/(android)\s([\d\.]+)/i),
98
- windows = ua.match(/(Windows NT)\s([\d\.]+)/i),
99
- mac = ua.match(/(Mac OS X)\s([\d_]+)/i);
100
- logMsg = [];
101
-
102
- if (android) {
103
- logMsg.push("Android " + android[2]);
104
- _platform = "h5";
105
- } else if (iphone) {
106
- logMsg.push("iPhone, iOS " + iphone[2].replace(/_/g, "."));
107
- _platform = "h5";
108
- } else if (ipad) {
109
- logMsg.push("iPad, iOS " + ipad[2].replace(/_/g, "."));
110
- _platform = "ipad";
111
- } else if (ipod) {
112
- logMsg.push("iPod, iOS " + ipod[2].replace(/_/g, "."));
113
- _platform = "h5";
114
- } else if (windows) {
115
- logMsg.push("Windows " + windows[2].replace(/_/g, "."));
116
- _platform = "pc";
117
- } else if (mac) {
118
- logMsg.push("Mac, MacOS " + mac[2].replace(/_/g, "."));
119
- _platform = "pc";
213
+ this.formatJsonString = function (obj) {
214
+ try {
215
+ return JSON.stringify(obj, null, " ");
216
+ } catch (e) {
217
+ return JSON.stringify(obj);
218
+ }
219
+ };
220
+
221
+ this.nativeForEach = Array.prototype.forEach;
222
+ this.slice = Array.prototype.slice;
223
+ this.hasOwnProperty = Object.prototype.hasOwnProperty;
224
+ this.breaker = {};
225
+ /**
226
+ * @description 循环遍历
227
+ */
228
+
229
+ this.each = function (obj, iterator, context) {
230
+ if (obj == null) {
231
+ return false;
232
+ }
233
+
234
+ if (_this.nativeForEach && obj.forEach === _this.nativeForEach) {
235
+ obj.forEach(iterator, context);
236
+ } else if (_this.isArray(obj) && obj.length === +obj.length) {
237
+ for (var i = 0, l = obj.length; i < l; i++) {
238
+ if (i in obj && iterator.call(context, obj[i], i, obj) === _this.breaker) {
239
+ return false;
240
+ }
120
241
  }
242
+ } else {
243
+ for (var key in obj) {
244
+ if (_this.hasOwnProperty.call(obj, key)) {
245
+ if (iterator.call(context, obj[key], key, obj) === _this.breaker) {
246
+ return false;
247
+ }
248
+ }
249
+ }
250
+ }
121
251
 
122
- if (wxVersion) {
123
- logMsg.push("WeChat " + wxVersion);
252
+ return true;
253
+ };
254
+
255
+ this.getDomIndex = function (el) {
256
+ if (!el.parentNode) return -1;
257
+ var i = 0;
258
+ var nodeName = el.tagName;
259
+ var list = el.parentNode.children;
260
+
261
+ for (var n = 0; n < list.length; n++) {
262
+ if (list[n].tagName === nodeName) {
263
+ if (el === list[n]) {
264
+ return i;
265
+ } else {
266
+ i++;
267
+ }
124
268
  }
269
+ }
125
270
 
126
- systemsInfo["client"] = logMsg.length ? logMsg.join(", ") : "Unknown";
127
- systemsInfo["platform"] = platform || _platform; // network type
271
+ return -1;
272
+ };
128
273
 
129
- var network = ua.toLowerCase().match(/ nettype\/([^ ]+)/g);
274
+ this.selector = function (el) {
275
+ var i = el.parentNode && 9 == el.parentNode.nodeType ? -1 : _this.getDomIndex(el);
130
276
 
131
- if (network && network[0]) {
132
- // @ts-ignore
133
- network = network[0].split("/");
134
- logMsg = [network[1]];
135
- systemsInfo["network"] = logMsg.length ? logMsg.join(", ") : "Unknown";
136
- } // User Agent
277
+ if (el.getAttribute && el.getAttribute("id") && /^[A-Za-z][-A-Za-z0-9_:.]*$/.test(el.getAttribute("id"))) {
278
+ return "#" + el.getAttribute("id");
279
+ } else {
280
+ return el.tagName.toLowerCase() + (~i ? ":nth-of-type(" + (i + 1) + ")" : "");
281
+ }
282
+ };
137
283
 
284
+ this.getDomSelector = function (el, arr) {
285
+ if (!el || !el.parentNode || !el.parentNode.children) {
286
+ return false;
287
+ }
138
288
 
139
- systemsInfo["ua"] = ua;
140
- return systemsInfo;
141
- };
142
- /**
143
- * 监听事件
144
- * @param {Window | Element} target
145
- * @param {String} type
146
- * @param {Function} handler
147
- */
289
+ arr = arr && arr.join ? arr : [];
290
+ var name = el.nodeName.toLowerCase();
291
+
292
+ if (!el || name === "body" || 1 != el.nodeType) {
293
+ arr.unshift("body");
294
+ return arr.join(" > ");
295
+ }
296
+
297
+ arr.unshift(_this.selector(el));
298
+ if (el.getAttribute && el.getAttribute("id") && /^[A-Za-z][-A-Za-z0-9_:.]*$/.test(el.getAttribute("id"))) return arr.join(" > ");
299
+ return _this.getDomSelector(el.parentNode, arr);
300
+ };
301
+ /**
302
+ * @description 增强cookie操作
303
+ */
148
304
 
149
305
 
150
- this.addEventListener = function (target, type, handler) {
151
- if (target.addEventListener) {
152
- target.addEventListener(type, handler, false);
306
+ this.getCookie = function (name) {
307
+ var nameEQ = name + "=";
308
+ var ca = document.cookie.split(";");
309
+
310
+ for (var i = 0; i < ca.length; i++) {
311
+ var c = ca[i];
312
+
313
+ while (c.charAt(0) == " ") {
314
+ c = c.substring(1, c.length);
315
+ }
316
+
317
+ if (c.indexOf(nameEQ) == 0) {
318
+ return this._decodeURIComponent(c.substring(nameEQ.length, c.length));
319
+ }
320
+ }
321
+
322
+ return null;
323
+ };
324
+
325
+ this.setCookie = function (name, value, days) {
326
+ var cdomain = "",
327
+ expires = "",
328
+ secure = "",
329
+ samesite = "";
330
+ days = days == null ? 73000 : days;
331
+ var domain = this.getMainHost();
332
+ cdomain = domain ? "; domain=" + domain : "";
333
+
334
+ if (days !== 0) {
335
+ var date = new Date();
336
+
337
+ if (String(days).slice(-1) === "s") {
338
+ date.setTime(date.getTime() + Number(String(days).slice(0, -1)) * 1000);
153
339
  } else {
154
- target.attachEvent && target.attachEvent("on" + type, function (event) {
155
- return handler.call(target, event);
156
- }, false);
340
+ date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
157
341
  }
158
- };
159
- /**
160
- * 移除监听事件
161
- * @param {Element} target
162
- * @param {String} type
163
- * @param {Funtion} handler
164
- */
165
342
 
343
+ expires = "; expires=" + date.toUTCString();
344
+ }
345
+
346
+ function getValid(data) {
347
+ if (data) {
348
+ return data;
349
+ } else {
350
+ return false;
351
+ }
352
+ }
353
+
354
+ var valid_name = "";
355
+ var valid_value = "";
356
+ var valid_domain = "";
357
+
358
+ if (name) {
359
+ valid_name = getValid(name);
360
+ }
361
+
362
+ if (value) {
363
+ valid_value = getValid(value);
364
+ }
365
+
366
+ if (cdomain) {
367
+ valid_domain = getValid(cdomain);
368
+ }
369
+
370
+ if (valid_name && valid_value) {
371
+ document.cookie = valid_name + "=" + encodeURIComponent(valid_value) + expires + "; path=/" + valid_domain + samesite + secure;
372
+ }
373
+ };
374
+ /**
375
+ * 获取localStorage值
376
+ * @param key 存储键名
377
+ * @returns 存储值
378
+ */
379
+
380
+
381
+ this.getLocalStorage = function (key) {
382
+ try {
383
+ return localStorage.getItem(key);
384
+ } catch (e) {
385
+ return null;
386
+ }
387
+ };
388
+ /**
389
+ * 设置localStorage值
390
+ * @param key 存储键名
391
+ * @param value 存储值
392
+ */
393
+
394
+
395
+ this.setLocalStorage = function (key, value) {
396
+ try {
397
+ localStorage.setItem(key, value);
398
+ } catch (e) {// 静默失败
399
+ }
400
+ };
401
+
402
+ this.removeCookie = function (name) {
403
+ _this.setCookie(name, "", -1);
404
+ };
405
+ /**
406
+ * 获取当前时间戳
407
+ * @return {number} [当前时间戳]
408
+ */
409
+
410
+
411
+ this.getTimeStamp = function () {
412
+ return new Date().getTime();
413
+ };
414
+ /**
415
+ * 获取 UUID
416
+ * @return {string} [UUID唯一值]
417
+ */
418
+
419
+
420
+ this.uuid = function () {
421
+ return "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".replace(/[xy]/g, function (c) {
422
+ var r = Math.random() * 16 | 0,
423
+ v = c == "x" ? r : r & 0x3 | 0x8;
424
+ return v.toString(16);
425
+ });
426
+ };
427
+ /**
428
+ * 获取设备Id
429
+ * @return {number} [设备Id]
430
+ */
431
+
432
+
433
+ this.getDistinctId = function () {
434
+ var distinctId = _this.getCookie("distinctId");
435
+
436
+ if (distinctId) return distinctId;
437
+ distinctId = _this.uuid();
438
+
439
+ _this.setCookie("distinctId", distinctId);
440
+
441
+ return distinctId;
442
+ };
443
+ /**
444
+ * 敏感字段过滤
445
+ * @param obj 需要过滤的对象
446
+ * @param sensitiveKeys 敏感字段列表
447
+ * @returns 过滤后的对象
448
+ */
449
+
450
+
451
+ this.filterSensitiveData = function (obj, sensitiveKeys) {
452
+ if (sensitiveKeys === void 0) {
453
+ sensitiveKeys = ["password", "token", "secret", "key"];
454
+ }
455
+
456
+ if (!_this.isObject(obj)) return obj;
457
+ var filteredObj = {};
458
+
459
+ _this.each(obj, function (value, key) {
460
+ // 检查是否是敏感字段
461
+ var isSensitive = sensitiveKeys.some(function (sensitiveKey) {
462
+ return typeof key === "string" && key.toLowerCase().includes(sensitiveKey.toLowerCase());
463
+ });
166
464
 
167
- this.removeEventListener = function (target, type, handler) {
168
- if (target.removeEventListener) {
169
- target.removeEventListener(type, handler);
465
+ if (isSensitive) {
466
+ filteredObj[key] = "***";
467
+ } else if (_this.isObject(value)) {
468
+ // 递归过滤嵌套对象
469
+ filteredObj[key] = _this.filterSensitiveData(value, sensitiveKeys);
170
470
  } else {
171
- target.detachEvent && target.detachEvent("on" + type, function (event) {
172
- return handler.call(target, event);
173
- }, true);
471
+ filteredObj[key] = value;
174
472
  }
175
- };
176
- /**
177
- * 重写history[pushState][replaceState]方法
178
- */
473
+ });
179
474
 
475
+ return filteredObj;
476
+ };
477
+ /**
478
+ * XSS过滤
479
+ * @param str 需要过滤的字符串
480
+ * @returns 过滤后的字符串
481
+ */
180
482
 
181
- this.rewriteHistory = function () {
182
- var history = window.history;
183
483
 
184
- var historyWrap = function historyWrap(type) {
185
- var history = window.history;
186
- var orig = history[type];
187
- var e = new Event(type);
188
- return function () {
189
- var rv = orig.apply(history, arguments);
190
- e.arguments = arguments;
191
- window.dispatchEvent(e);
192
- return rv;
193
- };
194
- }; // 重写
195
-
196
-
197
- if (!!window.history.pushState) {
198
- history.pushState = historyWrap("pushState");
199
- history.replaceState = historyWrap("replaceState");
484
+ this.xssFilter = function (str) {
485
+ if (!str) return "";
486
+ if (typeof str !== "string") return str.toString();
487
+ return str.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;").replace(/\//g, "&#x2F;");
488
+ };
489
+ /**
490
+ * 获取url中的参数
491
+ * @param {[string]} name [参数名]
492
+ */
493
+
494
+
495
+ this.getQueryValue = function () {
496
+ var _href = decodeURI(window.location.href);
497
+
498
+ var queryArr = _href.match(new RegExp("[?&][^?&]+=[^?&]+", "g")) || [];
499
+ var paramsObj = {};
500
+
501
+ for (var i = 0; i < queryArr.length; i++) {
502
+ var _item = queryArr[i].replace(/\?|\&/, "");
503
+
504
+ var pair = _item.split("=");
505
+
506
+ paramsObj[pair[0]] = pair[1];
507
+ }
508
+
509
+ return Object.keys(paramsObj).length > 0 ? paramsObj : null;
510
+ };
511
+ /**
512
+ * Ajax请求方法
513
+ * @param para Ajax请求参数
514
+ * @returns 是否成功发起请求
515
+ */
516
+
517
+
518
+ this.ajax = function (para) {
519
+ para.timeout = para.timeout || 30000;
520
+ para.credentials = typeof para.credentials === "undefined" ? true : para.credentials;
521
+
522
+ function getJSON(data) {
523
+ if (!data) {
524
+ return {};
525
+ }
526
+
527
+ if (typeof data === "string") {
528
+ try {
529
+ return JSON.parse(data);
530
+ } catch (e) {
531
+ return {};
532
+ }
533
+ }
534
+
535
+ if (_typeof(data) === "object") {
536
+ return data;
537
+ }
538
+
539
+ return {};
540
+ }
541
+
542
+ var g = _this.xhr(para.cors);
543
+
544
+ if (!g) {
545
+ return false;
546
+ }
547
+
548
+ if (!para.type) {
549
+ para.type = para.data ? "POST" : "GET";
550
+ }
551
+
552
+ var oldsuccess = para.success;
553
+ var olderror = para.error;
554
+ var errorTimer;
555
+
556
+ var abort = function abort() {
557
+ try {
558
+ if (_this.isObject(g) && g.abort) {
559
+ g.abort();
560
+ }
561
+ } catch (error) {
562
+ _this.printLog(error);
563
+ }
564
+
565
+ if (errorTimer) {
566
+ clearTimeout(errorTimer);
567
+ errorTimer = null;
568
+ para.error && para.error();
569
+ g.onreadystatechange = null;
570
+ g.onload = null;
571
+ g.onerror = null;
200
572
  }
201
573
  };
202
574
 
203
- this.isArray = Array.isArray || function (obj) {
204
- return toString.call(obj) === "[object Array]";
575
+ para.success = function (data) {
576
+ oldsuccess && oldsuccess(data);
577
+
578
+ if (errorTimer) {
579
+ clearTimeout(errorTimer);
580
+ errorTimer = null;
581
+ }
582
+ };
583
+
584
+ para.error = function (err, status) {
585
+ olderror && olderror(err, status);
586
+
587
+ if (errorTimer) {
588
+ clearTimeout(errorTimer);
589
+ errorTimer = null;
590
+ }
205
591
  };
206
- /**
207
- * 将json序列化成json字符串
208
- * @param obj
209
- * @returns {[JsonString]} [json字符串]
210
- */
211
592
 
593
+ errorTimer = window.setTimeout(function () {
594
+ abort();
595
+ }, para.timeout);
212
596
 
213
- this.formatJsonString = function (obj) {
597
+ g.onreadystatechange = function () {
214
598
  try {
215
- return JSON.stringify(obj, null, " ");
599
+ if (g.readyState == 4) {
600
+ if (g.status >= 200 && g.status < 300 || g.status == 304) {
601
+ para.success && para.success(getJSON(g.responseText));
602
+ } else {
603
+ para.error && para.error(getJSON(g.responseText), g.status);
604
+ }
605
+
606
+ g.onreadystatechange = null;
607
+ g.onload = null;
608
+ }
216
609
  } catch (e) {
217
- return JSON.stringify(obj);
610
+ g.onreadystatechange = null;
611
+ g.onload = null;
218
612
  }
219
613
  };
220
614
 
221
- this.nativeForEach = Array.prototype.forEach;
222
- this.slice = Array.prototype.slice;
223
- this.hasOwnProperty = Object.prototype.hasOwnProperty;
224
- this.breaker = {};
225
- /**
226
- * @description 循环遍历
227
- */
615
+ g.open(para.type || "GET", para.url, true);
228
616
 
229
- this.each = function (obj, iterator, context) {
230
- if (obj == null) {
231
- return false;
617
+ try {
618
+ if (para.credentials) {
619
+ g.withCredentials = true;
232
620
  }
233
621
 
234
- if (_this.nativeForEach && obj.forEach === _this.nativeForEach) {
235
- obj.forEach(iterator, context);
236
- } else if (_this.isArray(obj) && obj.length === +obj.length) {
237
- for (var i = 0, l = obj.length; i < l; i++) {
238
- if (i in obj && iterator.call(context, obj[i], i, obj) === _this.breaker) {
239
- return false;
240
- }
622
+ if (_this.isObject(para.header)) {
623
+ _this.each(para.header, function (v, i) {
624
+ g.setRequestHeader && g.setRequestHeader(i, v);
625
+ });
626
+ }
627
+
628
+ if (para.data) {
629
+ if (!para.cors) {
630
+ g.setRequestHeader && g.setRequestHeader("X-Requested-With", "XMLHttpRequest");
241
631
  }
632
+
633
+ if (para.contentType === "application/json") {
634
+ g.setRequestHeader && g.setRequestHeader("Content-type", "application/json; charset=UTF-8");
635
+ } else {
636
+ g.setRequestHeader && g.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
637
+ }
638
+ }
639
+ } catch (e) {
640
+ _this.printLog(e);
641
+ }
642
+
643
+ g.send(para.data || null);
644
+ };
645
+
646
+ this.xhr = function (cors) {
647
+ if (cors) {
648
+ if (typeof window.XMLHttpRequest !== "undefined" && "withCredentials" in new XMLHttpRequest()) {
649
+ return new XMLHttpRequest();
242
650
  } else {
243
- for (var key in obj) {
244
- if (_this.hasOwnProperty.call(obj, key)) {
245
- if (iterator.call(context, obj[key], key, obj) === _this.breaker) {
246
- return false;
247
- }
248
- }
651
+ return null;
652
+ }
653
+ } else if (typeof window.XMLHttpRequest !== "undefined") {
654
+ return new XMLHttpRequest();
655
+ } else {
656
+ return null;
657
+ }
658
+ };
659
+
660
+ this.getUA = function () {
661
+ var Sys = {};
662
+ var ua = navigator.userAgent.toLowerCase();
663
+ var s;
664
+
665
+ if (s = ua.match(/opera.([\d.]+)/)) {
666
+ Sys.opera = Number(s[1].split(".")[0]);
667
+ } else if (s = ua.match(/msie ([\d.]+)/)) {
668
+ Sys.ie = Number(s[1].split(".")[0]);
669
+ } else if (s = ua.match(/edge.([\d.]+)/)) {
670
+ Sys.edge = Number(s[1].split(".")[0]);
671
+ } else if (s = ua.match(/firefox\/([\d.]+)/)) {
672
+ Sys.firefox = Number(s[1].split(".")[0]);
673
+ } else if (s = ua.match(/chrome\/([\d.]+)/)) {
674
+ Sys.chrome = Number(s[1].split(".")[0]);
675
+ } else if (s = ua.match(/version\/([\d.]+).*safari/)) {
676
+ Sys.safari = Number(s[1].match(/^\d*.\d*/));
677
+ } else if (s = ua.match(/trident\/([\d.]+)/)) {
678
+ Sys.ie = 11;
679
+ }
680
+
681
+ return Sys;
682
+ };
683
+
684
+ this.isSupportBeaconSend = function () {
685
+ var supported = false;
686
+
687
+ if ((typeof navigator === "undefined" ? "undefined" : _typeof(navigator)) !== "object" || typeof navigator.sendBeacon !== "function") {
688
+ return supported;
689
+ }
690
+
691
+ var Sys = _this.getUA();
692
+
693
+ var ua = navigator.userAgent.toLowerCase();
694
+
695
+ if (/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)) {
696
+ var reg = /os [\d._]*/gi;
697
+ var verinfo = ua.match(reg);
698
+ var version = (verinfo + "").replace(/[^0-9|_.]/gi, "").replace(/_/gi, ".");
699
+ var ver = version.split(".");
700
+
701
+ if (typeof Sys.safari === "undefined") {
702
+ Sys.safari = Number(ver[0]);
703
+ }
704
+
705
+ if (ver[0] && +ver[0] < 13) {
706
+ if (Sys.chrome > 41 || Sys.firefox > 30 || Sys.opera > 25 || Sys.safari > 12) {
707
+ supported = true;
249
708
  }
709
+ } else if (Sys.chrome > 41 || Sys.firefox > 30 || Sys.opera > 25 || Sys.safari > 11.3) {
710
+ supported = true;
250
711
  }
712
+ } else {
713
+ if (Sys.chrome > 38 || Sys.edge > 13 || Sys.firefox > 30 || Sys.opera > 25 || Sys.safari > 11.0) {
714
+ supported = true;
715
+ }
716
+ }
251
717
 
252
- return true;
253
- };
718
+ return supported;
719
+ };
720
+ /**
721
+ * 节流函数
722
+ * @param func 需要节流的函数
723
+ * @param wait 等待时间
724
+ * @returns 节流后的函数
725
+ */
254
726
 
255
- this.getDomIndex = function (el) {
256
- if (!el.parentNode) return -1;
257
- var i = 0;
258
- var nodeName = el.tagName;
259
- var list = el.parentNode.children;
260
727
 
261
- for (var n = 0; n < list.length; n++) {
262
- if (list[n].tagName === nodeName) {
263
- if (el === list[n]) {
264
- return i;
265
- } else {
266
- i++;
267
- }
728
+ this.throttle = function (func, wait) {
729
+ var timeout = null;
730
+ var previous = 0;
731
+ return function () {
732
+ var args = [];
733
+
734
+ for (var _i = 0; _i < arguments.length; _i++) {
735
+ args[_i] = arguments[_i];
736
+ }
737
+
738
+ var now = Date.now();
739
+ var remaining = wait - (now - previous);
740
+
741
+ if (remaining <= 0 || remaining > wait) {
742
+ if (timeout) {
743
+ clearTimeout(timeout);
744
+ timeout = null;
268
745
  }
746
+
747
+ previous = now;
748
+ func.apply(void 0, args);
749
+ } else if (!timeout) {
750
+ timeout = window.setTimeout(function () {
751
+ previous = Date.now();
752
+ timeout = null;
753
+ func.apply(void 0, args);
754
+ }, remaining);
755
+ }
756
+ };
757
+ };
758
+ /**
759
+ * 防抖函数
760
+ * @param func 需要防抖的函数
761
+ * @param wait 等待时间
762
+ * @returns 防抖后的函数
763
+ */
764
+
765
+
766
+ this.debounce = function (func, wait) {
767
+ var timeout = null;
768
+ return function () {
769
+ var args = [];
770
+
771
+ for (var _i = 0; _i < arguments.length; _i++) {
772
+ args[_i] = arguments[_i];
269
773
  }
270
774
 
271
- return -1;
775
+ if (timeout) {
776
+ clearTimeout(timeout);
777
+ }
778
+
779
+ timeout = window.setTimeout(function () {
780
+ func.apply(void 0, args);
781
+ }, wait);
272
782
  };
783
+ };
784
+ /**
785
+ * beacon请求
786
+ * @param para SendBeacon参数
787
+ * @returns Promise<TrackingResponse>
788
+ */
789
+
273
790
 
274
- this.selector = function (el) {
275
- var i = el.parentNode && 9 == el.parentNode.nodeType ? -1 : _this.getDomIndex(el);
791
+ this.sendBeacon = function (para) {
792
+ if (_this.isSupportBeaconSend() === true) {
793
+ var headers = {
794
+ type: para.contentType
795
+ };
796
+ var blob = new Blob([JSON.stringify(para.data)], headers);
797
+ var supportType = navigator.sendBeacon(para.url, blob);
276
798
 
277
- if (el.getAttribute && el.getAttribute("id") && /^[A-Za-z][-A-Za-z0-9_:.]*$/.test(el.getAttribute("id"))) {
278
- return "#" + el.getAttribute("id");
799
+ if (supportType) {
800
+ return Promise.resolve({
801
+ success: true,
802
+ message: "发送成功"
803
+ });
279
804
  } else {
280
- return el.tagName.toLowerCase() + (~i ? ":nth-of-type(" + (i + 1) + ")" : "");
805
+ return Promise.reject({
806
+ success: false,
807
+ message: "sendBeacon返回false"
808
+ });
281
809
  }
282
- };
810
+ } else {
811
+ return Promise.reject({
812
+ success: false,
813
+ message: "不支持sendBeacon,发送失败!"
814
+ });
815
+ }
816
+ };
817
+ /**
818
+ * 获取设备唯一标识
819
+ * 基于浏览器指纹技术,通过收集浏览器特征生成唯一ID
820
+ * @returns 返回设备唯一标识字符串
821
+ */
822
+
823
+
824
+ this.getDeviceId = function () {
825
+ // 获取已存储的设备ID
826
+ var storedDeviceId = _this.getCookie("device_id") || _this.getLocalStorage("device_id");
827
+
828
+ if (storedDeviceId) {
829
+ return storedDeviceId;
830
+ } // 收集浏览器指纹信息
831
+
832
+
833
+ var fingerprint = _this.collectFingerprint(); // 生成设备ID
834
+
835
+
836
+ var deviceId = _this.hashFingerprint(fingerprint); // 存储设备ID
837
+
838
+
839
+ _this.setCookie("device_id", deviceId, 365 * 2); // 存储2年
840
+
841
+
842
+ _this.setLocalStorage("device_id", deviceId);
843
+
844
+ return deviceId;
845
+ };
846
+ /**
847
+ * 收集浏览器指纹信息
848
+ * @returns 返回浏览器指纹对象
849
+ */
850
+
851
+
852
+ this.collectFingerprint = function () {
853
+ var fingerprint = {}; // 1. 用户代理
854
+
855
+ fingerprint.userAgent = navigator.userAgent; // 2. 屏幕信息
856
+
857
+ fingerprint.screenWidth = screen.width;
858
+ fingerprint.screenHeight = screen.height;
859
+ fingerprint.colorDepth = screen.colorDepth;
860
+ fingerprint.pixelDepth = screen.pixelDepth; // 3. 时区
861
+
862
+ fingerprint.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
863
+ fingerprint.timezoneOffset = new Date().getTimezoneOffset(); // 4. 语言设置
864
+
865
+ fingerprint.language = navigator.language;
866
+ fingerprint.languages = Array.from(navigator.languages); // 5. 平台信息
867
+
868
+ fingerprint.platform = navigator.platform; // 6. WebGL 指纹
869
+
870
+ fingerprint.webgl = _this.getWebGLFingerprint(); // 7. Canvas 指纹
871
+
872
+ fingerprint.canvas = _this.getCanvasFingerprint(); // 8. 音频上下文指纹
873
+
874
+ fingerprint.audio = _this.getAudioFingerprint(); // 9. 字体检测
875
+
876
+ fingerprint.fonts = _this.getFontFingerprint(); // 10. 插件信息
877
+
878
+ fingerprint.plugins = _this.getPluginsFingerprint(); // 11. 存储检测
879
+
880
+ fingerprint.localStorage = _this.hasLocalStorage();
881
+ fingerprint.sessionStorage = _this.hasSessionStorage();
882
+ fingerprint.indexedDB = _this.hasIndexedDB(); // 12. 硬件信息
883
+
884
+ fingerprint.hardwareConcurrency = navigator.hardwareConcurrency;
885
+ fingerprint.deviceMemory = navigator.deviceMemory;
886
+ fingerprint.maxTouchPoints = navigator.maxTouchPoints; // 13. 连接信息
887
+
888
+ fingerprint.connection = _this.getConnectionFingerprint();
889
+ return fingerprint;
890
+ };
891
+ /**
892
+ * 获取WebGL指纹
893
+ * @returns WebGL指纹字符串
894
+ */
895
+
896
+
897
+ this.getWebGLFingerprint = function () {
898
+ try {
899
+ var canvas = document.createElement("canvas");
900
+ var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
901
+ if (!gl) return "not-supported";
902
+ var debugInfo = gl.getExtension("WEBGL_debug_renderer_info");
903
+ var vendor = debugInfo ? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) : "unknown";
904
+ var renderer = debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : "unknown";
905
+ return vendor + "|" + renderer;
906
+ } catch (e) {
907
+ return "error";
908
+ }
909
+ };
910
+ /**
911
+ * 获取Canvas指纹
912
+ * 注意:使用固定的尺寸和绘制参数,确保在不同时间生成一致的指纹
913
+ * @returns Canvas指纹字符串
914
+ */
915
+
916
+
917
+ this.getCanvasFingerprint = function () {
918
+ try {
919
+ // 使用固定的 canvas 尺寸,确保一致性
920
+ var canvas = document.createElement("canvas");
921
+ canvas.width = 200;
922
+ canvas.height = 50;
923
+ var ctx = canvas.getContext("2d");
924
+ if (!ctx) return "not-supported"; // 使用固定的绘制参数,确保每次生成一致
925
+
926
+ ctx.textBaseline = "top";
927
+ ctx.font = "14px Arial";
928
+ ctx.fillStyle = "#f60";
929
+ ctx.fillRect(125, 1, 62, 20);
930
+ ctx.fillStyle = "#069";
931
+ ctx.fillText("Canvas fingerprint", 2, 15);
932
+ ctx.fillStyle = "rgba(102, 204, 0, 0.7)";
933
+ ctx.fillText("Canvas fingerprint", 4, 17); // 取后50个字符作为指纹
934
+
935
+ return canvas.toDataURL().slice(-50);
936
+ } catch (e) {
937
+ return "error";
938
+ }
939
+ };
940
+ /**
941
+ * 获取音频上下文指纹
942
+ * 注意:只使用稳定的 sampleRate,不使用 currentTime(会随时间变化)
943
+ * @returns 音频指纹字符串
944
+ */
945
+
946
+
947
+ this.getAudioFingerprint = function () {
948
+ try {
949
+ var AudioContextClass = window.AudioContext || window.webkitAudioContext;
950
+ if (!AudioContextClass) return "not-supported";
951
+ var context = new AudioContextClass(); // 只使用稳定的 sampleRate,不使用 currentTime(会随时间变化导致指纹不一致)
952
+
953
+ var fingerprint = String(context.sampleRate || 0);
954
+ context.close();
955
+ return fingerprint;
956
+ } catch (e) {
957
+ return "error";
958
+ }
959
+ };
960
+ /**
961
+ * 获取字体指纹
962
+ * @returns 字体指纹字符串
963
+ */
964
+
965
+
966
+ this.getFontFingerprint = function () {
967
+ try {
968
+ var baseFonts_1 = ["monospace", "sans-serif", "serif"];
969
+ var testString_1 = "mmmmmmmmmmlli";
970
+ var testSize_1 = "72px";
971
+ var canvas = document.createElement("canvas");
972
+ var ctx_1 = canvas.getContext("2d");
973
+ if (!ctx_1) return "not-supported";
974
+ var detectedFonts_1 = []; // 测试字体列表
975
+
976
+ var fonts = ["Arial", "Arial Black", "Comic Sans MS", "Courier New", "Georgia", "Helvetica", "Impact", "Times New Roman", "Trebuchet MS", "Verdana"]; // 获取基准宽度
977
+
978
+ var baseWidths_1 = {};
979
+ baseFonts_1.forEach(function (font) {
980
+ ctx_1.font = testSize_1 + " " + font;
981
+ baseWidths_1[font] = ctx_1.measureText(testString_1).width;
982
+ }); // 测试每个字体
983
+
984
+ fonts.forEach(function (font) {
985
+ var detected = false;
986
+ baseFonts_1.forEach(function (baseFont) {
987
+ ctx_1.font = testSize_1 + " '" + font + "', " + baseFont;
988
+ var width = ctx_1.measureText(testString_1).width;
989
+
990
+ if (width !== baseWidths_1[baseFont]) {
991
+ detected = true;
992
+ }
993
+ });
994
+
995
+ if (detected) {
996
+ detectedFonts_1.push(font);
997
+ }
998
+ });
999
+ return detectedFonts_1.join(",");
1000
+ } catch (e) {
1001
+ return "error";
1002
+ }
1003
+ };
1004
+ /**
1005
+ * 获取插件指纹
1006
+ * @returns 插件指纹字符串
1007
+ */
1008
+
1009
+
1010
+ this.getPluginsFingerprint = function () {
1011
+ try {
1012
+ var plugins = [];
1013
+
1014
+ if (navigator.plugins) {
1015
+ for (var i = 0; i < navigator.plugins.length; i++) {
1016
+ var plugin = navigator.plugins[i];
1017
+
1018
+ if (plugin) {
1019
+ plugins.push(plugin.name + "|" + plugin.description + "|" + plugin.filename);
1020
+ }
1021
+ }
1022
+ }
1023
+
1024
+ return plugins.join(";");
1025
+ } catch (e) {
1026
+ return "error";
1027
+ }
1028
+ };
1029
+ /**
1030
+ * 检测localStorage支持
1031
+ * @returns 是否支持localStorage
1032
+ */
1033
+
1034
+
1035
+ this.hasLocalStorage = function () {
1036
+ try {
1037
+ var test = "__test__";
1038
+ localStorage.setItem(test, test);
1039
+ localStorage.removeItem(test);
1040
+ return true;
1041
+ } catch (e) {
1042
+ return false;
1043
+ }
1044
+ };
1045
+ /**
1046
+ * 检测sessionStorage支持
1047
+ * @returns 是否支持sessionStorage
1048
+ */
1049
+
1050
+
1051
+ this.hasSessionStorage = function () {
1052
+ try {
1053
+ var test = "__test__";
1054
+ sessionStorage.setItem(test, test);
1055
+ sessionStorage.removeItem(test);
1056
+ return true;
1057
+ } catch (e) {
1058
+ return false;
1059
+ }
1060
+ };
1061
+ /**
1062
+ * 检测IndexedDB支持
1063
+ * @returns 是否支持IndexedDB
1064
+ */
1065
+
1066
+
1067
+ this.hasIndexedDB = function () {
1068
+ return "indexedDB" in window && indexedDB !== null;
1069
+ };
1070
+ /**
1071
+ * 获取网络连接指纹
1072
+ * @returns 网络连接指纹字符串
1073
+ */
283
1074
 
284
- this.getDomSelector = function (el, arr) {
285
- if (!el || !el.parentNode || !el.parentNode.children) {
286
- return false;
287
- }
288
1075
 
289
- arr = arr && arr.join ? arr : [];
290
- var name = el.nodeName.toLowerCase();
1076
+ this.getConnectionFingerprint = function () {
1077
+ try {
1078
+ var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
1079
+ if (!connection) return "not-supported"; // 只使用稳定的 effectiveType,不使用 downlink 和 rtt(会随网络状态变化导致指纹不一致)
291
1080
 
292
- if (!el || name === "body" || 1 != el.nodeType) {
293
- arr.unshift("body");
294
- return arr.join(" > ");
295
- }
1081
+ return connection.effectiveType || "unknown";
1082
+ } catch (e) {
1083
+ return "error";
1084
+ }
1085
+ };
1086
+ /**
1087
+ * 将指纹信息哈希为唯一ID
1088
+ * @param fingerprint 指纹信息
1089
+ * @returns 返回哈希后的设备ID
1090
+ */
296
1091
 
297
- arr.unshift(_this.selector(el));
298
- if (el.getAttribute && el.getAttribute("id") && /^[A-Za-z][-A-Za-z0-9_:.]*$/.test(el.getAttribute("id"))) return arr.join(" > ");
299
- return _this.getDomSelector(el.parentNode, arr);
300
- };
301
- /**
302
- * @description 增强cookie操作
303
- */
304
1092
 
1093
+ this.hashFingerprint = function (fingerprint) {
1094
+ // 将指纹对象转换为字符串
1095
+ var fingerprintString = JSON.stringify(fingerprint, Object.keys(fingerprint).sort()); // 使用更强大的哈希算法生成ID
305
1096
 
306
- this.getCookie = function (name) {
307
- var nameEQ = name + "=";
308
- var ca = document.cookie.split(";");
1097
+ var hash1 = 5381; // DJB2哈希算法的初始值
309
1098
 
310
- for (var i = 0; i < ca.length; i++) {
311
- var c = ca[i];
1099
+ var hash2 = 52711; // 第二个哈希的初始值
312
1100
 
313
- while (c.charAt(0) == " ") {
314
- c = c.substring(1, c.length);
315
- }
1101
+ for (var i = 0; i < fingerprintString.length; i++) {
1102
+ var char = fingerprintString.charCodeAt(i);
1103
+ hash1 = (hash1 << 5) + hash1 + char; // hash1 * 33 + char
316
1104
 
317
- if (c.indexOf(nameEQ) == 0) {
318
- return this._decodeURIComponent(c.substring(nameEQ.length, c.length));
319
- }
320
- }
1105
+ hash2 = (hash2 << 5) + hash2 + char; // hash2 * 33 + char
1106
+ } // 将两个32位哈希值转换为十六进制字符串并拼接,保持完整长度
321
1107
 
322
- return null;
323
- };
324
1108
 
325
- this.setCookie = function (name, value, days) {
326
- var cdomain = "",
327
- expires = "",
328
- secure = "",
329
- samesite = "";
330
- days = days == null ? 73000 : days;
331
- var domain = this.getMainHost();
332
- cdomain = domain ? "; domain=" + domain : "";
1109
+ var hash1Hex = (hash1 >>> 0).toString(16).padStart(8, '0'); // 32位 = 8个十六进制字符
333
1110
 
334
- if (days !== 0) {
335
- var date = new Date();
1111
+ var hash2Hex = (hash2 >>> 0).toString(16).padStart(8, '0'); // 32位 = 8个十六进制字符
336
1112
 
337
- if (String(days).slice(-1) === "s") {
338
- date.setTime(date.getTime() + Number(String(days).slice(0, -1)) * 1000);
339
- } else {
340
- date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
341
- }
1113
+ var deviceId = "fp_" + hash1Hex + hash2Hex;
1114
+ return deviceId;
1115
+ };
1116
+ }
1117
+ /**
1118
+ * 打印log
1119
+ */
342
1120
 
343
- expires = "; expires=" + date.toUTCString();
344
- }
345
1121
 
346
- function getValid(data) {
347
- if (data) {
348
- return data;
349
- } else {
350
- return false;
351
- }
352
- }
1122
+ WebTrackingTools.prototype.printLog = function () {
1123
+ var rest = [];
353
1124
 
354
- var valid_name = "";
355
- var valid_value = "";
356
- var valid_domain = "";
1125
+ for (var _i = 0; _i < arguments.length; _i++) {
1126
+ rest[_i] = arguments[_i];
1127
+ }
357
1128
 
358
- if (name) {
359
- valid_name = getValid(name);
360
- }
1129
+ if (this.isObject(rest[0])) {
1130
+ rest[0] = this.formatJsonString(rest[0]);
1131
+ }
361
1132
 
362
- if (value) {
363
- valid_value = getValid(value);
364
- }
1133
+ if ((typeof console === "undefined" ? "undefined" : _typeof(console)) === "object" && console.log) {
1134
+ try {
1135
+ return console.log.apply(console, rest);
1136
+ } catch (e) {
1137
+ console.log(rest[0]);
1138
+ }
1139
+ }
1140
+ };
1141
+ /**
1142
+ * @description 检验是否是对象
1143
+ */
365
1144
 
366
- if (cdomain) {
367
- valid_domain = getValid(cdomain);
368
- }
369
1145
 
370
- if (valid_name && valid_value) {
371
- document.cookie = valid_name + "=" + encodeURIComponent(valid_value) + expires + "; path=/" + valid_domain + samesite + secure;
372
- }
373
- };
374
- /**
375
- * 获取localStorage值
376
- * @param key 存储键名
377
- * @returns 存储值
378
- */
1146
+ WebTrackingTools.prototype.isObject = function (obj) {
1147
+ if (obj == null) {
1148
+ return false;
1149
+ } else {
1150
+ return toString.call(obj) == "[object Object]";
1151
+ }
1152
+ };
379
1153
 
1154
+ WebTrackingTools.prototype.isUndefined = function (obj) {
1155
+ return obj === void 0;
1156
+ };
380
1157
 
381
- this.getLocalStorage = function (key) {
382
- try {
383
- return localStorage.getItem(key);
384
- } catch (e) {
385
- return null;
386
- }
387
- };
388
- /**
389
- * 设置localStorage值
390
- * @param key 存储键名
391
- * @param value 存储值
392
- */
1158
+ WebTrackingTools.prototype.isString = function (obj) {
1159
+ return toString.call(obj) == "[object String]";
1160
+ };
393
1161
 
1162
+ WebTrackingTools.prototype.isDate = function (obj) {
1163
+ return toString.call(obj) == "[object Date]";
1164
+ };
394
1165
 
395
- this.setLocalStorage = function (key, value) {
396
- try {
397
- localStorage.setItem(key, value);
398
- } catch (e) {// 静默失败
399
- }
400
- };
1166
+ WebTrackingTools.prototype.isBoolean = function (obj) {
1167
+ return toString.call(obj) == "[object Boolean]";
1168
+ };
401
1169
 
402
- this.removeCookie = function (name) {
403
- _this.setCookie(name, "", -1);
404
- };
405
- /**
406
- * 获取当前时间戳
407
- * @return {number} [当前时间戳]
408
- */
1170
+ WebTrackingTools.prototype.isNumber = function (obj) {
1171
+ return toString.call(obj) == "[object Number]" && /[\d\.]+/.test(String(obj));
1172
+ };
409
1173
 
1174
+ WebTrackingTools.prototype.isElement = function (obj) {
1175
+ return !!(obj && obj.nodeType === 1);
1176
+ };
410
1177
 
411
- this.getTimeStamp = function () {
412
- return new Date().getTime();
413
- };
414
- /**
415
- * 获取 UUID
416
- * @return {string} [UUID唯一值]
417
- */
1178
+ WebTrackingTools.prototype.isFunction = function (f) {
1179
+ if (!f) {
1180
+ return false;
1181
+ }
418
1182
 
1183
+ var type = toString.call(f);
1184
+ return type == "[object Function]" || type == "[object AsyncFunction]";
1185
+ };
419
1186
 
420
- this.uuid = function () {
421
- return "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".replace(/[xy]/g, function (c) {
422
- var r = Math.random() * 16 | 0,
423
- v = c == "x" ? r : r & 0x3 | 0x8;
424
- return v.toString(16);
425
- });
426
- };
427
- /**
428
- * 获取设备Id
429
- * @return {number} [设备Id]
430
- */
1187
+ WebTrackingTools.prototype.isJSONString = function (str) {
1188
+ if (!this.isString(str)) return false;
431
1189
 
1190
+ try {
1191
+ JSON.parse(str);
1192
+ } catch (e) {
1193
+ return false;
1194
+ }
432
1195
 
433
- this.getDistinctId = function () {
434
- var distinctId = _this.getCookie("distinctId");
1196
+ return true;
1197
+ };
435
1198
 
436
- if (distinctId) return distinctId;
437
- distinctId = _this.uuid();
1199
+ WebTrackingTools.prototype._decodeURIComponent = function (val) {
1200
+ var result = val;
438
1201
 
439
- _this.setCookie("distinctId", distinctId);
1202
+ try {
1203
+ result = decodeURIComponent(val);
1204
+ } catch (e) {
1205
+ result = val;
1206
+ }
440
1207
 
441
- return distinctId;
442
- };
443
- /**
444
- * 敏感字段过滤
445
- * @param obj 需要过滤的对象
446
- * @param sensitiveKeys 敏感字段列表
447
- * @returns 过滤后的对象
448
- */
1208
+ return result;
1209
+ };
449
1210
 
1211
+ WebTrackingTools.prototype.getMainHost = function () {
1212
+ var key = "mh_" + Math.random();
1213
+ var keyR = new RegExp("(^|;)\\s*" + key + "=12345");
1214
+ var expiredTime = new Date(0);
1215
+ var domain = document.domain;
1216
+ var domainList = domain.split(".");
1217
+ var urlItems = []; // 主域名一定会有两部分组成
450
1218
 
451
- this.filterSensitiveData = function (obj, sensitiveKeys) {
452
- if (sensitiveKeys === void 0) {
453
- sensitiveKeys = ['password', 'token', 'secret', 'key'];
454
- }
1219
+ urlItems.unshift(domainList.pop()); // 慢慢从后往前测试
455
1220
 
456
- if (!_this.isObject(obj)) return obj;
457
- var filteredObj = {};
1221
+ while (domainList.length) {
1222
+ urlItems.unshift(domainList.pop());
1223
+ var mainHost = urlItems.join(".");
1224
+ var cookie = key + "=" + 12345 + ";domain=." + mainHost;
1225
+ document.cookie = cookie; //如果cookie存在,则说明域名合法
458
1226
 
459
- _this.each(obj, function (value, key) {
460
- // 检查是否是敏感字段
461
- var isSensitive = sensitiveKeys.some(function (sensitiveKey) {
462
- return typeof key === 'string' && key.toLowerCase().includes(sensitiveKey.toLowerCase());
463
- });
1227
+ if (keyR.test(document.cookie)) {
1228
+ document.cookie = cookie + ";expires=" + expiredTime;
1229
+ return mainHost;
1230
+ }
1231
+ }
464
1232
 
465
- if (isSensitive) {
466
- filteredObj[key] = '***';
467
- } else if (_this.isObject(value)) {
468
- // 递归过滤嵌套对象
469
- filteredObj[key] = _this.filterSensitiveData(value, sensitiveKeys);
470
- } else {
471
- filteredObj[key] = value;
472
- }
473
- });
1233
+ return domain;
1234
+ };
474
1235
 
475
- return filteredObj;
476
- };
477
- /**
478
- * XSS过滤
479
- * @param str 需要过滤的字符串
480
- * @returns 过滤后的字符串
481
- */
1236
+ return WebTrackingTools;
1237
+ }();
482
1238
 
1239
+ var WebTracking =
1240
+ /** @class */
1241
+ function (_super) {
1242
+ __extends(WebTracking, _super);
483
1243
 
484
- this.xssFilter = function (str) {
485
- if (!str) return '';
486
- if (typeof str !== 'string') return str.toString();
487
- return str.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#x27;').replace(/\//g, '&#x2F;');
488
- };
489
- /**
490
- * 获取url中的参数
491
- * @param {[string]} name [参数名]
492
- */
1244
+ function WebTracking() {
1245
+ var _this = _super.call(this) || this; // 批量发送定时器
493
1246
 
494
1247
 
495
- this.getQueryValue = function () {
496
- var _href = decodeURI(window.location.href);
1248
+ _this.batchTimer = null; // LocalStorage 存储 key
497
1249
 
498
- var queryArr = _href.match(new RegExp("[?&][^?&]+=[^?&]+", "g")) || [];
499
- var paramsObj = {};
1250
+ _this.BATCH_QUEUE_STORAGE_KEY = "web_tracking_batch_queue"; // 是否使用自定义 pageKey(如果为 true,路由变化时不会自动更新 pageKey)
500
1251
 
501
- for (var i = 0; i < queryArr.length; i++) {
502
- var _item = queryArr[i].replace(/\?|\&/, "");
1252
+ _this.useCustomPageKey = false; // 页面卸载监听器是否已设置
503
1253
 
504
- var pair = _item.split("=");
1254
+ _this.isUnloadListenerSetup = false; // 定时上报页面停留时长的定时器
505
1255
 
506
- paramsObj[pair[0]] = pair[1];
507
- }
1256
+ _this.pageDurationTimer = null; // LocalStorage 存储 key(待发送请求)
508
1257
 
509
- return Object.keys(paramsObj).length > 0 ? paramsObj : null;
510
- };
511
- /**
512
- * Ajax请求方法
513
- * @param para Ajax请求参数
514
- * @returns 是否成功发起请求
515
- */
1258
+ _this.PENDING_REQUESTS_STORAGE_KEY = "web_tracking_pending_requests"; // 待发送请求队列最大大小(默认值,可通过配置覆盖)
516
1259
 
1260
+ _this.DEFAULT_PENDING_REQUESTS_MAX_SIZE = 50; // LocalStorage 最大大小限制(4MB)
517
1261
 
518
- this.ajax = function (para) {
519
- para.timeout = para.timeout || 30000;
520
- para.credentials = typeof para.credentials === "undefined" ? true : para.credentials;
1262
+ _this.MAX_STORAGE_SIZE = 4 * 1024 * 1024; // 用户信息
521
1263
 
522
- function getJSON(data) {
523
- if (!data) {
524
- return {};
525
- }
1264
+ _this.userInfo = null; // 当前路由
526
1265
 
527
- if (typeof data === 'string') {
528
- try {
529
- return JSON.parse(data);
530
- } catch (e) {
531
- return {};
532
- }
533
- }
1266
+ _this.currentUrl = ""; // 页面唯一标识,取当前路由 window.location.pathname.replace(/\//g, '_').substr(1)
534
1267
 
535
- if (_typeof(data) === 'object') {
536
- return data;
537
- }
1268
+ _this.pageKey = ""; // 设备唯一标识
538
1269
 
539
- return {};
540
- }
1270
+ _this.deviceId = ""; // 上传事件描述
541
1271
 
542
- var g = _this.xhr(para.cors);
1272
+ _this.eventDescMap = {
1273
+ PageView: "Web 浏览页面",
1274
+ WebClick: "Web 元素点击",
1275
+ PageRetained: "Web 页面浏览时长",
1276
+ CustomTrack: "Web 自定义代码上报"
1277
+ };
1278
+ /**
1279
+ * @description 初始化函数
1280
+ * @param {object} InitParams [初始化参数]
1281
+ */
543
1282
 
544
- if (!g) {
545
- return false;
546
- }
1283
+ _this.init = function (initParams) {
1284
+ _this.preset(initParams);
547
1285
 
548
- if (!para.type) {
549
- para.type = para.data ? "POST" : "GET";
550
- }
1286
+ var pathname = window.location.pathname;
1287
+ _this.currentUrl = window.location.href; // 如果传入了自定义 pageKey,使用自定义值,否则自动生成
551
1288
 
552
- var oldsuccess = para.success;
553
- var olderror = para.error;
554
- var errorTimer;
1289
+ if (initParams.pageKey) {
1290
+ _this.pageKey = initParams.pageKey;
1291
+ _this.useCustomPageKey = true;
1292
+ } else {
1293
+ _this.pageKey = pathname.replace(/\//g, "_").substring(1);
1294
+ _this.useCustomPageKey = false;
1295
+ }
555
1296
 
556
- var abort = function abort() {
557
- try {
558
- if (_this.isObject(g) && g.abort) {
559
- g.abort();
560
- }
561
- } catch (error) {
562
- _this.printLog(error);
563
- }
1297
+ _this.systemsInfo = _this.getSystemsInfo(initParams.platform); // 获取设备ID
564
1298
 
565
- if (errorTimer) {
566
- clearTimeout(errorTimer);
567
- errorTimer = null;
568
- para.error && para.error();
569
- g.onreadystatechange = null;
570
- g.onload = null;
571
- g.onerror = null;
572
- }
573
- };
1299
+ _this.deviceId = _this.getDeviceId(); // 如果传入了 userInfo,设置用户信息
574
1300
 
575
- para.success = function (data) {
576
- oldsuccess && oldsuccess(data);
1301
+ if (initParams.userInfo && _this.isObject(initParams.userInfo)) {
1302
+ _this.userInfo = initParams.userInfo;
1303
+ }
577
1304
 
578
- if (errorTimer) {
579
- clearTimeout(errorTimer);
580
- errorTimer = null;
581
- }
582
- };
1305
+ _this.setCookie("retainedStartTime", _this.getTimeStamp()); // 如果启用了批量发送,从 LocalStorage 恢复队列
583
1306
 
584
- para.error = function (err, status) {
585
- olderror && olderror(err, status);
586
1307
 
587
- if (errorTimer) {
588
- clearTimeout(errorTimer);
589
- errorTimer = null;
590
- }
591
- };
1308
+ if (_this.initConfig.batchSend) {
1309
+ _this.restoreBatchQueueFromStorage();
1310
+ } // 恢复待发送的单个请求
592
1311
 
593
- errorTimer = window.setTimeout(function () {
594
- abort();
595
- }, para.timeout);
596
1312
 
597
- g.onreadystatechange = function () {
598
- try {
599
- if (g.readyState == 4) {
600
- if (g.status >= 200 && g.status < 300 || g.status == 304) {
601
- para.success && para.success(getJSON(g.responseText));
602
- } else {
603
- para.error && para.error(getJSON(g.responseText), g.status);
604
- }
1313
+ _this.restorePendingRequestsFromStorage(); // 无论是否启用批量发送,都需要监听页面卸载事件,确保数据发送
605
1314
 
606
- g.onreadystatechange = null;
607
- g.onload = null;
608
- }
609
- } catch (e) {
610
- g.onreadystatechange = null;
611
- g.onload = null;
612
- }
613
- };
614
1315
 
615
- g.open(para.type || "GET", para.url, true);
1316
+ _this.setupBeforeUnloadListener(); // 如果启用了定时上报,启动定时器
616
1317
 
617
- try {
618
- if (para.credentials) {
619
- g.withCredentials = true;
620
- }
621
1318
 
622
- if (_this.isObject(para.header)) {
623
- _this.each(para.header, function (v, i) {
624
- g.setRequestHeader && g.setRequestHeader(i, v);
625
- });
626
- }
1319
+ if (_this.initConfig.autoTrackPageDurationInterval) {
1320
+ _this.startPageDurationTimer();
1321
+ }
1322
+ };
1323
+ /**
1324
+ * @description 预置参数
1325
+ * @param {object} PresetParams [预置参数]
1326
+ */
627
1327
 
628
- if (para.data) {
629
- if (!para.cors) {
630
- g.setRequestHeader && g.setRequestHeader("X-Requested-With", "XMLHttpRequest");
631
- }
632
1328
 
633
- if (para.contentType === "application/json") {
634
- g.setRequestHeader && g.setRequestHeader("Content-type", "application/json; charset=UTF-8");
635
- } else {
636
- g.setRequestHeader && g.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
637
- }
1329
+ _this.preset = function (presetParams) {
1330
+ if (presetParams instanceof Object) {
1331
+ // 处理 pageKey 特殊逻辑
1332
+ if (presetParams.pageKey !== undefined) {
1333
+ if (presetParams.pageKey === null || presetParams.pageKey === '') {
1334
+ // 恢复自动生成
1335
+ _this.useCustomPageKey = false;
1336
+ var pathname = window.location.pathname;
1337
+ _this.pageKey = pathname.replace(/\//g, "_").substring(1);
1338
+ } else {
1339
+ _this.pageKey = presetParams.pageKey;
1340
+ _this.useCustomPageKey = true;
638
1341
  }
639
- } catch (e) {
640
- _this.printLog(e);
641
1342
  }
642
1343
 
643
- g.send(para.data || null);
644
- };
1344
+ _this.each(presetParams, function (val, key) {
1345
+ // 跳过 pageKey,因为已经单独处理
1346
+ if (key === 'pageKey') return;
645
1347
 
646
- this.xhr = function (cors) {
647
- if (cors) {
648
- if (typeof window.XMLHttpRequest !== "undefined" && "withCredentials" in new XMLHttpRequest()) {
649
- return new XMLHttpRequest();
650
- } else {
651
- return null;
652
- }
653
- } else if (typeof window.XMLHttpRequest !== "undefined") {
654
- return new XMLHttpRequest();
655
- } else {
656
- return null;
657
- }
658
- };
1348
+ if (_this.initConfig.hasOwnProperty(key)) {
1349
+ // 参数验证
1350
+ var validationResult = _this.validateConfigParam(String(key), val);
659
1351
 
660
- this.getUA = function () {
661
- var Sys = {};
662
- var ua = navigator.userAgent.toLowerCase();
663
- var s;
664
-
665
- if (s = ua.match(/opera.([\d.]+)/)) {
666
- Sys.opera = Number(s[1].split(".")[0]);
667
- } else if (s = ua.match(/msie ([\d.]+)/)) {
668
- Sys.ie = Number(s[1].split(".")[0]);
669
- } else if (s = ua.match(/edge.([\d.]+)/)) {
670
- Sys.edge = Number(s[1].split(".")[0]);
671
- } else if (s = ua.match(/firefox\/([\d.]+)/)) {
672
- Sys.firefox = Number(s[1].split(".")[0]);
673
- } else if (s = ua.match(/chrome\/([\d.]+)/)) {
674
- Sys.chrome = Number(s[1].split(".")[0]);
675
- } else if (s = ua.match(/version\/([\d.]+).*safari/)) {
676
- Sys.safari = Number(s[1].match(/^\d*.\d*/));
677
- } else if (s = ua.match(/trident\/([\d.]+)/)) {
678
- Sys.ie = 11;
679
- }
1352
+ if (validationResult.valid) {
1353
+ _this.initConfig[key] = val;
1354
+ } else {
1355
+ _this.printLog("\u914D\u7F6E\u53C2\u6570\u9A8C\u8BC1\u5931\u8D25: " + String(key) + " = " + val + ", \u539F\u56E0: " + validationResult.message);
1356
+ }
1357
+ }
1358
+ });
1359
+ }
680
1360
 
681
- return Sys;
682
- };
1361
+ if (!/^(((ht|f)tps?):\/\/)?[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-\(\)]*[\w@?^=%&/~+#-\(\)])?$/.test(_this.initConfig["serverUrl"])) {
1362
+ _this.printLog("当前 server_url 为空或不正确,只在控制台打印日志,network 中不会发数据,请配置正确的 server_url!");
683
1363
 
684
- this.isSupportBeaconSend = function () {
685
- var supported = false;
1364
+ _this.initConfig["showLog"] = true;
1365
+ } // 如果启用了全埋点或启用了 data-part-key 点击追踪
686
1366
 
687
- if ((typeof navigator === "undefined" ? "undefined" : _typeof(navigator)) !== "object" || typeof navigator.sendBeacon !== "function") {
688
- return supported;
689
- }
690
1367
 
691
- var Sys = _this.getUA();
1368
+ if (!!_this.initConfig["autoTrack"] || !!_this.initConfig["trackPartKeyClick"]) {
1369
+ // 启用监听
1370
+ _this.listener();
1371
+ } else {
1372
+ // 取消监听
1373
+ _this.unlistener();
1374
+ } // 处理定时上报配置
692
1375
 
693
- var ua = navigator.userAgent.toLowerCase();
694
1376
 
695
- if (/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)) {
696
- var reg = /os [\d._]*/gi;
697
- var verinfo = ua.match(reg);
698
- var version = (verinfo + "").replace(/[^0-9|_.]/gi, "").replace(/_/gi, ".");
699
- var ver = version.split(".");
1377
+ if (_this.initConfig.autoTrackPageDurationInterval) {
1378
+ _this.startPageDurationTimer();
1379
+ } else {
1380
+ _this.stopPageDurationTimer();
1381
+ }
1382
+ };
1383
+ /**
1384
+ * @description 验证配置参数
1385
+ * @param key 参数名
1386
+ * @param value 参数值
1387
+ * @returns 验证结果
1388
+ */
700
1389
 
701
- if (typeof Sys.safari === "undefined") {
702
- Sys.safari = Number(ver[0]);
703
- }
704
1390
 
705
- if (ver[0] && +ver[0] < 13) {
706
- if (Sys.chrome > 41 || Sys.firefox > 30 || Sys.opera > 25 || Sys.safari > 12) {
707
- supported = true;
708
- }
709
- } else if (Sys.chrome > 41 || Sys.firefox > 30 || Sys.opera > 25 || Sys.safari > 11.3) {
710
- supported = true;
711
- }
712
- } else {
713
- if (Sys.chrome > 38 || Sys.edge > 13 || Sys.firefox > 30 || Sys.opera > 25 || Sys.safari > 11.0) {
714
- supported = true;
1391
+ _this.validateConfigParam = function (key, value) {
1392
+ switch (key) {
1393
+ case 'sampleRate':
1394
+ if (typeof value !== 'number' || value < 0 || value > 1) {
1395
+ return {
1396
+ valid: false,
1397
+ message: 'sampleRate 必须是 0-1 之间的数字'
1398
+ };
715
1399
  }
716
- }
717
1400
 
718
- return supported;
719
- };
720
- /**
721
- * 节流函数
722
- * @param func 需要节流的函数
723
- * @param wait 等待时间
724
- * @returns 节流后的函数
725
- */
1401
+ break;
726
1402
 
1403
+ case 'sendTimeout':
1404
+ if (typeof value !== 'number' || value <= 0) {
1405
+ return {
1406
+ valid: false,
1407
+ message: 'sendTimeout 必须是大于 0 的数字'
1408
+ };
1409
+ }
727
1410
 
728
- this.throttle = function (func, wait) {
729
- var timeout = null;
730
- var previous = 0;
731
- return function () {
732
- var args = [];
1411
+ break;
733
1412
 
734
- for (var _i = 0; _i < arguments.length; _i++) {
735
- args[_i] = arguments[_i];
1413
+ case 'batchInterval':
1414
+ if (typeof value !== 'number' || value <= 0) {
1415
+ return {
1416
+ valid: false,
1417
+ message: 'batchInterval 必须是大于 0 的数字'
1418
+ };
736
1419
  }
737
1420
 
738
- var now = Date.now();
739
- var remaining = wait - (now - previous);
740
-
741
- if (remaining <= 0 || remaining > wait) {
742
- if (timeout) {
743
- clearTimeout(timeout);
744
- timeout = null;
745
- }
1421
+ break;
746
1422
 
747
- previous = now;
748
- func.apply(void 0, args);
749
- } else if (!timeout) {
750
- timeout = window.setTimeout(function () {
751
- previous = Date.now();
752
- timeout = null;
753
- func.apply(void 0, args);
754
- }, remaining);
1423
+ case 'batchMaxSize':
1424
+ if (typeof value !== 'number' || value <= 0 || !Number.isInteger(value)) {
1425
+ return {
1426
+ valid: false,
1427
+ message: 'batchMaxSize 必须是大于 0 的整数'
1428
+ };
755
1429
  }
756
- };
757
- };
758
- /**
759
- * 防抖函数
760
- * @param func 需要防抖的函数
761
- * @param wait 等待时间
762
- * @returns 防抖后的函数
763
- */
764
-
765
1430
 
766
- this.debounce = function (func, wait) {
767
- var timeout = null;
768
- return function () {
769
- var args = [];
1431
+ break;
770
1432
 
771
- for (var _i = 0; _i < arguments.length; _i++) {
772
- args[_i] = arguments[_i];
1433
+ case 'pendingRequestsMaxSize':
1434
+ if (typeof value !== 'number' || value <= 0 || !Number.isInteger(value)) {
1435
+ return {
1436
+ valid: false,
1437
+ message: 'pendingRequestsMaxSize 必须是大于 0 的整数'
1438
+ };
773
1439
  }
774
1440
 
775
- if (timeout) {
776
- clearTimeout(timeout);
777
- }
1441
+ break;
778
1442
 
779
- timeout = window.setTimeout(function () {
780
- func.apply(void 0, args);
781
- }, wait);
782
- };
783
- };
784
- /**
785
- * beacon请求
786
- * @param para SendBeacon参数
787
- * @returns Promise<TrackingResponse>
788
- */
789
-
790
-
791
- this.sendBeacon = function (para) {
792
- if (_this.isSupportBeaconSend() === true) {
793
- var headers = {
794
- type: para.contentType
795
- };
796
- var blob = new Blob([JSON.stringify(para.data)], headers);
797
- var supportType = navigator.sendBeacon(para.url, blob);
798
-
799
- if (supportType) {
800
- return Promise.resolve({
801
- success: true,
802
- message: "发送成功"
803
- });
804
- } else {
805
- return Promise.reject({
806
- success: false,
807
- message: "sendBeacon返回false"
808
- });
1443
+ case 'pageDurationInterval':
1444
+ if (typeof value !== 'number' || value <= 0) {
1445
+ return {
1446
+ valid: false,
1447
+ message: 'pageDurationInterval 必须是大于 0 的数字'
1448
+ };
809
1449
  }
810
- } else {
811
- return Promise.reject({
812
- success: false,
813
- message: "不支持sendBeacon,发送失败!"
814
- });
815
- }
816
- };
817
- /**
818
- * 获取设备唯一标识
819
- * 基于浏览器指纹技术,通过收集浏览器特征生成唯一ID
820
- * @returns 返回设备唯一标识字符串
821
- */
822
-
823
1450
 
824
- this.getDeviceId = function () {
825
- // 获取已存储的设备ID
826
- var storedDeviceId = _this.getCookie('device_id') || _this.getLocalStorage('device_id');
1451
+ break;
827
1452
 
828
- if (storedDeviceId) {
829
- return storedDeviceId;
830
- } // 收集浏览器指纹信息
1453
+ case 'sendMethod':
1454
+ if (typeof value !== 'string' || !['auto', 'xhr', 'beacon'].includes(value)) {
1455
+ return {
1456
+ valid: false,
1457
+ message: 'sendMethod 必须是 auto、xhr 或 beacon'
1458
+ };
1459
+ }
831
1460
 
1461
+ break;
1462
+
1463
+ case 'showLog':
1464
+ case 'autoTrack':
1465
+ case 'isTrackSinglePage':
1466
+ case 'batchSend':
1467
+ case 'trackPartKeyClick':
1468
+ case 'autoTrackPageDurationInterval':
1469
+ if (typeof value !== 'boolean') {
1470
+ return {
1471
+ valid: false,
1472
+ message: key + " \u5FC5\u987B\u662F\u5E03\u5C14\u503C"
1473
+ };
1474
+ }
832
1475
 
833
- var fingerprint = _this.collectFingerprint(); // 生成设备ID
1476
+ break;
834
1477
 
1478
+ case 'business':
1479
+ case 'header':
1480
+ if (value !== null && _typeof(value) !== 'object') {
1481
+ return {
1482
+ valid: false,
1483
+ message: key + " \u5FC5\u987B\u662F\u5BF9\u8C61\u6216 null"
1484
+ };
1485
+ }
835
1486
 
836
- var deviceId = _this.hashFingerprint(fingerprint); // 存储设备ID
1487
+ break;
837
1488
 
1489
+ case 'contentType':
1490
+ if (value !== 'application/json' && value !== 'application/x-www-form-urlencoded') {
1491
+ return {
1492
+ valid: false,
1493
+ message: 'contentType 必须是 application/json 或 application/x-www-form-urlencoded'
1494
+ };
1495
+ }
838
1496
 
839
- _this.setCookie('device_id', deviceId, 365 * 2); // 存储2年
1497
+ break;
840
1498
 
1499
+ case 'platform':
1500
+ if (typeof value !== 'string') {
1501
+ return {
1502
+ valid: false,
1503
+ message: 'platform 必须是字符串'
1504
+ };
1505
+ }
841
1506
 
842
- _this.setLocalStorage('device_id', deviceId);
1507
+ break;
1508
+ }
843
1509
 
844
- return deviceId;
1510
+ return {
1511
+ valid: true
845
1512
  };
846
- /**
847
- * 收集浏览器指纹信息
848
- * @returns 返回浏览器指纹对象
849
- */
850
-
851
-
852
- this.collectFingerprint = function () {
853
- var fingerprint = {}; // 1. 用户代理
854
-
855
- fingerprint.userAgent = navigator.userAgent; // 2. 屏幕信息
856
-
857
- fingerprint.screenWidth = screen.width;
858
- fingerprint.screenHeight = screen.height;
859
- fingerprint.colorDepth = screen.colorDepth;
860
- fingerprint.pixelDepth = screen.pixelDepth; // 3. 时区
861
-
862
- fingerprint.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
863
- fingerprint.timezoneOffset = new Date().getTimezoneOffset(); // 4. 语言设置
864
-
865
- fingerprint.language = navigator.language;
866
- fingerprint.languages = Array.from(navigator.languages); // 5. 平台信息
867
-
868
- fingerprint.platform = navigator.platform; // 6. WebGL 指纹
869
-
870
- fingerprint.webgl = _this.getWebGLFingerprint(); // 7. Canvas 指纹
871
-
872
- fingerprint.canvas = _this.getCanvasFingerprint(); // 8. 音频上下文指纹
873
-
874
- fingerprint.audio = _this.getAudioFingerprint(); // 9. 字体检测
875
-
876
- fingerprint.fonts = _this.getFontFingerprint(); // 10. 插件信息
877
-
878
- fingerprint.plugins = _this.getPluginsFingerprint(); // 11. 存储检测
879
-
880
- fingerprint.localStorage = _this.hasLocalStorage();
881
- fingerprint.sessionStorage = _this.hasSessionStorage();
882
- fingerprint.indexedDB = _this.hasIndexedDB(); // 12. 硬件信息
1513
+ };
1514
+ /**
1515
+ * 用户登录
1516
+ */
883
1517
 
884
- fingerprint.hardwareConcurrency = navigator.hardwareConcurrency;
885
- fingerprint.deviceMemory = navigator.deviceMemory;
886
- fingerprint.maxTouchPoints = navigator.maxTouchPoints; // 13. 连接信息
887
1518
 
888
- fingerprint.connection = _this.getConnectionFingerprint();
889
- return fingerprint;
890
- };
891
- /**
892
- * 获取WebGL指纹
893
- * @returns WebGL指纹字符串
894
- */
1519
+ _this.login = function (userInfo) {
1520
+ if (_this.isObject(userInfo)) _this.userInfo = userInfo;
1521
+ };
1522
+ /**
1523
+ * 获取设备唯一标识
1524
+ * @returns 设备唯一标识
1525
+ */
895
1526
 
896
1527
 
897
- this.getWebGLFingerprint = function () {
898
- try {
899
- var canvas = document.createElement('canvas');
900
- var gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
901
- if (!gl) return 'not-supported';
902
- var debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
903
- var vendor = debugInfo ? gl.getParameter(debugInfo.UNMASKED_VENDOR_WEBGL) : 'unknown';
904
- var renderer = debugInfo ? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL) : 'unknown';
905
- return vendor + "|" + renderer;
906
- } catch (e) {
907
- return 'error';
908
- }
909
- };
910
- /**
911
- * 获取Canvas指纹
912
- * @returns Canvas指纹字符串
913
- */
1528
+ _this.getDeviceId = function () {
1529
+ // 如果已有设备ID,直接返回
1530
+ if (_this.deviceId) {
1531
+ return _this.deviceId;
1532
+ } // 获取已存储的设备ID
914
1533
 
915
1534
 
916
- this.getCanvasFingerprint = function () {
917
- try {
918
- var canvas = document.createElement('canvas');
919
- var ctx = canvas.getContext('2d');
920
- if (!ctx) return 'not-supported'; // 绘制特定图形
921
-
922
- ctx.textBaseline = 'top';
923
- ctx.font = '14px Arial';
924
- ctx.fillStyle = '#f60';
925
- ctx.fillRect(125, 1, 62, 20);
926
- ctx.fillStyle = '#069';
927
- ctx.fillText('Canvas fingerprint', 2, 15);
928
- ctx.fillStyle = 'rgba(102, 204, 0, 0.7)';
929
- ctx.fillText('Canvas fingerprint', 4, 17);
930
- return canvas.toDataURL().slice(-50); // 取后50个字符
931
- } catch (e) {
932
- return 'error';
933
- }
934
- };
935
- /**
936
- * 获取音频上下文指纹
937
- * @returns 音频指纹字符串
938
- */
1535
+ var storedDeviceId = _this.getCookie("device_id") || _this.getLocalStorage("device_id");
939
1536
 
1537
+ if (storedDeviceId) {
1538
+ _this.deviceId = storedDeviceId;
1539
+ return _this.deviceId;
1540
+ } // 收集浏览器指纹信息
940
1541
 
941
- this.getAudioFingerprint = function () {
942
- try {
943
- var AudioContextClass = window.AudioContext || window.webkitAudioContext;
944
- if (!AudioContextClass) return 'not-supported';
945
- var context = new AudioContextClass();
946
- var oscillator = context.createOscillator();
947
- var analyser = context.createAnalyser();
948
- var gain = context.createGain();
949
- var scriptProcessor = context.createScriptProcessor(4096, 1, 1);
950
- oscillator.type = 'triangle';
951
- oscillator.frequency.value = 10000;
952
- gain.gain.value = 0;
953
- oscillator.connect(analyser);
954
- analyser.connect(scriptProcessor);
955
- scriptProcessor.connect(gain);
956
- gain.connect(context.destination);
957
- oscillator.start(0);
958
- var fingerprint = context.sampleRate + '|' + context.currentTime;
959
- oscillator.stop();
960
- context.close();
961
- return fingerprint;
962
- } catch (e) {
963
- return 'error';
964
- }
965
- };
966
- /**
967
- * 获取字体指纹
968
- * @returns 字体指纹字符串
969
- */
970
1542
 
1543
+ var fingerprint = _this.collectFingerprint(); // 生成设备ID
971
1544
 
972
- this.getFontFingerprint = function () {
973
- try {
974
- var baseFonts_1 = ['monospace', 'sans-serif', 'serif'];
975
- var testString_1 = 'mmmmmmmmmmlli';
976
- var testSize_1 = '72px';
977
- var canvas = document.createElement('canvas');
978
- var ctx_1 = canvas.getContext('2d');
979
- if (!ctx_1) return 'not-supported';
980
- var detectedFonts_1 = []; // 测试字体列表
981
-
982
- var fonts = ['Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Georgia', 'Helvetica', 'Impact', 'Times New Roman', 'Trebuchet MS', 'Verdana']; // 获取基准宽度
983
-
984
- var baseWidths_1 = {};
985
- baseFonts_1.forEach(function (font) {
986
- ctx_1.font = testSize_1 + " " + font;
987
- baseWidths_1[font] = ctx_1.measureText(testString_1).width;
988
- }); // 测试每个字体
989
-
990
- fonts.forEach(function (font) {
991
- var detected = false;
992
- baseFonts_1.forEach(function (baseFont) {
993
- ctx_1.font = testSize_1 + " '" + font + "', " + baseFont;
994
- var width = ctx_1.measureText(testString_1).width;
995
-
996
- if (width !== baseWidths_1[baseFont]) {
997
- detected = true;
998
- }
999
- });
1000
1545
 
1001
- if (detected) {
1002
- detectedFonts_1.push(font);
1003
- }
1004
- });
1005
- return detectedFonts_1.join(',');
1006
- } catch (e) {
1007
- return 'error';
1008
- }
1009
- };
1010
- /**
1011
- * 获取插件指纹
1012
- * @returns 插件指纹字符串
1013
- */
1546
+ var deviceId = _this.hashFingerprint(fingerprint); // 存储设备ID(统一使用2年过期时间,与tools.ts保持一致)
1014
1547
 
1015
1548
 
1016
- this.getPluginsFingerprint = function () {
1017
- try {
1018
- var plugins = [];
1549
+ _this.setCookie("device_id", deviceId, 365 * 2); // 存储2年
1019
1550
 
1020
- if (navigator.plugins) {
1021
- for (var i = 0; i < navigator.plugins.length; i++) {
1022
- var plugin = navigator.plugins[i];
1023
1551
 
1024
- if (plugin) {
1025
- plugins.push(plugin.name + "|" + plugin.description + "|" + plugin.filename);
1026
- }
1027
- }
1028
- }
1552
+ _this.setLocalStorage("device_id", deviceId);
1029
1553
 
1030
- return plugins.join(';');
1031
- } catch (e) {
1032
- return 'error';
1033
- }
1034
- };
1035
- /**
1036
- * 检测localStorage支持
1037
- * @returns 是否支持localStorage
1038
- */
1554
+ _this.deviceId = deviceId;
1555
+ return _this.deviceId;
1556
+ };
1557
+ /**
1558
+ * 重置设备ID
1559
+ * 清除存储的设备ID并重新生成
1560
+ * @returns 新的设备ID
1561
+ */
1039
1562
 
1040
1563
 
1041
- this.hasLocalStorage = function () {
1042
- try {
1043
- var test = '__test__';
1044
- localStorage.setItem(test, test);
1045
- localStorage.removeItem(test);
1046
- return true;
1047
- } catch (e) {
1048
- return false;
1049
- }
1050
- };
1051
- /**
1052
- * 检测sessionStorage支持
1053
- * @returns 是否支持sessionStorage
1054
- */
1564
+ _this.resetDeviceId = function () {
1565
+ // 清除cookie和localStorage中的设备ID
1566
+ document.cookie = "device_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
1567
+ localStorage.removeItem("device_id"); // 清除内存中的设备ID
1055
1568
 
1569
+ _this.deviceId = ""; // 重新生成设备ID
1056
1570
 
1057
- this.hasSessionStorage = function () {
1058
- try {
1059
- var test = '__test__';
1060
- sessionStorage.setItem(test, test);
1061
- sessionStorage.removeItem(test);
1062
- return true;
1063
- } catch (e) {
1064
- return false;
1065
- }
1066
- };
1067
- /**
1068
- * 检测IndexedDB支持
1069
- * @returns 是否支持IndexedDB
1070
- */
1571
+ var newDeviceId = _this.getDeviceId();
1071
1572
 
1573
+ return newDeviceId;
1574
+ };
1575
+ /**
1576
+ * 自定义代码埋点上报
1577
+ * @param {object} TrackParams [自定义上报参数]
1578
+ * @return {Promise<TrackingResponse>} [回调]
1579
+ */
1072
1580
 
1073
- this.hasIndexedDB = function () {
1074
- return 'indexedDB' in window && indexedDB !== null;
1075
- };
1076
- /**
1077
- * 获取网络连接指纹
1078
- * @returns 网络连接指纹字符串
1079
- */
1080
1581
 
1582
+ _this.track = function (_a) {
1583
+ var desc = _a.desc,
1584
+ pageKey = _a.pageKey,
1585
+ partkey = _a.partkey,
1586
+ business = _a.business,
1587
+ header = _a.header;
1081
1588
 
1082
- this.getConnectionFingerprint = function () {
1083
- try {
1084
- var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
1085
- if (!connection) return 'not-supported';
1086
- return connection.effectiveType + "|" + connection.downlink + "|" + connection.rtt;
1087
- } catch (e) {
1088
- return 'error';
1589
+ var params = _this.getParams({
1590
+ desc: desc,
1591
+ event: "CustomTrack",
1592
+ itemKey: _this.getItemKey(partkey, pageKey),
1593
+ privateParamMap: {
1594
+ business: business
1089
1595
  }
1090
- };
1091
- /**
1092
- * 将指纹信息哈希为唯一ID
1093
- * @param fingerprint 指纹信息
1094
- * @returns 返回哈希后的设备ID
1095
- */
1596
+ });
1096
1597
 
1598
+ return _this.sendData(params, header);
1599
+ };
1600
+ /**
1601
+ * @description 监听全埋点事件
1602
+ */
1097
1603
 
1098
- this.hashFingerprint = function (fingerprint) {
1099
- // 将指纹对象转换为字符串
1100
- var fingerprintString = JSON.stringify(fingerprint, Object.keys(fingerprint).sort()); // 使用更强大的哈希算法生成ID
1101
1604
 
1102
- var hash1 = 5381; // DJB2哈希算法的初始值
1605
+ _this.listener = function () {
1606
+ // 先移除旧的监听器,避免重复绑定
1607
+ _this.unlistener(); // 如果启用了全埋点,监听页面浏览事件
1103
1608
 
1104
- var hash2 = 52711; // 第二个哈希的初始值
1105
1609
 
1106
- for (var i = 0; i < fingerprintString.length; i++) {
1107
- var char = fingerprintString.charCodeAt(i);
1108
- hash1 = (hash1 << 5) + hash1 + char; // hash1 * 33 + char
1610
+ if (!!_this.initConfig.autoTrack) {
1611
+ if (!!_this.initConfig.isTrackSinglePage) {
1612
+ _this.rewriteHistory();
1109
1613
 
1110
- hash2 = (hash2 << 5) + hash2 + char; // hash2 * 33 + char
1111
- } // 组合两个哈希值并转换为64位整数
1614
+ _this.addSinglePageEvent(_this.onPageViewCallback);
1615
+ }
1112
1616
 
1617
+ _this.each(["load", "beforeunload"], function (historyType) {
1618
+ _this.addEventListener(window, historyType, _this.onPageViewCallback);
1619
+ });
1620
+ } // 如果启用了全埋点或启用了 data-part-key 点击追踪,监听点击事件
1113
1621
 
1114
- var combinedHash = (hash1 >>> 0) * 4096 + (hash2 >>> 0); // 转换为36进制并添加前缀,增加长度和唯一性
1115
1622
 
1116
- var deviceId = 'fp_' + combinedHash.toString(36);
1117
- return deviceId;
1118
- };
1119
- }
1623
+ if (!!_this.initConfig.autoTrack || !!_this.initConfig.trackPartKeyClick) {
1624
+ _this.addEventListener(window, "click", _this.onClickCallback);
1625
+ }
1626
+ };
1120
1627
  /**
1121
- * 打印log
1628
+ * @description 取消全埋点事件
1122
1629
  */
1123
1630
 
1124
1631
 
1125
- WebTrackingTools.prototype.printLog = function () {
1126
- var rest = [];
1632
+ _this.unlistener = function () {
1633
+ if (!!_this.initConfig.isTrackSinglePage) {
1634
+ var historyPushState = window.history.pushState;
1635
+ var singlePageEvent = !!historyPushState ? "popstate" : "hashchange";
1127
1636
 
1128
- for (var _i = 0; _i < arguments.length; _i++) {
1129
- rest[_i] = arguments[_i];
1637
+ _this.each(["pushState", "replaceState", singlePageEvent], function (historyName) {
1638
+ _this.removeEventListener(window, historyName, _this.onPageViewCallback);
1639
+ });
1130
1640
  }
1131
1641
 
1132
- if (this.isObject(rest[0])) {
1133
- rest[0] = this.formatJsonString(rest[0]);
1134
- }
1642
+ _this.each(["load", "beforeunload"], function (historyType) {
1643
+ _this.removeEventListener(window, historyType, _this.onPageViewCallback);
1644
+ });
1135
1645
 
1136
- if ((typeof console === "undefined" ? "undefined" : _typeof(console)) === "object" && console.log) {
1137
- try {
1138
- return console.log.apply(console, rest);
1139
- } catch (e) {
1140
- console.log(rest[0]);
1141
- }
1142
- }
1646
+ _this.removeEventListener(window, "click", _this.onClickCallback); // 清理批量发送定时器
1647
+
1648
+
1649
+ _this.clearBatchTimer(); // 清理定时上报定时器
1650
+
1651
+
1652
+ _this.stopPageDurationTimer();
1143
1653
  };
1144
1654
  /**
1145
- * @description 检验是否是对象
1655
+ * @description 清理批量发送定时器
1146
1656
  */
1147
1657
 
1148
1658
 
1149
- WebTrackingTools.prototype.isObject = function (obj) {
1150
- if (obj == null) {
1151
- return false;
1152
- } else {
1153
- return toString.call(obj) == "[object Object]";
1659
+ _this.clearBatchTimer = function () {
1660
+ if (_this.batchTimer !== null) {
1661
+ clearTimeout(_this.batchTimer);
1662
+ _this.batchTimer = null;
1154
1663
  }
1155
1664
  };
1665
+ /**
1666
+ * @description 清空批量队列(包括 LocalStorage 中的数据)
1667
+ */
1156
1668
 
1157
- WebTrackingTools.prototype.isUndefined = function (obj) {
1158
- return obj === void 0;
1159
- };
1160
1669
 
1161
- WebTrackingTools.prototype.isString = function (obj) {
1162
- return toString.call(obj) == "[object String]";
1163
- };
1670
+ _this.clearBatchQueue = function () {
1671
+ _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
1164
1672
 
1165
- WebTrackingTools.prototype.isDate = function (obj) {
1166
- return toString.call(obj) == "[object Date]";
1673
+ if (_this.initConfig.showLog) {
1674
+ _this.printLog("批量队列已清空");
1675
+ }
1167
1676
  };
1677
+ /**
1678
+ * @description 从 LocalStorage 获取批量队列
1679
+ * @returns 批量队列数组
1680
+ */
1168
1681
 
1169
- WebTrackingTools.prototype.isBoolean = function (obj) {
1170
- return toString.call(obj) == "[object Boolean]";
1171
- };
1172
1682
 
1173
- WebTrackingTools.prototype.isNumber = function (obj) {
1174
- return toString.call(obj) == "[object Number]" && /[\d\.]+/.test(String(obj));
1175
- };
1683
+ _this.getBatchQueueFromStorage = function () {
1684
+ try {
1685
+ var storedQueue = _this.getLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY);
1176
1686
 
1177
- WebTrackingTools.prototype.isElement = function (obj) {
1178
- return !!(obj && obj.nodeType === 1);
1179
- };
1687
+ if (storedQueue) {
1688
+ var parsedQueue = JSON.parse(storedQueue);
1180
1689
 
1181
- WebTrackingTools.prototype.isFunction = function (f) {
1182
- if (!f) {
1183
- return false;
1690
+ if (Array.isArray(parsedQueue)) {
1691
+ return parsedQueue;
1692
+ }
1693
+ }
1694
+ } catch (e) {
1695
+ _this.printLog("\u8BFB\u53D6\u6279\u91CF\u961F\u5217\u5931\u8D25: " + e); // 如果解析失败,清除损坏的数据
1696
+
1697
+
1698
+ _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
1184
1699
  }
1185
1700
 
1186
- var type = toString.call(f);
1187
- return type == "[object Function]" || type == "[object AsyncFunction]";
1701
+ return [];
1188
1702
  };
1703
+ /**
1704
+ * @description 保存批量队列到 LocalStorage
1705
+ * @param queue 批量队列数组
1706
+ */
1189
1707
 
1190
- WebTrackingTools.prototype.isJSONString = function (str) {
1191
- if (!this.isString(str)) return false;
1192
1708
 
1709
+ _this.saveBatchQueueToStorage = function (queue) {
1193
1710
  try {
1194
- JSON.parse(str);
1711
+ var queueString = JSON.stringify(queue); // 检查存储大小,避免超出 LocalStorage 限制
1712
+
1713
+ if (queueString.length > _this.MAX_STORAGE_SIZE) {
1714
+ var maxItems = Math.floor(queue.length * 0.8); // 保留 80%
1715
+
1716
+ var trimmedQueue = queue.slice(-maxItems);
1717
+
1718
+ _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");
1719
+
1720
+ _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, JSON.stringify(trimmedQueue));
1721
+ } else {
1722
+ _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, queueString);
1723
+ }
1195
1724
  } catch (e) {
1196
- return false;
1725
+ // LocalStorage 可能已满或不可用
1726
+ _this.printLog("\u4FDD\u5B58\u6279\u91CF\u961F\u5217\u5230 LocalStorage \u5931\u8D25: " + e);
1197
1727
  }
1198
-
1199
- return true;
1200
1728
  };
1729
+ /**
1730
+ * @description 从 LocalStorage 获取待发送请求队列
1731
+ * @returns 待发送请求队列数组
1732
+ */
1201
1733
 
1202
- WebTrackingTools.prototype._decodeURIComponent = function (val) {
1203
- var result = val;
1204
1734
 
1735
+ _this.getPendingRequestsFromStorage = function () {
1205
1736
  try {
1206
- result = decodeURIComponent(val);
1737
+ var storedRequests = _this.getLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY);
1738
+
1739
+ if (storedRequests) {
1740
+ var parsedRequests = JSON.parse(storedRequests);
1741
+
1742
+ if (Array.isArray(parsedRequests)) {
1743
+ return parsedRequests;
1744
+ }
1745
+ }
1207
1746
  } catch (e) {
1208
- result = val;
1747
+ _this.printLog("\u8BFB\u53D6\u5F85\u53D1\u9001\u8BF7\u6C42\u5931\u8D25: " + e); // 如果解析失败,清除损坏的数据
1748
+
1749
+
1750
+ _this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, "[]");
1209
1751
  }
1210
1752
 
1211
- return result;
1753
+ return [];
1754
+ };
1755
+ /**
1756
+ * @description 保存待发送请求队列到 LocalStorage
1757
+ * @param requests 待发送请求队列数组
1758
+ */
1759
+
1760
+
1761
+ _this.savePendingRequestsToStorage = function (requests) {
1762
+ try {
1763
+ _this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, JSON.stringify(requests));
1764
+ } catch (e) {
1765
+ // LocalStorage 可能已满或不可用
1766
+ _this.printLog("\u4FDD\u5B58\u5F85\u53D1\u9001\u8BF7\u6C42\u5230 LocalStorage \u5931\u8D25: " + e);
1767
+ }
1212
1768
  };
1769
+ /**
1770
+ * @description 设置自定义页面唯一标识
1771
+ * @param pageKey 页面唯一标识,如果传入 null 或空字符串,则恢复自动生成
1772
+ * @param autoUpdate 路由变化时是否自动更新(默认:false,使用自定义值后不再自动更新)
1773
+ */
1774
+
1213
1775
 
1214
- WebTrackingTools.prototype.getMainHost = function () {
1215
- var key = "mh_" + Math.random();
1216
- var keyR = new RegExp("(^|;)\\s*" + key + "=12345");
1217
- var expiredTime = new Date(0);
1218
- var domain = document.domain;
1219
- var domainList = domain.split(".");
1220
- var urlItems = []; // 主域名一定会有两部分组成
1776
+ _this.setPageKey = function (pageKey, autoUpdate) {
1777
+ if (autoUpdate === void 0) {
1778
+ autoUpdate = false;
1779
+ }
1221
1780
 
1222
- urlItems.unshift(domainList.pop()); // 慢慢从后往前测试
1781
+ if (pageKey === null || pageKey === '') {
1782
+ // 恢复自动生成
1783
+ _this.useCustomPageKey = false;
1784
+ var pathname = window.location.pathname;
1785
+ _this.pageKey = pathname.replace(/\//g, "_").substring(1);
1223
1786
 
1224
- while (domainList.length) {
1225
- urlItems.unshift(domainList.pop());
1226
- var mainHost = urlItems.join(".");
1227
- var cookie = key + "=" + 12345 + ";domain=." + mainHost;
1228
- document.cookie = cookie; //如果cookie存在,则说明域名合法
1787
+ if (_this.initConfig.showLog) {
1788
+ _this.printLog("\u9875\u9762\u6807\u8BC6\u5DF2\u6062\u590D\u81EA\u52A8\u751F\u6210: " + _this.pageKey);
1789
+ }
1790
+ } else {
1791
+ _this.pageKey = pageKey;
1792
+ _this.useCustomPageKey = !autoUpdate;
1229
1793
 
1230
- if (keyR.test(document.cookie)) {
1231
- document.cookie = cookie + ";expires=" + expiredTime;
1232
- return mainHost;
1794
+ if (_this.initConfig.showLog) {
1795
+ _this.printLog("\u9875\u9762\u6807\u8BC6\u5DF2\u8BBE\u7F6E\u4E3A: " + pageKey + ", \u81EA\u52A8\u66F4\u65B0: " + autoUpdate);
1233
1796
  }
1234
1797
  }
1798
+ };
1799
+ /**
1800
+ * @description 获取当前页面唯一标识
1801
+ * @returns 当前页面唯一标识
1802
+ */
1803
+
1235
1804
 
1236
- return domain;
1805
+ _this.getPageKey = function () {
1806
+ return _this.pageKey;
1237
1807
  };
1238
1808
 
1239
- return WebTrackingTools;
1240
- }();
1809
+ _this.onClickCallback = function (e) {
1810
+ var _a;
1811
+
1812
+ var target = e.target;
1813
+ if (!((_a = target === null || target === void 0 ? void 0 : target.dataset) === null || _a === void 0 ? void 0 : _a.partKey)) return;
1814
+ var position = [e.pageX, e.pageY];
1815
+ var id = target.id;
1816
+ var className = target.className;
1817
+ var nodeName = target.nodeName;
1818
+ var targetEle = {
1819
+ id: id,
1820
+ nodeName: nodeName,
1821
+ className: className,
1822
+ position: position
1823
+ };
1824
+
1825
+ var params = _this.getParams({
1826
+ event: "WebClick",
1827
+ desc: _this.eventDescMap["WebClick"],
1828
+ itemKey: _this.getItemKey(target.dataset.partKey),
1829
+ privateParamMap: {
1830
+ targetEle: targetEle,
1831
+ pointerType: e.pointerType,
1832
+ currentUrl: _this.currentUrl,
1833
+ elementSelector: _this.getDomSelector(target) || ""
1834
+ }
1835
+ });
1241
1836
 
1242
- var WebTracking =
1243
- /** @class */
1244
- function (_super) {
1245
- __extends(WebTracking, _super);
1837
+ _this.sendData(params);
1838
+ };
1839
+ /**
1840
+ * @description 路由触发事件
1841
+ */
1246
1842
 
1247
- function WebTracking() {
1248
- var _this = _super.call(this) || this; // 批量发送队列
1249
1843
 
1844
+ _this.onPageViewCallback = function (e) {
1845
+ var _a, _b; // 在路由变化前,先发送待发送的数据(避免被取消)
1250
1846
 
1251
- _this.batchQueue = []; // 批量发送定时器
1252
1847
 
1253
- _this.batchTimer = null; // LocalStorage 存储 key
1848
+ var pendingRequests = _this.getPendingRequestsFromStorage();
1254
1849
 
1255
- _this.BATCH_QUEUE_STORAGE_KEY = "web_tracking_batch_queue"; // 是否使用自定义 pageKey(如果为 true,路由变化时不会自动更新 pageKey)
1850
+ var batchQueue = _this.initConfig.batchSend ? _this.getBatchQueueFromStorage() : [];
1256
1851
 
1257
- _this.useCustomPageKey = false; // 待发送的单个请求队列(用于页面跳转时发送)
1852
+ if (pendingRequests.length > 0 || batchQueue.length > 0) {
1853
+ _this.flushPendingData();
1854
+ }
1258
1855
 
1259
- _this.pendingRequests = []; // 页面卸载监听器是否已设置
1856
+ var ORGIN = window.location.origin;
1260
1857
 
1261
- _this.isUnloadListenerSetup = false; // LocalStorage 存储 key(待发送请求)
1858
+ var params = _this.getParams({
1859
+ event: "PageView",
1860
+ desc: _this.eventDescMap["PageView"],
1861
+ privateParamMap: {
1862
+ currentUrl: _this.currentUrl,
1863
+ 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
1864
+ }
1865
+ });
1262
1866
 
1263
- _this.PENDING_REQUESTS_STORAGE_KEY = "web_tracking_pending_requests"; // 待发送请求队列最大大小(默认值,可通过配置覆盖)
1867
+ _this.currentUrl = window.location.href; // 如果使用自定义 pageKey,路由变化时不自动更新
1264
1868
 
1265
- _this.DEFAULT_PENDING_REQUESTS_MAX_SIZE = 50; // LocalStorage 最大大小限制(4MB)
1869
+ if (!_this.useCustomPageKey) {
1870
+ _this.pageKey = window.location.pathname.replace(/\//g, "_").substring(1);
1871
+ } // 路由变化时,如果启用了定时上报,需要重启定时器
1266
1872
 
1267
- _this.MAX_STORAGE_SIZE = 4 * 1024 * 1024; // 用户信息
1268
1873
 
1269
- _this.userInfo = null; // 当前路由
1874
+ if (_this.initConfig.autoTrackPageDurationInterval) {
1875
+ _this.stopPageDurationTimer();
1270
1876
 
1271
- _this.currentUrl = ""; // 页面唯一标识,取当前路由 window.location.pathname.replace(/\//g, '_').substr(1)
1877
+ _this.startPageDurationTimer();
1878
+ }
1272
1879
 
1273
- _this.pageKey = ""; // 设备唯一标识
1880
+ _this.sendRetained(e.type);
1274
1881
 
1275
- _this.deviceId = ""; // 上传事件描述
1882
+ _this.sendData(params);
1883
+ };
1276
1884
 
1277
- _this.eventDescMap = {
1278
- PageView: "Web 浏览页面",
1279
- WebClick: "Web 元素点击",
1280
- PageRetained: "Web 页面浏览时长",
1281
- CustomTrack: "Web 自定义代码上报"
1282
- };
1283
- /**
1284
- * @description 初始化函数
1285
- * @param {object} InitParams [初始化参数]
1286
- */
1885
+ _this.getParams = function (_a) {
1886
+ var event = _a.event,
1887
+ desc = _a.desc,
1888
+ _b = _a.privateParamMap,
1889
+ privateParamMap = _b === void 0 ? {} : _b,
1890
+ itemKey = _a.itemKey;
1891
+ var business = _this.initConfig.business;
1892
+ var pageWidth = window.innerWidth;
1893
+ var pageHeight = window.innerHeight;
1894
+ var screenWidth = window.screen.width;
1895
+ var screenHeight = window.screen.height; // 过滤敏感数据
1287
1896
 
1288
- _this.init = function (initParams) {
1289
- _this.preset(initParams);
1897
+ var filteredBusiness = _this.filterSensitiveData(business || {});
1290
1898
 
1291
- var pathname = window.location.pathname;
1292
- _this.currentUrl = window.location.href; // 如果传入了自定义 pageKey,使用自定义值,否则自动生成
1899
+ var filteredUserInfo = _this.filterSensitiveData(_this.userInfo || {});
1293
1900
 
1294
- if (initParams.pageKey) {
1295
- _this.pageKey = initParams.pageKey;
1296
- _this.useCustomPageKey = true;
1297
- } else {
1298
- _this.pageKey = pathname.replace(/\//g, "_").substring(1);
1299
- _this.useCustomPageKey = false;
1300
- }
1901
+ var filteredPrivateParamMap = _this.filterSensitiveData(privateParamMap || {});
1301
1902
 
1302
- _this.systemsInfo = _this.getSystemsInfo(initParams.platform); // 获取设备ID
1903
+ var filteredUrlParams = _this.filterSensitiveData(_this.getQueryValue() || {}); // 创建私有参数对象
1303
1904
 
1304
- _this.deviceId = _this.getDeviceId(); // 如果传入了 userInfo,设置用户信息
1305
1905
 
1306
- if (initParams.userInfo && _this.isObject(initParams.userInfo)) {
1307
- _this.userInfo = initParams.userInfo;
1308
- }
1906
+ var privateParamMapData = {
1907
+ currentUrl: filteredPrivateParamMap.currentUrl || _this.currentUrl,
1908
+ business: Object.assign({}, filteredBusiness, filteredPrivateParamMap.business || {}),
1909
+ pageWidth: pageWidth,
1910
+ pageHeight: pageHeight,
1911
+ screenWidth: screenWidth,
1912
+ screenHeight: screenHeight,
1913
+ sdkVersion: _this.sdkVersion,
1914
+ systemsInfo: _this.systemsInfo,
1915
+ urlParams: filteredUrlParams,
1916
+ userInfo: filteredUserInfo,
1917
+ deviceId: _this.deviceId // 添加设备ID
1309
1918
 
1310
- _this.setCookie("retainedStartTime", _this.getTimeStamp()); // 如果启用了批量发送,从 LocalStorage 恢复队列
1919
+ }; // 添加其他可能的属性
1311
1920
 
1921
+ if (filteredPrivateParamMap.targetEle) {
1922
+ privateParamMapData.targetEle = filteredPrivateParamMap.targetEle;
1923
+ }
1312
1924
 
1313
- if (_this.initConfig.batchSend) {
1314
- _this.restoreBatchQueueFromStorage();
1315
- } // 恢复待发送的单个请求
1925
+ if (filteredPrivateParamMap.targetUrl) {
1926
+ privateParamMapData.targetUrl = filteredPrivateParamMap.targetUrl;
1927
+ }
1316
1928
 
1929
+ if (filteredPrivateParamMap.pointerType) {
1930
+ privateParamMapData.pointerType = filteredPrivateParamMap.pointerType;
1931
+ }
1317
1932
 
1318
- _this.restorePendingRequestsFromStorage(); // 无论是否启用批量发送,都需要监听页面卸载事件,确保数据发送
1933
+ if (filteredPrivateParamMap.elementSelector) {
1934
+ privateParamMapData.elementSelector = filteredPrivateParamMap.elementSelector;
1935
+ }
1319
1936
 
1937
+ if (filteredPrivateParamMap.retainedDuration) {
1938
+ privateParamMapData.retainedDuration = filteredPrivateParamMap.retainedDuration;
1939
+ }
1320
1940
 
1321
- _this.setupBeforeUnloadListener();
1941
+ return {
1942
+ event: event,
1943
+ desc: desc,
1944
+ itemKey: itemKey || _this.getItemKey(),
1945
+ requestTime: _this.getTimeStamp(),
1946
+ privateParamMap: privateParamMapData
1322
1947
  };
1323
- /**
1324
- * TODO: 需要判断有哪些不能被预制的参数
1325
- * @description 预置参数
1326
- * @param {object} PresetParams [预置参数]
1327
- */
1328
-
1329
-
1330
- _this.preset = function (presetParams) {
1331
- if (presetParams instanceof Object) {
1332
- // 处理 pageKey 特殊逻辑
1333
- if (presetParams.pageKey !== undefined) {
1334
- if (presetParams.pageKey === null || presetParams.pageKey === '') {
1335
- // 恢复自动生成
1336
- _this.useCustomPageKey = false;
1337
- var pathname = window.location.pathname;
1338
- _this.pageKey = pathname.replace(/\//g, "_").substring(1);
1339
- } else {
1340
- _this.pageKey = presetParams.pageKey;
1341
- _this.useCustomPageKey = true;
1342
- }
1343
- }
1948
+ };
1949
+ /**
1950
+ * 数据采样判断
1951
+ * @returns 是否应该采样
1952
+ */
1344
1953
 
1345
- _this.each(presetParams, function (val, key) {
1346
- // 跳过 pageKey,因为已经单独处理
1347
- if (key === 'pageKey') return;
1348
1954
 
1349
- if (_this.initConfig.hasOwnProperty(key)) {
1350
- // TODO:后面加一些校验
1351
- _this.initConfig[key] = val;
1352
- }
1353
- });
1354
- }
1955
+ _this.shouldSample = function () {
1956
+ var sampleRate = _this.initConfig.sampleRate;
1957
+ if (sampleRate >= 1) return true;
1958
+ if (sampleRate <= 0) return false;
1959
+ return Math.random() < sampleRate;
1960
+ };
1961
+ /**
1962
+ * 批量发送数据
1963
+ */
1355
1964
 
1356
- if (!/^(((ht|f)tps?):\/\/)?[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-\(\)]*[\w@?^=%&/~+#-\(\)])?$/.test(_this.initConfig["serverUrl"])) {
1357
- _this.printLog("当前 server_url 为空或不正确,只在控制台打印日志,network 中不会发数据,请配置正确的 server_url!");
1358
1965
 
1359
- _this.initConfig["showLog"] = true;
1360
- } // 如果启用了全埋点或启用了 data-part-key 点击追踪
1966
+ _this.flushBatchQueue = function () {
1967
+ var batchQueue = _this.getBatchQueueFromStorage();
1361
1968
 
1969
+ if (batchQueue.length === 0) return;
1362
1970
 
1363
- if (!!_this.initConfig["autoTrack"] || !!_this.initConfig["trackPartKeyClick"]) {
1364
- // 启用监听
1365
- _this.listener();
1366
- } else {
1367
- // 取消监听
1368
- _this.unlistener();
1971
+ var currentTime = _this.getTimeStamp(); // 过滤出可以发送的数据(未到重试时间的不发送)
1972
+
1973
+
1974
+ var readyToSend = batchQueue.filter(function (item) {
1975
+ if (!item._nextRetryTime) {
1976
+ return true;
1369
1977
  }
1370
- };
1371
- /**
1372
- * 用户登录
1373
- */
1374
1978
 
1979
+ return item._nextRetryTime <= currentTime;
1980
+ });
1375
1981
 
1376
- _this.login = function (userInfo) {
1377
- if (_this.isObject(userInfo)) _this.userInfo = userInfo;
1378
- };
1379
- /**
1380
- * 获取设备唯一标识
1381
- * @returns 设备唯一标识
1382
- */
1982
+ if (readyToSend.length === 0) {
1983
+ if (_this.initConfig.showLog) {
1984
+ _this.printLog("\u6279\u91CF\u961F\u5217\u4E2D\u6709 " + batchQueue.length + " \u6761\u6570\u636E\u7B49\u5F85\u91CD\u8BD5");
1985
+ }
1383
1986
 
1987
+ return;
1988
+ } // 从队列中移除已准备发送的数据
1384
1989
 
1385
- _this.getDeviceId = function () {
1386
- // 如果已有设备ID,直接返回
1387
- if (_this.deviceId) {
1388
- return _this.deviceId;
1389
- } // 获取已存储的设备ID
1390
1990
 
1991
+ var remainingQueue = batchQueue.filter(function (item) {
1992
+ if (!item._nextRetryTime) {
1993
+ return false;
1994
+ }
1391
1995
 
1392
- var storedDeviceId = _this.getCookie("device_id") || _this.getLocalStorage("device_id");
1996
+ return item._nextRetryTime > currentTime;
1997
+ }); // 保存剩余的队列
1393
1998
 
1394
- if (storedDeviceId) {
1395
- _this.deviceId = storedDeviceId;
1396
- return _this.deviceId;
1397
- } // 收集浏览器指纹信息
1999
+ _this.saveBatchQueueToStorage(remainingQueue); // 发送批量数据
1398
2000
 
1399
2001
 
1400
- var fingerprint = _this.collectFingerprint(); // 生成设备ID
2002
+ _this.sendBatchData(readyToSend);
2003
+ };
2004
+ /**
2005
+ * 发送批量数据
2006
+ * @param data 批量数据
2007
+ */
1401
2008
 
1402
2009
 
1403
- var deviceId = _this.hashFingerprint(fingerprint); // 存储设备ID(统一使用2年过期时间,与tools.ts保持一致)
2010
+ _this.sendBatchData = function (data) {
2011
+ var _a = _this.initConfig,
2012
+ serverUrl = _a.serverUrl,
2013
+ contentType = _a.contentType,
2014
+ showLog = _a.showLog,
2015
+ sendMethod = _a.sendMethod,
2016
+ initHeader = _a.header;
1404
2017
 
2018
+ if (showLog) {
2019
+ _this.printLog("\u6279\u91CF\u53D1\u9001 " + data.length + " \u6761\u6570\u636E");
1405
2020
 
1406
- _this.setCookie("device_id", deviceId, 365 * 2); // 存储2年
2021
+ data.forEach(function (item) {
2022
+ return _this.printLog(item);
2023
+ });
2024
+ } // 判断是否使用 sendBeacon
1407
2025
 
1408
2026
 
1409
- _this.setLocalStorage("device_id", deviceId);
2027
+ var shouldUseBeacon = _this.shouldUseBeacon(sendMethod, undefined, initHeader); // 如果使用 sendBeacon
1410
2028
 
1411
- _this.deviceId = deviceId;
1412
- return _this.deviceId;
1413
- };
1414
- /**
1415
- * 重置设备ID
1416
- * 清除存储的设备ID并重新生成
1417
- * @returns 新的设备ID
1418
- */
1419
2029
 
2030
+ if (shouldUseBeacon) {
2031
+ try {
2032
+ var blob = new Blob([JSON.stringify(data)], {
2033
+ type: contentType || "application/json"
2034
+ });
2035
+ var sent = navigator.sendBeacon(serverUrl, blob);
1420
2036
 
1421
- _this.resetDeviceId = function () {
1422
- // 清除cookie和localStorage中的设备ID
1423
- document.cookie = "device_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
1424
- localStorage.removeItem("device_id"); // 清除内存中的设备ID
2037
+ if (sent) {
2038
+ // 发送成功,确保 LocalStorage 已清空
2039
+ _this.saveBatchQueueToStorage([]);
1425
2040
 
1426
- _this.deviceId = ""; // 重新生成设备ID
2041
+ if (showLog) {
2042
+ _this.printLog("\u6279\u91CF\u53D1\u9001\u6210\u529F: " + data.length + " \u6761\u6570\u636E");
2043
+ }
2044
+ } else {
2045
+ // sendBeacon 返回 false,重新加入队列以便重试
2046
+ _this.printLog("\u6279\u91CF\u53D1\u9001\u5931\u8D25: sendBeacon \u8FD4\u56DE false\uFF0C\u6570\u636E\u5DF2\u91CD\u65B0\u52A0\u5165\u961F\u5217");
1427
2047
 
1428
- var newDeviceId = _this.getDeviceId();
2048
+ _this.retryBatchData(data);
2049
+ }
2050
+ } catch (e) {
2051
+ // sendBeacon 失败,重新加入队列以便重试
2052
+ _this.printLog("\u6279\u91CF\u53D1\u9001\u5931\u8D25: " + e + "\uFF0C\u6570\u636E\u5DF2\u91CD\u65B0\u52A0\u5165\u961F\u5217");
1429
2053
 
1430
- return newDeviceId;
1431
- };
1432
- /**
1433
- * 自定义代码埋点上报
1434
- * @param {object} TrackParams [自定义上报参数]
1435
- * @return {Promise<TrackingResponse>} [回调]
1436
- */
1437
-
1438
-
1439
- _this.track = function (_a) {
1440
- var desc = _a.desc,
1441
- pageKey = _a.pageKey,
1442
- partkey = _a.partkey,
1443
- business = _a.business,
1444
- header = _a.header;
1445
-
1446
- var params = _this.getParams({
1447
- desc: desc,
1448
- event: "CustomTrack",
1449
- itemKey: _this.getItemKey(partkey, pageKey),
1450
- privateParamMap: {
1451
- business: business
2054
+ _this.retryBatchData(data);
2055
+ }
2056
+ } else {
2057
+ // 使用 XMLHttpRequest 发送
2058
+ _this.ajax({
2059
+ url: serverUrl,
2060
+ type: "POST",
2061
+ data: JSON.stringify({
2062
+ events: data
2063
+ }),
2064
+ contentType: contentType,
2065
+ credentials: false,
2066
+ timeout: _this.initConfig.sendTimeout,
2067
+ cors: true,
2068
+ success: function success() {
2069
+ // 批量发送成功,确保 LocalStorage 已清空
2070
+ // flushBatchQueue 在发送前已清空 LocalStorage,这里再次确认
2071
+ _this.saveBatchQueueToStorage([]);
2072
+
2073
+ if (_this.initConfig.showLog) {
2074
+ _this.printLog("\u6279\u91CF\u53D1\u9001\u6210\u529F: " + data.length + " \u6761\u6570\u636E");
2075
+ }
2076
+ },
2077
+ error: function error(err) {
2078
+ // 批量发送失败,重新加入队列以便重试
2079
+ _this.printLog("\u6279\u91CF\u53D1\u9001\u5931\u8D25: " + err + "\uFF0C\u6570\u636E\u5DF2\u91CD\u65B0\u52A0\u5165\u961F\u5217");
2080
+
2081
+ _this.retryBatchData(data);
1452
2082
  }
1453
2083
  });
2084
+ }
2085
+ };
2086
+ /**
2087
+ * @description 批量数据重试逻辑
2088
+ * @param data 批量数据
2089
+ */
1454
2090
 
1455
- return _this.sendData(params, header);
1456
- };
1457
- /**
1458
- * @description 监听全埋点事件
1459
- */
1460
2091
 
2092
+ _this.retryBatchData = function (data) {
2093
+ // 获取当前队列
2094
+ var currentQueue = _this.getBatchQueueFromStorage(); // 去重:基于事件类型、itemKey、requestTime 生成唯一键
1461
2095
 
1462
- _this.listener = function () {
1463
- // 如果启用了全埋点,监听页面浏览事件
1464
- if (!!_this.initConfig.autoTrack) {
1465
- if (!!_this.initConfig.isTrackSinglePage) {
1466
- _this.rewriteHistory();
1467
2096
 
1468
- _this.addSinglePageEvent(_this.onPageViewCallback);
1469
- }
2097
+ var getEventKey = function getEventKey(item) {
2098
+ return item.event + "_" + item.itemKey + "_" + item.requestTime;
2099
+ };
1470
2100
 
1471
- _this.each(["load", "beforeunload"], function (historyType) {
1472
- _this.addEventListener(window, historyType, _this.onPageViewCallback);
1473
- });
1474
- } // 如果启用了全埋点或启用了 data-part-key 点击追踪,监听点击事件
2101
+ var existingKeys = new Set(currentQueue.map(getEventKey));
2102
+ var maxRetryCount = 3; // 最大重试次数
1475
2103
 
2104
+ var currentTime = _this.getTimeStamp(); // 过滤并更新重试信息
1476
2105
 
1477
- if (!!_this.initConfig.autoTrack || !!_this.initConfig.trackPartKeyClick) {
1478
- _this.addEventListener(window, "click", _this.onClickCallback);
1479
- }
1480
- };
1481
- /**
1482
- * @description 取消全埋点事件
1483
- */
1484
2106
 
2107
+ var retryData = data.filter(function (item) {
2108
+ var key = getEventKey(item); // 检查是否已存在
1485
2109
 
1486
- _this.unlistener = function () {
1487
- if (!!_this.initConfig.isTrackSinglePage) {
1488
- var historyPushState = window.history.pushState;
1489
- var singlePageEvent = !!historyPushState ? "popstate" : "hashchange";
2110
+ if (existingKeys.has(key)) {
2111
+ return false;
2112
+ } // 检查重试次数
1490
2113
 
1491
- _this.each(["pushState", "replaceState", singlePageEvent], function (historyName) {
1492
- _this.removeEventListener(window, historyName, _this.onPageViewCallback);
1493
- });
1494
- }
1495
2114
 
1496
- _this.each(["load", "beforeunload"], function (historyType) {
1497
- _this.removeEventListener(window, historyType, _this.onPageViewCallback);
1498
- });
2115
+ var retryCount = (item._retryCount || 0) + 1;
1499
2116
 
1500
- _this.removeEventListener(window, "click", _this.onClickCallback); // 清理批量发送定时器
2117
+ if (retryCount > maxRetryCount) {
2118
+ if (_this.initConfig.showLog) {
2119
+ _this.printLog("\u6570\u636E\u5DF2\u8FBE\u5230\u6700\u5927\u91CD\u8BD5\u6B21\u6570\uFF0C\u653E\u5F03\u91CD\u8BD5: " + key);
2120
+ }
1501
2121
 
2122
+ return false;
2123
+ } // 更新重试信息
1502
2124
 
1503
- _this.clearBatchTimer();
1504
- };
1505
- /**
1506
- * @description 清理批量发送定时器
1507
- */
1508
2125
 
2126
+ item._retryCount = retryCount; // 指数退避:2^retryCount * 1000ms
1509
2127
 
1510
- _this.clearBatchTimer = function () {
1511
- if (_this.batchTimer !== null) {
1512
- clearTimeout(_this.batchTimer);
1513
- _this.batchTimer = null;
1514
- }
1515
- };
1516
- /**
1517
- * @description 清空批量队列(包括 LocalStorage 中的数据)
1518
- */
2128
+ item._nextRetryTime = currentTime + Math.pow(2, retryCount) * 1000;
2129
+ existingKeys.add(key);
2130
+ return true;
2131
+ }); // 将失败的数据重新加入队列(添加到队列前面,优先重试)
1519
2132
 
2133
+ var retryQueue = __spreadArray(__spreadArray([], retryData), currentQueue); // 限制重试队列大小,避免内存溢出
1520
2134
 
1521
- _this.clearBatchQueue = function () {
1522
- _this.batchQueue = [];
1523
2135
 
1524
- _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
2136
+ var maxSize = _this.initConfig.batchMaxSize * 2;
2137
+ var trimmedQueue = retryQueue.length > maxSize ? retryQueue.slice(0, maxSize) : retryQueue; // 保存失败的数据到 LocalStorage,确保数据不丢失
2138
+
2139
+ _this.saveBatchQueueToStorage(trimmedQueue);
2140
+
2141
+ if (_this.initConfig.showLog) {
2142
+ _this.printLog("\u5DF2\u5C06 " + retryData.length + " \u6761\u6570\u636E\u52A0\u5165\u91CD\u8BD5\u961F\u5217");
2143
+ }
2144
+ };
2145
+ /**
2146
+ * 添加到批量队列
2147
+ * @param params 数据参数
2148
+ */
1525
2149
 
1526
- if (_this.initConfig.showLog) {
1527
- _this.printLog("批量队列已清空");
1528
- }
1529
- };
1530
- /**
1531
- * @description 设置自定义页面唯一标识
1532
- * @param pageKey 页面唯一标识,如果传入 null 或空字符串,则恢复自动生成
1533
- * @param autoUpdate 路由变化时是否自动更新(默认:false,使用自定义值后不再自动更新)
1534
- */
1535
2150
 
2151
+ _this.addToBatchQueue = function (params) {
2152
+ var _a = _this.initConfig,
2153
+ batchInterval = _a.batchInterval,
2154
+ batchMaxSize = _a.batchMaxSize; // 数据采样判断(在添加到队列前判断)
1536
2155
 
1537
- _this.setPageKey = function (pageKey, autoUpdate) {
1538
- if (autoUpdate === void 0) {
1539
- autoUpdate = false;
2156
+ if (!_this.shouldSample()) {
2157
+ if (_this.initConfig.showLog) {
2158
+ _this.printLog("数据已采样跳过(批量模式)");
1540
2159
  }
1541
2160
 
1542
- if (pageKey === null || pageKey === '') {
1543
- // 恢复自动生成
1544
- _this.useCustomPageKey = false;
1545
- var pathname = window.location.pathname;
1546
- _this.pageKey = pathname.replace(/\//g, "_").substring(1);
2161
+ return;
2162
+ } // 从 LocalStorage 获取当前队列
1547
2163
 
1548
- if (_this.initConfig.showLog) {
1549
- _this.printLog("\u9875\u9762\u6807\u8BC6\u5DF2\u6062\u590D\u81EA\u52A8\u751F\u6210: " + _this.pageKey);
1550
- }
1551
- } else {
1552
- _this.pageKey = pageKey;
1553
- _this.useCustomPageKey = !autoUpdate;
1554
2164
 
1555
- if (_this.initConfig.showLog) {
1556
- _this.printLog("\u9875\u9762\u6807\u8BC6\u5DF2\u8BBE\u7F6E\u4E3A: " + pageKey + ", \u81EA\u52A8\u66F4\u65B0: " + autoUpdate);
1557
- }
1558
- }
1559
- };
1560
- /**
1561
- * @description 获取当前页面唯一标识
1562
- * @returns 当前页面唯一标识
1563
- */
2165
+ var currentQueue = _this.getBatchQueueFromStorage(); // 添加新数据
1564
2166
 
1565
2167
 
1566
- _this.getPageKey = function () {
1567
- return _this.pageKey;
1568
- };
2168
+ currentQueue.push(params); // 保存到 LocalStorage
1569
2169
 
1570
- _this.onClickCallback = function (e) {
1571
- var _a;
1572
-
1573
- var target = e.target;
1574
- if (!((_a = target === null || target === void 0 ? void 0 : target.dataset) === null || _a === void 0 ? void 0 : _a.partKey)) return;
1575
- var position = [e.pageX, e.pageY];
1576
- var id = target.id;
1577
- var className = target.className;
1578
- var nodeName = target.nodeName;
1579
- var targetEle = {
1580
- id: id,
1581
- nodeName: nodeName,
1582
- className: className,
1583
- position: position
1584
- };
2170
+ _this.saveBatchQueueToStorage(currentQueue); // 如果队列达到最大数量,立即发送
1585
2171
 
1586
- var params = _this.getParams({
1587
- event: "WebClick",
1588
- desc: _this.eventDescMap["WebClick"],
1589
- itemKey: _this.getItemKey(target.dataset.partKey),
1590
- privateParamMap: {
1591
- targetEle: targetEle,
1592
- pointerType: e.pointerType,
1593
- currentUrl: _this.currentUrl,
1594
- elementSelector: _this.getDomSelector(target) || ""
1595
- }
1596
- });
1597
2172
 
1598
- return _this.sendData(params);
1599
- };
1600
- /**
1601
- * @description 路由触发事件
1602
- */
2173
+ if (currentQueue.length >= batchMaxSize) {
2174
+ _this.flushBatchQueue();
1603
2175
 
2176
+ return;
2177
+ } // 设置定时发送
1604
2178
 
1605
- _this.onPageViewCallback = function (e) {
1606
- var _a, _b; // 在路由变化前,先发送待发送的数据(避免被取消)
1607
2179
 
2180
+ if (!_this.batchTimer) {
2181
+ _this.batchTimer = window.setTimeout(function () {
2182
+ _this.flushBatchQueue();
1608
2183
 
1609
- if (_this.pendingRequests.length > 0 || _this.initConfig.batchSend && _this.batchQueue.length > 0) {
1610
- _this.flushPendingData();
1611
- }
2184
+ _this.batchTimer = null;
2185
+ }, batchInterval);
2186
+ }
2187
+ };
2188
+ /**
2189
+ * 从 LocalStorage 恢复批量队列
2190
+ */
1612
2191
 
1613
- var ORGIN = window.location.origin;
1614
2192
 
1615
- var params = _this.getParams({
1616
- event: "PageView",
1617
- desc: _this.eventDescMap["PageView"],
1618
- privateParamMap: {
1619
- currentUrl: _this.currentUrl,
1620
- 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
1621
- }
1622
- });
2193
+ _this.restoreBatchQueueFromStorage = function () {
2194
+ var batchQueue = _this.getBatchQueueFromStorage();
1623
2195
 
1624
- _this.currentUrl = window.location.href; // 如果使用自定义 pageKey,路由变化时不自动更新
2196
+ if (batchQueue.length > 0) {
2197
+ if (_this.initConfig.showLog) {
2198
+ _this.printLog("\u4ECE LocalStorage \u6062\u590D " + batchQueue.length + " \u6761\u5F85\u53D1\u9001\u6570\u636E");
2199
+ } // 恢复后立即尝试发送(如果达到条件)
1625
2200
 
1626
- if (!_this.useCustomPageKey) {
1627
- _this.pageKey = window.location.pathname.replace(/\//g, "_").substring(1);
1628
- }
1629
2201
 
1630
- _this.sendRetained(e.type);
2202
+ var batchMaxSize = _this.initConfig.batchMaxSize;
1631
2203
 
1632
- _this.sendData(params);
1633
- };
2204
+ if (batchQueue.length >= batchMaxSize) {
2205
+ _this.flushBatchQueue();
2206
+ } else {
2207
+ // 设置定时发送
2208
+ var batchInterval = _this.initConfig.batchInterval;
1634
2209
 
1635
- _this.getParams = function (_a) {
1636
- var event = _a.event,
1637
- desc = _a.desc,
1638
- _b = _a.privateParamMap,
1639
- privateParamMap = _b === void 0 ? {} : _b,
1640
- itemKey = _a.itemKey;
1641
- var business = _this.initConfig.business;
1642
- var pageWidth = window.innerWidth;
1643
- var pageHeight = window.innerHeight;
1644
- var screenWidth = window.screen.width;
1645
- var screenHeight = window.screen.height; // 过滤敏感数据
2210
+ if (!_this.batchTimer) {
2211
+ _this.batchTimer = window.setTimeout(function () {
2212
+ _this.flushBatchQueue();
1646
2213
 
1647
- var filteredBusiness = _this.filterSensitiveData(business || {});
2214
+ _this.batchTimer = null;
2215
+ }, batchInterval);
2216
+ }
2217
+ }
2218
+ }
2219
+ };
2220
+ /**
2221
+ * 添加到待发送请求队列
2222
+ * @param params 数据参数
2223
+ */
1648
2224
 
1649
- var filteredUserInfo = _this.filterSensitiveData(_this.userInfo || {});
1650
2225
 
1651
- var filteredPrivateParamMap = _this.filterSensitiveData(privateParamMap || {});
2226
+ _this.addToPendingRequests = function (params) {
2227
+ // 从 LocalStorage 获取当前队列
2228
+ var currentRequests = _this.getPendingRequestsFromStorage(); // 添加新数据
1652
2229
 
1653
- var filteredUrlParams = _this.filterSensitiveData(_this.getQueryValue() || {}); // 创建私有参数对象
1654
2230
 
2231
+ currentRequests.push(params); // 限制队列大小,防止内存溢出
1655
2232
 
1656
- var privateParamMapData = {
1657
- currentUrl: filteredPrivateParamMap.currentUrl || _this.currentUrl,
1658
- business: Object.assign({}, filteredBusiness, filteredPrivateParamMap.business || {}),
1659
- pageWidth: pageWidth,
1660
- pageHeight: pageHeight,
1661
- screenWidth: screenWidth,
1662
- screenHeight: screenHeight,
1663
- sdkVersion: _this.sdkVersion,
1664
- systemsInfo: _this.systemsInfo,
1665
- urlParams: filteredUrlParams,
1666
- userInfo: filteredUserInfo,
1667
- deviceId: _this.deviceId // 添加设备ID
2233
+ var maxSize = _this.initConfig.pendingRequestsMaxSize || _this.DEFAULT_PENDING_REQUESTS_MAX_SIZE;
1668
2234
 
1669
- }; // 添加其他可能的属性
2235
+ if (currentRequests.length > maxSize) {
2236
+ var trimmedRequests = currentRequests.slice(-maxSize);
1670
2237
 
1671
- if (filteredPrivateParamMap.targetEle) {
1672
- privateParamMapData.targetEle = filteredPrivateParamMap.targetEle;
2238
+ if (_this.initConfig.showLog) {
2239
+ _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");
1673
2240
  }
1674
2241
 
1675
- if (filteredPrivateParamMap.targetUrl) {
1676
- privateParamMapData.targetUrl = filteredPrivateParamMap.targetUrl;
1677
- }
2242
+ _this.savePendingRequestsToStorage(trimmedRequests);
2243
+ } else {
2244
+ _this.savePendingRequestsToStorage(currentRequests);
2245
+ }
2246
+ };
2247
+ /**
2248
+ * 从 LocalStorage 恢复待发送请求
2249
+ */
1678
2250
 
1679
- if (filteredPrivateParamMap.pointerType) {
1680
- privateParamMapData.pointerType = filteredPrivateParamMap.pointerType;
1681
- }
1682
2251
 
1683
- if (filteredPrivateParamMap.elementSelector) {
1684
- privateParamMapData.elementSelector = filteredPrivateParamMap.elementSelector;
1685
- }
2252
+ _this.restorePendingRequestsFromStorage = function () {
2253
+ var pendingRequests = _this.getPendingRequestsFromStorage();
1686
2254
 
1687
- if (filteredPrivateParamMap.retainedDuration) {
1688
- privateParamMapData.retainedDuration = filteredPrivateParamMap.retainedDuration;
1689
- }
2255
+ if (pendingRequests.length > 0) {
2256
+ if (_this.initConfig.showLog) {
2257
+ _this.printLog("\u4ECE LocalStorage \u6062\u590D " + pendingRequests.length + " \u6761\u5F85\u53D1\u9001\u8BF7\u6C42");
2258
+ } // 注意:恢复后不立即发送,避免重复
2259
+ // 数据会留在 LocalStorage 中,等待下次正常发送或页面卸载时发送
2260
+ // 这样可以避免与批量队列冲突
2261
+
2262
+ }
2263
+ };
2264
+ /**
2265
+ * 检查页面是否即将卸载
2266
+ * @returns 如果页面即将卸载返回 true,否则返回 false
2267
+ */
1690
2268
 
1691
- return {
1692
- event: event,
1693
- desc: desc,
1694
- itemKey: itemKey || _this.getItemKey(),
1695
- requestTime: _this.getTimeStamp(),
1696
- privateParamMap: privateParamMapData
1697
- };
1698
- };
1699
- /**
1700
- * 数据采样判断
1701
- * @returns 是否应该采样
1702
- */
1703
2269
 
2270
+ _this.isPageUnloading = function () {
2271
+ return document.visibilityState === "hidden";
2272
+ };
2273
+ /**
2274
+ * 使用 sendBeacon 发送数据(页面卸载时的备用方案)
2275
+ * @param params 数据参数
2276
+ * @param serverUrl 服务器地址
2277
+ * @param contentType 内容类型
2278
+ * @returns 是否发送成功
2279
+ */
1704
2280
 
1705
- _this.shouldSample = function () {
1706
- var sampleRate = _this.initConfig.sampleRate;
1707
- if (sampleRate >= 1) return true;
1708
- if (sampleRate <= 0) return false;
1709
- return Math.random() < sampleRate;
1710
- };
1711
- /**
1712
- * 批量发送数据
1713
- */
1714
2281
 
2282
+ _this.sendWithBeacon = function (params, serverUrl, contentType) {
2283
+ try {
2284
+ var blob = new Blob([JSON.stringify(params)], {
2285
+ type: contentType || "application/json"
2286
+ });
2287
+ return navigator.sendBeacon(serverUrl, blob);
2288
+ } catch (e) {
2289
+ if (_this.initConfig.showLog) {
2290
+ _this.printLog("sendBeacon \u53D1\u9001\u5931\u8D25: " + e);
2291
+ }
2292
+
2293
+ return false;
2294
+ }
2295
+ };
2296
+ /**
2297
+ * 刷新待发送的单个请求(正常情况下的发送)
2298
+ * 注意:这个方法会直接使用 ajax 发送,避免通过 sendData 导致重复
2299
+ */
1715
2300
 
1716
- _this.flushBatchQueue = function () {
1717
- if (_this.batchQueue.length === 0) return;
1718
2301
 
1719
- var batchData = __spreadArray([], _this.batchQueue);
2302
+ _this.flushPendingRequests = function () {
2303
+ var pendingRequests = _this.getPendingRequestsFromStorage();
1720
2304
 
1721
- _this.batchQueue = []; // 从 LocalStorage 中移除已发送的数据
2305
+ if (pendingRequests.length === 0) {
2306
+ return;
2307
+ } // 清除 LocalStorage 中的待发送请求
1722
2308
 
1723
- _this.saveBatchQueueToStorage(); // 发送批量数据
1724
2309
 
2310
+ _this.savePendingRequestsToStorage([]); // 直接使用 ajax 发送每个请求,避免通过 sendData 导致重复
1725
2311
 
1726
- _this.sendBatchData(batchData);
1727
- };
1728
- /**
1729
- * 发送批量数据
1730
- * @param data 批量数据
1731
- */
1732
2312
 
2313
+ var _a = _this.initConfig,
2314
+ serverUrl = _a.serverUrl,
2315
+ sendTimeout = _a.sendTimeout,
2316
+ contentType = _a.contentType,
2317
+ showLog = _a.showLog,
2318
+ initHeader = _a.header;
2319
+ pendingRequests.forEach(function (params) {
2320
+ // 数据采样判断
2321
+ if (!_this.shouldSample()) {
2322
+ if (showLog) {
2323
+ _this.printLog("待发送请求已采样跳过");
2324
+ }
1733
2325
 
1734
- _this.sendBatchData = function (data) {
1735
- var _a = _this.initConfig,
1736
- serverUrl = _a.serverUrl,
1737
- contentType = _a.contentType,
1738
- showLog = _a.showLog;
2326
+ return;
2327
+ }
1739
2328
 
1740
2329
  if (showLog) {
1741
- _this.printLog("\u6279\u91CF\u53D1\u9001 " + data.length + " \u6761\u6570\u636E");
1742
-
1743
- data.forEach(function (item) {
1744
- return _this.printLog(item);
1745
- });
1746
- } // 这里可以根据实际需求决定是逐条发送还是打包发送
1747
- // 这里选择打包发送
2330
+ _this.printLog(params);
2331
+ } // 直接使用 ajax 发送
1748
2332
 
1749
2333
 
1750
2334
  _this.ajax({
2335
+ header: initHeader,
1751
2336
  url: serverUrl,
1752
2337
  type: "POST",
1753
- data: JSON.stringify({
1754
- events: data
1755
- }),
2338
+ data: JSON.stringify(params),
1756
2339
  contentType: contentType,
1757
2340
  credentials: false,
1758
- timeout: _this.initConfig.sendTimeout,
2341
+ timeout: sendTimeout,
1759
2342
  cors: true,
1760
2343
  success: function success() {
1761
- // 批量发送成功,数据已从队列中移除,无需额外操作
1762
- if (_this.initConfig.showLog) {
1763
- _this.printLog("\u6279\u91CF\u53D1\u9001\u6210\u529F: " + data.length + " \u6761\u6570\u636E");
2344
+ if (showLog) {
2345
+ _this.printLog("待发送请求发送成功");
1764
2346
  }
1765
2347
  },
1766
2348
  error: function error(err) {
1767
- var _a; // 批量发送失败,重新加入队列以便重试
1768
-
1769
-
1770
- _this.printLog("\u6279\u91CF\u53D1\u9001\u5931\u8D25: " + err + "\uFF0C\u6570\u636E\u5DF2\u91CD\u65B0\u52A0\u5165\u961F\u5217"); // 将失败的数据重新加入队列,避免数据丢失
1771
-
1772
-
1773
- (_a = _this.batchQueue).unshift.apply(_a, data); // 限制重试队列大小,避免内存溢出
1774
-
1775
-
1776
- if (_this.batchQueue.length > _this.initConfig.batchMaxSize * 2) {
1777
- _this.batchQueue = _this.batchQueue.slice(0, _this.initConfig.batchMaxSize);
1778
- } // 保存失败的数据到 LocalStorage
1779
-
1780
-
1781
- _this.saveBatchQueueToStorage();
2349
+ if (showLog) {
2350
+ _this.printLog("\u5F85\u53D1\u9001\u8BF7\u6C42\u53D1\u9001\u5931\u8D25\uFF08\u4E0D\u518D\u91CD\u8BD5\uFF09: " + err);
2351
+ }
1782
2352
  }
1783
2353
  });
1784
- };
1785
- /**
1786
- * 添加到批量队列
1787
- * @param params 数据参数
1788
- */
1789
-
2354
+ });
2355
+ };
2356
+ /**
2357
+ * 设置页面卸载监听器,确保数据发送
2358
+ */
1790
2359
 
1791
- _this.addToBatchQueue = function (params) {
1792
- var _a = _this.initConfig,
1793
- batchInterval = _a.batchInterval,
1794
- batchMaxSize = _a.batchMaxSize;
1795
2360
 
1796
- _this.batchQueue.push(params); // 保存到 LocalStorage
2361
+ _this.setupBeforeUnloadListener = function () {
2362
+ // 避免重复设置监听器
2363
+ if (_this.isUnloadListenerSetup) {
2364
+ return;
2365
+ }
1797
2366
 
2367
+ _this.isUnloadListenerSetup = true; // 使用 visibilitychange 事件(更可靠,支持页面跳转、切换标签页等场景)
1798
2368
 
1799
- _this.saveBatchQueueToStorage(); // 如果队列达到最大数量,立即发送
2369
+ document.addEventListener("visibilitychange", function () {
2370
+ if (_this.isPageUnloading()) {
2371
+ _this.flushPendingData();
2372
+ }
2373
+ }); // 使用 beforeunload 事件作为备用(页面关闭/刷新)
1800
2374
 
2375
+ window.addEventListener("beforeunload", function () {
2376
+ _this.flushPendingData();
2377
+ }); // 使用 pagehide 事件(更可靠,支持移动端)
1801
2378
 
1802
- if (_this.batchQueue.length >= batchMaxSize) {
1803
- _this.flushBatchQueue();
2379
+ window.addEventListener("pagehide", function () {
2380
+ _this.flushPendingData();
2381
+ });
2382
+ }; // 标记是否正在刷新待发送数据,避免重复发送
1804
2383
 
1805
- return;
1806
- } // 设置定时发送
1807
2384
 
2385
+ _this.isFlushingPendingData = false;
2386
+ /**
2387
+ * 刷新待发送数据(在页面卸载/跳转时调用)
2388
+ */
1808
2389
 
1809
- if (!_this.batchTimer) {
1810
- _this.batchTimer = window.setTimeout(function () {
1811
- _this.flushBatchQueue();
2390
+ _this.flushPendingData = function () {
2391
+ // 如果正在刷新,避免重复执行
2392
+ if (_this.isFlushingPendingData) {
2393
+ return;
2394
+ } // 页面卸载时停止定时器
1812
2395
 
1813
- _this.batchTimer = null;
1814
- }, batchInterval);
1815
- }
1816
- };
1817
- /**
1818
- * 从 LocalStorage 恢复批量队列
1819
- */
1820
2396
 
2397
+ _this.stopPageDurationTimer(); // 收集所有待发送的数据
1821
2398
 
1822
- _this.restoreBatchQueueFromStorage = function () {
1823
- try {
1824
- var storedQueue = _this.getLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY);
1825
2399
 
1826
- if (storedQueue) {
1827
- var parsedQueue = JSON.parse(storedQueue);
2400
+ var allPendingData = []; // 如果有批量队列,添加到待发送列表
1828
2401
 
1829
- if (Array.isArray(parsedQueue) && parsedQueue.length > 0) {
1830
- _this.batchQueue = parsedQueue;
2402
+ var batchQueue = _this.getBatchQueueFromStorage();
1831
2403
 
1832
- if (_this.initConfig.showLog) {
1833
- _this.printLog("\u4ECE LocalStorage \u6062\u590D " + parsedQueue.length + " \u6761\u5F85\u53D1\u9001\u6570\u636E");
1834
- } // 恢复后立即尝试发送(如果达到条件)
2404
+ if (batchQueue.length > 0) {
2405
+ allPendingData.push.apply(allPendingData, batchQueue);
2406
+ } // 如果有待发送的单个请求,也添加到列表
1835
2407
 
1836
2408
 
1837
- var batchMaxSize = _this.initConfig.batchMaxSize;
2409
+ var pendingRequests = _this.getPendingRequestsFromStorage();
1838
2410
 
1839
- if (_this.batchQueue.length >= batchMaxSize) {
1840
- _this.flushBatchQueue();
1841
- } else {
1842
- // 设置定时发送
1843
- var batchInterval = _this.initConfig.batchInterval;
2411
+ if (pendingRequests.length > 0) {
2412
+ allPendingData.push.apply(allPendingData, pendingRequests);
2413
+ }
1844
2414
 
1845
- if (!_this.batchTimer) {
1846
- _this.batchTimer = window.setTimeout(function () {
1847
- _this.flushBatchQueue();
2415
+ if (allPendingData.length === 0) {
2416
+ return;
2417
+ } // 标记正在刷新
1848
2418
 
1849
- _this.batchTimer = null;
1850
- }, batchInterval);
1851
- }
1852
- }
1853
- }
1854
- }
1855
- } catch (e) {
1856
- _this.printLog("\u6062\u590D\u6279\u91CF\u961F\u5217\u5931\u8D25: " + e); // 如果解析失败,清除损坏的数据
1857
2419
 
2420
+ _this.isFlushingPendingData = true; // 先保存到 LocalStorage,确保数据不丢失(在发送前保存)
1858
2421
 
1859
- _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
2422
+ try {
2423
+ if (_this.initConfig.batchSend) {
2424
+ _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, JSON.stringify(allPendingData));
2425
+ } else {
2426
+ _this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, JSON.stringify(allPendingData));
1860
2427
  }
1861
- };
1862
- /**
1863
- * 添加到待发送请求队列
1864
- * @param params 数据参数
1865
- */
1866
-
2428
+ } catch (e) {
2429
+ if (_this.initConfig.showLog) {
2430
+ _this.printLog("\u4FDD\u5B58\u5F85\u53D1\u9001\u8BF7\u6C42\u5230 LocalStorage \u5931\u8D25: " + e);
2431
+ }
2432
+ } // 使用 sendBeacon 发送数据(最可靠的方式)
1867
2433
 
1868
- _this.addToPendingRequests = function (params) {
1869
- _this.pendingRequests.push(params); // 限制队列大小,防止内存溢出
1870
2434
 
2435
+ if (navigator.sendBeacon && _this.initConfig.serverUrl) {
2436
+ try {
2437
+ // 如果只有一条数据,直接发送;否则批量发送
2438
+ var dataToSend = allPendingData.length === 1 ? allPendingData[0] : allPendingData;
2439
+ var blob = new Blob([JSON.stringify(dataToSend)], {
2440
+ type: _this.initConfig.contentType || "application/json"
2441
+ });
2442
+ var sent = navigator.sendBeacon(_this.initConfig.serverUrl, blob);
1871
2443
 
1872
- var maxSize = _this.initConfig.pendingRequestsMaxSize || _this.DEFAULT_PENDING_REQUESTS_MAX_SIZE;
2444
+ if (sent) {
2445
+ // 发送成功,清除所有 LocalStorage
2446
+ _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
1873
2447
 
1874
- if (_this.pendingRequests.length > maxSize) {
1875
- _this.pendingRequests = _this.pendingRequests.slice(-maxSize);
2448
+ _this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, "[]");
1876
2449
 
2450
+ if (_this.initConfig.showLog) {
2451
+ _this.printLog("\u9875\u9762\u5378\u8F7D\u65F6\u6210\u529F\u53D1\u9001 " + allPendingData.length + " \u6761\u6570\u636E");
2452
+ }
2453
+ } else {
2454
+ // sendBeacon 返回 false,数据已在 LocalStorage 中,等待下次恢复
2455
+ if (_this.initConfig.showLog) {
2456
+ _this.printLog("sendBeacon \u8FD4\u56DE false\uFF0C\u6570\u636E\u5DF2\u4FDD\u5B58\u5230 LocalStorage \u7B49\u5F85\u4E0B\u6B21\u6062\u590D");
2457
+ }
2458
+ }
2459
+ } catch (e) {
2460
+ // sendBeacon 失败,数据已在 LocalStorage 中,等待下次恢复
1877
2461
  if (_this.initConfig.showLog) {
1878
- _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");
2462
+ _this.printLog("\u9875\u9762\u5378\u8F7D\u65F6\u53D1\u9001\u6570\u636E\u5931\u8D25: " + e + "\uFF0C\u6570\u636E\u5DF2\u4FDD\u5B58\u5230 LocalStorage");
1879
2463
  }
2464
+ } finally {
2465
+ // 重置标记
2466
+ _this.isFlushingPendingData = false;
1880
2467
  }
1881
- };
1882
- /**
1883
- * LocalStorage 恢复待发送请求
1884
- */
1885
-
1886
-
1887
- _this.restorePendingRequestsFromStorage = function () {
1888
- try {
1889
- var storedRequests = _this.getLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY);
2468
+ } else {
2469
+ // 不支持 sendBeacon,数据已在 LocalStorage 中,等待下次恢复
2470
+ if (_this.initConfig.showLog) {
2471
+ _this.printLog("\u4E0D\u652F\u6301 sendBeacon\uFF0C\u6570\u636E\u5DF2\u4FDD\u5B58\u5230 LocalStorage \u7B49\u5F85\u4E0B\u6B21\u6062\u590D");
2472
+ } // 重置标记
1890
2473
 
1891
- if (storedRequests) {
1892
- var parsedRequests = JSON.parse(storedRequests);
1893
2474
 
1894
- if (Array.isArray(parsedRequests) && parsedRequests.length > 0) {
1895
- _this.pendingRequests = parsedRequests;
2475
+ _this.isFlushingPendingData = false;
2476
+ }
2477
+ };
2478
+ /**
2479
+ * 发送数据通用函数
2480
+ */
1896
2481
 
1897
- if (_this.initConfig.showLog) {
1898
- _this.printLog("\u4ECE LocalStorage \u6062\u590D " + parsedRequests.length + " \u6761\u5F85\u53D1\u9001\u8BF7\u6C42");
1899
- } // 恢复后立即尝试发送
1900
2482
 
2483
+ _this.sendData = function (params, header) {
2484
+ // 数据采样判断
2485
+ if (!_this.shouldSample()) {
2486
+ return Promise.resolve({
2487
+ success: true,
2488
+ message: "数据已采样跳过"
2489
+ });
2490
+ }
1901
2491
 
1902
- if (_this.pendingRequests.length > 0) {
1903
- _this.flushPendingRequests();
1904
- }
1905
- }
1906
- }
1907
- } catch (e) {
1908
- _this.printLog("\u6062\u590D\u5F85\u53D1\u9001\u8BF7\u6C42\u5931\u8D25: " + e); // 如果解析失败,清除损坏的数据
2492
+ var _a = _this.initConfig,
2493
+ serverUrl = _a.serverUrl,
2494
+ sendTimeout = _a.sendTimeout,
2495
+ contentType = _a.contentType,
2496
+ showLog = _a.showLog,
2497
+ initHeader = _a.header,
2498
+ batchSend = _a.batchSend,
2499
+ sendMethod = _a.sendMethod;
2500
+ if (!!showLog) _this.printLog(params); // 如果启用批量发送
2501
+
2502
+ if (batchSend) {
2503
+ _this.addToBatchQueue(params);
2504
+
2505
+ return Promise.resolve({
2506
+ success: true,
2507
+ message: "已添加到批量队列"
2508
+ });
2509
+ } // 判断是否使用 sendBeacon
1909
2510
 
1910
2511
 
1911
- _this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, "[]");
1912
- }
1913
- };
1914
- /**
1915
- * 检查页面是否即将卸载
1916
- * @returns 如果页面即将卸载返回 true,否则返回 false
1917
- */
2512
+ var shouldUseBeacon = _this.shouldUseBeacon(sendMethod, header, initHeader); // 如果使用 sendBeacon
1918
2513
 
1919
2514
 
1920
- _this.isPageUnloading = function () {
1921
- return document.visibilityState === "hidden";
1922
- };
1923
- /**
1924
- * 使用 sendBeacon 发送数据(页面卸载时的备用方案)
1925
- * @param params 数据参数
1926
- * @param serverUrl 服务器地址
1927
- * @param contentType 内容类型
1928
- * @returns 是否发送成功
1929
- */
2515
+ if (shouldUseBeacon) {
2516
+ // 检查页面是否即将卸载,如果是,直接使用 sendBeacon 发送,避免被取消
2517
+ if (_this.isPageUnloading()) {
2518
+ var sent = _this.sendWithBeacon(params, serverUrl, contentType);
1930
2519
 
2520
+ if (sent) {
2521
+ return Promise.resolve({
2522
+ success: true,
2523
+ message: "页面卸载时发送成功"
2524
+ });
2525
+ } else {
2526
+ // sendBeacon 返回 false,添加到待发送队列
2527
+ _this.addToPendingRequests(params);
1931
2528
 
1932
- _this.sendWithBeacon = function (params, serverUrl, contentType) {
1933
- try {
1934
- var blob = new Blob([JSON.stringify(params)], {
1935
- type: contentType || "application/json"
1936
- });
1937
- return navigator.sendBeacon(serverUrl, blob);
1938
- } catch (e) {
1939
- if (_this.initConfig.showLog) {
1940
- _this.printLog("sendBeacon \u53D1\u9001\u5931\u8D25: " + e);
2529
+ return Promise.resolve({
2530
+ success: true,
2531
+ message: "已添加到待发送队列"
2532
+ });
1941
2533
  }
1942
-
1943
- return false;
1944
- }
1945
- };
1946
- /**
1947
- * 刷新待发送的单个请求(正常情况下的发送)
1948
- * 注意:这个方法会直接发送,不会再次添加到 pendingRequests,避免循环
1949
- */
2534
+ } // 正常情况使用 sendBeacon
1950
2535
 
1951
2536
 
1952
- _this.flushPendingRequests = function () {
1953
- if (_this.pendingRequests.length === 0) {
1954
- return;
1955
- }
2537
+ return _this.sendBeacon({
2538
+ contentType: contentType,
2539
+ url: serverUrl,
2540
+ data: params
2541
+ }).catch(function (err) {
2542
+ // sendBeacon 失败,添加到待发送队列,避免数据丢失
2543
+ _this.addToPendingRequests(params);
1956
2544
 
1957
- var requestsToSend = __spreadArray([], _this.pendingRequests);
2545
+ return Promise.resolve({
2546
+ success: true,
2547
+ message: "sendBeacon 失败,已添加到待发送队列"
2548
+ });
2549
+ });
2550
+ } else {
2551
+ // 使用 XMLHttpRequest 发送
2552
+ return new Promise(function (resolve, reject) {
2553
+ // 如果页面即将卸载且配置为 auto,尝试使用 sendBeacon 作为备用
2554
+ if (_this.isPageUnloading() && sendMethod === 'auto' && _this.isSupportBeaconSend() && !header && !initHeader) {
2555
+ var sent = _this.sendWithBeacon(params, serverUrl, contentType);
1958
2556
 
1959
- _this.pendingRequests = []; // 清除 LocalStorage 中的待发送请求
2557
+ if (sent) {
2558
+ resolve({
2559
+ success: true,
2560
+ message: "页面卸载时使用 sendBeacon 发送成功"
2561
+ });
2562
+ return;
2563
+ } // sendBeacon 失败,继续使用 XMLHttpRequest
1960
2564
 
1961
- _this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, "[]"); // 尝试发送每个请求(使用 sendData,但如果失败不再添加到 pendingRequests,避免循环)
2565
+ }
1962
2566
 
2567
+ _this.ajax({
2568
+ header: header || initHeader,
2569
+ url: serverUrl,
2570
+ type: "POST",
2571
+ data: JSON.stringify(params),
2572
+ contentType: contentType,
2573
+ credentials: false,
2574
+ timeout: sendTimeout,
2575
+ cors: true,
2576
+ success: function success(res) {
2577
+ return resolve({
2578
+ success: true,
2579
+ data: res
2580
+ });
2581
+ },
2582
+ error: function error(err, status) {
2583
+ // 如果请求失败且页面即将卸载且配置为 auto,尝试使用 sendBeacon
2584
+ if (_this.isPageUnloading() && sendMethod === 'auto' && _this.isSupportBeaconSend() && !header && !initHeader) {
2585
+ var sent = _this.sendWithBeacon(params, serverUrl, contentType);
2586
+
2587
+ if (sent) {
2588
+ resolve({
2589
+ success: true,
2590
+ message: "XMLHttpRequest 失败,已使用 sendBeacon 发送"
2591
+ });
2592
+ return;
2593
+ }
2594
+ }
1963
2595
 
1964
- requestsToSend.forEach(function (params) {
1965
- // 直接调用 sendData,但不处理失败情况(避免循环)
1966
- // 如果失败,数据会丢失,但这是可接受的,因为我们已经尝试过了
1967
- _this.sendData(params).catch(function (err) {
1968
- if (_this.initConfig.showLog) {
1969
- _this.printLog("\u5F85\u53D1\u9001\u8BF7\u6C42\u53D1\u9001\u5931\u8D25\uFF08\u4E0D\u518D\u91CD\u8BD5\uFF09: " + err);
2596
+ reject({
2597
+ success: false,
2598
+ message: String(err),
2599
+ code: status
2600
+ });
1970
2601
  }
1971
2602
  });
1972
2603
  });
1973
- };
1974
- /**
1975
- * 保存批量队列到 LocalStorage
1976
- */
1977
-
2604
+ }
2605
+ };
2606
+ /**
2607
+ * @description 判断是否应该使用 sendBeacon
2608
+ * @param sendMethod 配置的发送方式
2609
+ * @param header 自定义 header
2610
+ * @param initHeader 初始化配置的 header
2611
+ * @returns 是否使用 sendBeacon
2612
+ */
1978
2613
 
1979
- _this.saveBatchQueueToStorage = function () {
1980
- try {
1981
- var queueString = JSON.stringify(_this.batchQueue); // 检查存储大小,避免超出 LocalStorage 限制(通常 5-10MB)
1982
- // 如果队列过大,只保留最新的数据
1983
2614
 
1984
- if (queueString.length > _this.MAX_STORAGE_SIZE) {
1985
- var maxItems = Math.floor(_this.batchQueue.length * 0.8); // 保留 80%
2615
+ _this.shouldUseBeacon = function (sendMethod, header, initHeader) {
2616
+ // 如果配置为 xhr,不使用 beacon
2617
+ if (sendMethod === 'xhr') {
2618
+ return false;
2619
+ } // 如果配置为 beacon,检查是否支持
1986
2620
 
1987
- _this.batchQueue = _this.batchQueue.slice(-maxItems);
1988
2621
 
1989
- _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");
1990
- }
2622
+ if (sendMethod === 'beacon') {
2623
+ return _this.isSupportBeaconSend() === true;
2624
+ } // 如果配置为 auto(默认),使用原有逻辑
2625
+ // 只有在支持 sendBeacon 且没有自定义 header 时才使用
1991
2626
 
1992
- _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, JSON.stringify(_this.batchQueue));
1993
- } catch (e) {
1994
- // LocalStorage 可能已满或不可用
1995
- _this.printLog("\u4FDD\u5B58\u6279\u91CF\u961F\u5217\u5230 LocalStorage \u5931\u8D25: " + e);
1996
- }
1997
- };
1998
- /**
1999
- * 设置页面卸载监听器,确保数据发送
2000
- */
2001
2627
 
2628
+ return _this.isSupportBeaconSend() === true && !header && !initHeader;
2629
+ };
2630
+ /**
2631
+ * @description 留存时长上报
2632
+ * @param type
2633
+ */
2002
2634
 
2003
- _this.setupBeforeUnloadListener = function () {
2004
- // 避免重复设置监听器
2005
- if (_this.isUnloadListenerSetup) {
2006
- return;
2007
- }
2008
2635
 
2009
- _this.isUnloadListenerSetup = true; // 使用 visibilitychange 事件(更可靠,支持页面跳转、切换标签页等场景)
2636
+ _this.sendRetained = function (type) {
2637
+ var params = _this.getParams({
2638
+ event: "PageRetained",
2639
+ desc: _this.eventDescMap["PageRetained"]
2640
+ });
2010
2641
 
2011
- document.addEventListener("visibilitychange", function () {
2012
- if (_this.isPageUnloading()) {
2013
- _this.flushPendingData();
2014
- }
2015
- }); // 使用 beforeunload 事件作为备用(页面关闭/刷新)
2642
+ if (["beforeunload", "pushState", "replaceState", "hashchange", "popstate"].indexOf(type) >= 0) {
2643
+ var __time = _this.getCookie("retainedStartTime");
2016
2644
 
2017
- window.addEventListener("beforeunload", function () {
2018
- _this.flushPendingData();
2019
- }); // 使用 pagehide 事件(更可靠,支持移动端)
2645
+ var retainedStartTime = __time ? +__time : _this.getTimeStamp();
2020
2646
 
2021
- window.addEventListener("pagehide", function () {
2022
- _this.flushPendingData();
2647
+ var retainedData = __assign(__assign({}, params), {
2648
+ privateParamMap: __assign(__assign({}, params.privateParamMap), {
2649
+ retainedDuration: Math.max(params.requestTime - retainedStartTime, 0)
2650
+ })
2023
2651
  });
2024
- };
2025
- /**
2026
- * 刷新待发送数据(在页面卸载/跳转时调用)
2027
- */
2028
-
2029
-
2030
- _this.flushPendingData = function () {
2031
- // 收集所有待发送的数据
2032
- var allPendingData = []; // 如果有批量队列,添加到待发送列表
2033
-
2034
- if (_this.batchQueue.length > 0) {
2035
- allPendingData.push.apply(allPendingData, _this.batchQueue);
2036
- } // 如果有待发送的单个请求,也添加到列表
2037
2652
 
2653
+ _this.sendData(retainedData);
2038
2654
 
2039
- if (_this.pendingRequests.length > 0) {
2040
- allPendingData.push.apply(allPendingData, _this.pendingRequests);
2041
- }
2655
+ _this.setCookie("retainedStartTime", _this.getTimeStamp());
2656
+ }
2657
+ };
2658
+ /**
2659
+ * @description 用户主动上报页面停留时长
2660
+ * @param duration 自定义停留时长(毫秒),如果不传则自动计算从页面加载(或上次调用)到当前的时长
2661
+ * @param options 可选参数,包括自定义描述、业务参数等
2662
+ * @param resetStartTime 是否重置起始时间,默认 true(手动上报后重置,定时上报不重置)
2663
+ * @returns Promise<TrackingResponse> 上报结果
2664
+ */
2042
2665
 
2043
- if (allPendingData.length === 0) {
2044
- return;
2045
- } // 使用 sendBeacon 发送数据(最可靠的方式)
2046
2666
 
2667
+ _this.trackPageDuration = function (duration, options, resetStartTime) {
2668
+ if (resetStartTime === void 0) {
2669
+ resetStartTime = true;
2670
+ } // 计算停留时长
2047
2671
 
2048
- if (navigator.sendBeacon && _this.initConfig.serverUrl) {
2049
- try {
2050
- // 如果只有一条数据,直接发送;否则批量发送
2051
- var dataToSend = allPendingData.length === 1 ? allPendingData[0] : {
2052
- events: allPendingData
2053
- };
2054
- var blob = new Blob([JSON.stringify(dataToSend)], {
2055
- type: _this.initConfig.contentType || "application/json"
2056
- });
2057
- var sent = navigator.sendBeacon(_this.initConfig.serverUrl, blob);
2058
2672
 
2059
- if (sent) {
2060
- // 发送成功,清除所有队列
2061
- _this.batchQueue = [];
2062
- _this.pendingRequests = [];
2673
+ var retainedDuration;
2063
2674
 
2064
- _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
2675
+ if (duration !== undefined && duration !== null) {
2676
+ // 使用自定义时长
2677
+ retainedDuration = Math.max(duration, 0);
2678
+ } else {
2679
+ // 自动计算时长
2680
+ var __time = _this.getCookie("retainedStartTime");
2065
2681
 
2066
- if (_this.initConfig.showLog) {
2067
- _this.printLog("\u9875\u9762\u5378\u8F7D\u65F6\u6210\u529F\u53D1\u9001 " + allPendingData.length + " \u6761\u6570\u636E");
2068
- }
2069
- } else {
2070
- // sendBeacon 返回 false,保存到 LocalStorage(批量模式)
2071
- if (_this.initConfig.batchSend && _this.batchQueue.length > 0) {
2072
- _this.saveBatchQueueToStorage();
2073
- } // 保存 pendingRequests 到 LocalStorage(如果支持)
2074
-
2075
-
2076
- if (_this.pendingRequests.length > 0) {
2077
- try {
2078
- _this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, JSON.stringify(_this.pendingRequests));
2079
- } catch (e) {
2080
- // LocalStorage 可能已满或不可用
2081
- if (_this.initConfig.showLog) {
2082
- _this.printLog("\u4FDD\u5B58\u5F85\u53D1\u9001\u8BF7\u6C42\u5230 LocalStorage \u5931\u8D25: " + e);
2083
- }
2084
- }
2085
- }
2086
- }
2087
- } catch (e) {
2088
- // sendBeacon 失败,保存到 LocalStorage(批量模式)
2089
- if (_this.initConfig.batchSend && _this.batchQueue.length > 0) {
2090
- _this.saveBatchQueueToStorage();
2091
- } // 保存 pendingRequests 到 LocalStorage(如果支持)
2682
+ var retainedStartTime = __time ? +__time : _this.getTimeStamp();
2092
2683
 
2684
+ var currentTime = _this.getTimeStamp();
2093
2685
 
2094
- if (_this.pendingRequests.length > 0) {
2095
- try {
2096
- _this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, JSON.stringify(_this.pendingRequests));
2097
- } catch (e) {// LocalStorage 可能已满或不可用
2098
- }
2099
- }
2686
+ retainedDuration = Math.max(currentTime - retainedStartTime, 0);
2687
+ } // 构建参数
2100
2688
 
2101
- if (_this.initConfig.showLog) {
2102
- _this.printLog("\u9875\u9762\u5378\u8F7D\u65F6\u53D1\u9001\u6570\u636E\u5931\u8D25: " + e);
2103
- }
2104
- }
2105
- } else {
2106
- // 不支持 sendBeacon,保存到 LocalStorage(批量模式)
2107
- if (_this.initConfig.batchSend && _this.batchQueue.length > 0) {
2108
- _this.saveBatchQueueToStorage();
2109
- } // 保存 pendingRequests 到 LocalStorage(如果支持)
2110
2689
 
2690
+ var desc = (options === null || options === void 0 ? void 0 : options.desc) || _this.eventDescMap["PageRetained"];
2691
+ var pageKey = (options === null || options === void 0 ? void 0 : options.pageKey) || _this.pageKey;
2692
+ var business = (options === null || options === void 0 ? void 0 : options.business) || {};
2693
+ var header = options === null || options === void 0 ? void 0 : options.header;
2111
2694
 
2112
- if (_this.pendingRequests.length > 0) {
2113
- try {
2114
- _this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, JSON.stringify(_this.pendingRequests));
2115
- } catch (e) {// LocalStorage 可能已满或不可用
2116
- }
2117
- }
2695
+ var params = _this.getParams({
2696
+ event: "PageRetained",
2697
+ desc: desc,
2698
+ itemKey: _this.getItemKey(undefined, pageKey),
2699
+ privateParamMap: {
2700
+ business: business,
2701
+ retainedDuration: retainedDuration
2118
2702
  }
2119
- };
2120
- /**
2121
- * 发送数据通用函数
2122
- */
2703
+ }); // 上报数据
2123
2704
 
2124
2705
 
2125
- _this.sendData = function (params, header) {
2126
- // 数据采样判断
2127
- if (!_this.shouldSample()) {
2128
- return Promise.resolve({
2129
- success: true,
2130
- message: "数据已采样跳过"
2131
- });
2132
- }
2133
-
2134
- var _a = _this.initConfig,
2135
- serverUrl = _a.serverUrl,
2136
- sendTimeout = _a.sendTimeout,
2137
- contentType = _a.contentType,
2138
- showLog = _a.showLog,
2139
- initHeader = _a.header,
2140
- batchSend = _a.batchSend;
2141
- if (!!showLog) _this.printLog(params); // 如果启用批量发送
2706
+ var result = _this.sendData(params, header); // 根据 resetStartTime 参数决定是否重置起始时间
2707
+ // 手动上报后重置起始时间,定时上报不重置(累积计算)
2142
2708
 
2143
- if (batchSend) {
2144
- _this.addToBatchQueue(params);
2145
2709
 
2146
- return Promise.resolve({
2147
- success: true,
2148
- message: "已添加到批量队列"
2149
- });
2150
- } // 如果使用 sendBeacon 且没有自定义 header
2710
+ if (resetStartTime) {
2711
+ _this.setCookie("retainedStartTime", _this.getTimeStamp());
2712
+ }
2151
2713
 
2714
+ return result;
2715
+ };
2716
+ /**
2717
+ * @description 启动定时上报页面停留时长的定时器
2718
+ */
2152
2719
 
2153
- if (_this.isSupportBeaconSend() === true && !header && !initHeader) {
2154
- // 检查页面是否即将卸载,如果是,直接使用 sendBeacon 发送,避免被取消
2155
- if (_this.isPageUnloading()) {
2156
- var sent = _this.sendWithBeacon(params, serverUrl, contentType);
2157
2720
 
2158
- if (sent) {
2159
- return Promise.resolve({
2160
- success: true,
2161
- message: "页面卸载时发送成功"
2162
- });
2163
- } else {
2164
- // sendBeacon 返回 false,添加到待发送队列
2165
- _this.addToPendingRequests(params);
2721
+ _this.startPageDurationTimer = function () {
2722
+ // 先停止现有的定时器(避免重复启动)
2723
+ _this.stopPageDurationTimer();
2166
2724
 
2167
- return Promise.resolve({
2168
- success: true,
2169
- message: "已添加到待发送队列"
2170
- });
2171
- }
2172
- } // 正常情况使用 sendBeacon
2725
+ var interval = _this.initConfig.pageDurationInterval || 30000; // 检查间隔时间是否有效
2173
2726
 
2727
+ if (interval <= 0) {
2728
+ if (_this.initConfig.showLog) {
2729
+ _this.printLog("定时上报间隔时间无效,已禁用定时上报");
2730
+ }
2174
2731
 
2175
- return _this.sendBeacon({
2176
- contentType: contentType,
2177
- url: serverUrl,
2178
- data: params
2179
- }).catch(function (err) {
2180
- // sendBeacon 失败,添加到待发送队列,避免数据丢失
2181
- _this.addToPendingRequests(params);
2732
+ return;
2733
+ } // 启动定时器
2182
2734
 
2183
- return Promise.resolve({
2184
- success: true,
2185
- message: "sendBeacon 失败,已添加到待发送队列"
2186
- });
2187
- });
2188
- } else {
2189
- // 使用 XMLHttpRequest 发送
2190
- return new Promise(function (resolve, reject) {
2191
- // 如果页面即将卸载,也尝试使用 sendBeacon 作为备用
2192
- if (_this.isPageUnloading() && _this.isSupportBeaconSend() && !header && !initHeader) {
2193
- var sent = _this.sendWithBeacon(params, serverUrl, contentType);
2194
-
2195
- if (sent) {
2196
- resolve({
2197
- success: true,
2198
- message: "页面卸载时使用 sendBeacon 发送成功"
2199
- });
2200
- return;
2201
- } // sendBeacon 失败,继续使用 XMLHttpRequest
2202
2735
 
2736
+ _this.pageDurationTimer = window.setInterval(function () {
2737
+ // 只在页面可见时上报(避免后台上报)
2738
+ if (document.visibilityState === "visible") {
2739
+ // 定时上报:retainedDuration 直接使用上报间隔时间,数据工程师会清洗数据
2740
+ _this.trackPageDuration(interval, {
2741
+ desc: "定时上报页面停留时长",
2742
+ business: {
2743
+ reportType: "interval",
2744
+ interval: interval
2745
+ }
2746
+ }, true).catch(function (err) {
2747
+ if (_this.initConfig.showLog) {
2748
+ _this.printLog("\u5B9A\u65F6\u4E0A\u62A5\u9875\u9762\u505C\u7559\u65F6\u957F\u5931\u8D25: " + err);
2203
2749
  }
2204
-
2205
- _this.ajax({
2206
- header: header || initHeader,
2207
- url: serverUrl,
2208
- type: "POST",
2209
- data: JSON.stringify(params),
2210
- contentType: contentType,
2211
- credentials: false,
2212
- timeout: sendTimeout,
2213
- cors: true,
2214
- success: function success(res) {
2215
- return resolve({
2216
- success: true,
2217
- data: res
2218
- });
2219
- },
2220
- error: function error(err, status) {
2221
- // 如果请求失败且页面即将卸载,尝试使用 sendBeacon
2222
- if (_this.isPageUnloading() && _this.isSupportBeaconSend() && !header && !initHeader) {
2223
- var sent = _this.sendWithBeacon(params, serverUrl, contentType);
2224
-
2225
- if (sent) {
2226
- resolve({
2227
- success: true,
2228
- message: "XMLHttpRequest 失败,已使用 sendBeacon 发送"
2229
- });
2230
- return;
2231
- }
2232
- }
2233
-
2234
- reject({
2235
- success: false,
2236
- message: String(err),
2237
- code: status
2238
- });
2239
- }
2240
- });
2241
2750
  });
2242
2751
  }
2243
- };
2244
- /**
2245
- * @description 留存时长上报
2246
- * @param type
2247
- */
2248
-
2752
+ }, interval);
2249
2753
 
2250
- _this.sendRetained = function (type) {
2251
- var params = _this.getParams({
2252
- event: "PageRetained",
2253
- desc: _this.eventDescMap["PageRetained"]
2254
- });
2255
-
2256
- if (["beforeunload", "pushState", "replaceState", "hashchange", "popstate"].indexOf(type) >= 0) {
2257
- var __time = _this.getCookie("retainedStartTime");
2258
-
2259
- var retainedStartTime = __time ? +__time : _this.getTimeStamp();
2754
+ if (_this.initConfig.showLog) {
2755
+ _this.printLog("\u5B9A\u65F6\u4E0A\u62A5\u9875\u9762\u505C\u7559\u65F6\u957F\u5DF2\u542F\u52A8\uFF0C\u95F4\u9694: " + interval + "ms");
2756
+ }
2757
+ };
2758
+ /**
2759
+ * @description 停止定时上报页面停留时长的定时器
2760
+ */
2260
2761
 
2261
- var retainedData = __assign(__assign({}, params), {
2262
- privateParamMap: __assign(__assign({}, params.privateParamMap), {
2263
- retainedDuration: Math.max(params.requestTime - retainedStartTime, 0)
2264
- })
2265
- });
2266
2762
 
2267
- _this.sendData(retainedData);
2763
+ _this.stopPageDurationTimer = function () {
2764
+ if (_this.pageDurationTimer !== null) {
2765
+ clearInterval(_this.pageDurationTimer);
2766
+ _this.pageDurationTimer = null;
2268
2767
 
2269
- _this.setCookie("retainedStartTime", _this.getTimeStamp());
2768
+ if (_this.initConfig.showLog) {
2769
+ _this.printLog("定时上报页面停留时长已停止");
2270
2770
  }
2271
- };
2272
- /**
2273
- * @description 获取 itemKey
2274
- * @param {[string]} partkey [控件/自定义事件的唯一标识]
2275
- * @return {[string]}
2276
- */
2277
-
2278
-
2279
- _this.getItemKey = function (partkey, pageKey) {
2280
- var appKey = _this.initConfig.appKey;
2281
- var keys = [appKey, (pageKey || _this.pageKey).toString(), partkey ? partkey.toString() : undefined].filter(function (key) {
2282
- return !!key;
2283
- });
2284
- return keys.reduce(function (str, key) {
2285
- return str + ("" + (str.length ? "." : "")) + key;
2286
- }, "");
2287
- };
2288
-
2289
- _this.sdkVersion = "1.2.0"; // sdk版本
2290
-
2291
- _this.initConfig = {
2292
- appKey: "",
2293
- platform: undefined,
2294
- showLog: false,
2295
- serverUrl: "",
2296
- autoTrack: false,
2297
- sendTimeout: 3000,
2298
- isTrackSinglePage: false,
2299
- contentType: "application/json",
2300
- business: {},
2301
- header: undefined,
2302
- sampleRate: 1,
2303
- batchSend: false,
2304
- batchInterval: 5000,
2305
- batchMaxSize: 10,
2306
- trackPartKeyClick: false,
2307
- pendingRequestsMaxSize: 50 // 待发送请求队列最大数量(防止内存溢出)
2308
-
2309
- }; // 系统信息
2310
-
2311
- _this.systemsInfo = {};
2312
- return _this;
2313
- }
2771
+ }
2772
+ };
2314
2773
  /**
2315
- * @description 添加单页面监听事件
2316
- * @param callback
2774
+ * @description 获取 itemKey
2775
+ * @param {[string]} partkey [控件/自定义事件的唯一标识]
2776
+ * @return {[string]}
2317
2777
  */
2318
2778
 
2319
2779
 
2320
- WebTracking.prototype.addSinglePageEvent = function (callback) {
2321
- var _this = this;
2322
-
2323
- var historyPushState = window.history.pushState;
2324
- var singlePageEvent = historyPushState ? "popstate" : "hashchange";
2325
- this.each(["pushState", "replaceState", singlePageEvent], function (historyType) {
2326
- _this.addEventListener(window, historyType, callback);
2780
+ _this.getItemKey = function (partkey, pageKey) {
2781
+ var appKey = _this.initConfig.appKey;
2782
+ var keys = [appKey, (pageKey || _this.pageKey).toString(), partkey ? partkey.toString() : undefined].filter(function (key) {
2783
+ return !!key;
2327
2784
  });
2785
+ return keys.reduce(function (str, key) {
2786
+ return str + ("" + (str.length ? "." : "")) + key;
2787
+ }, "");
2328
2788
  };
2329
2789
 
2330
- return WebTracking;
2331
- }(WebTrackingTools);
2790
+ _this.sdkVersion = "1.2.3"; // sdk版本
2791
+
2792
+ _this.initConfig = {
2793
+ appKey: "",
2794
+ platform: undefined,
2795
+ showLog: false,
2796
+ serverUrl: "",
2797
+ autoTrack: false,
2798
+ sendTimeout: 3000,
2799
+ isTrackSinglePage: false,
2800
+ contentType: "application/json",
2801
+ business: {},
2802
+ header: undefined,
2803
+ sampleRate: 1,
2804
+ batchSend: false,
2805
+ batchInterval: 5000,
2806
+ batchMaxSize: 10,
2807
+ trackPartKeyClick: false,
2808
+ pendingRequestsMaxSize: 50,
2809
+ autoTrackPageDurationInterval: false,
2810
+ pageDurationInterval: 30000,
2811
+ sendMethod: "auto" // 数据发送方式:auto(自动选择)、xhr(XMLHttpRequest)、beacon(sendBeacon)
2812
+
2813
+ }; // 系统信息
2814
+
2815
+ _this.systemsInfo = {};
2816
+ return _this;
2817
+ }
2818
+ /**
2819
+ * @description 添加单页面监听事件
2820
+ * @param callback
2821
+ */
2822
+
2823
+
2824
+ WebTracking.prototype.addSinglePageEvent = function (callback) {
2825
+ var _this = this;
2826
+
2827
+ var historyPushState = window.history.pushState;
2828
+ var singlePageEvent = historyPushState ? "popstate" : "hashchange";
2829
+ this.each(["pushState", "replaceState", singlePageEvent], function (historyType) {
2830
+ _this.addEventListener(window, historyType, callback);
2831
+ });
2832
+ };
2833
+
2834
+ return WebTracking;
2835
+ }(WebTrackingTools);
2332
2836
 
2333
- var index = new WebTracking();
2837
+ var index = new WebTracking();
2334
2838
 
2335
- return index;
2839
+ return index;
2336
2840
 
2337
2841
  })));