@fle-sdk/event-tracking-web 1.2.1 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js CHANGED
@@ -1,2056 +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
+ */
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";
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
+ */
211
+
212
+
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
+ }
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
+ }
251
+
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
+ }
268
+ }
269
+ }
270
+
271
+ return -1;
272
+ };
273
+
274
+ this.selector = function (el) {
275
+ var i = el.parentNode && 9 == el.parentNode.nodeType ? -1 : _this.getDomIndex(el);
276
+
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
+ };
283
+
284
+ this.getDomSelector = function (el, arr) {
285
+ if (!el || !el.parentNode || !el.parentNode.children) {
286
+ return false;
287
+ }
288
+
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
+ */
304
+
305
+
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);
339
+ } else {
340
+ date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
341
+ }
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
+ });
464
+
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
+ });
474
+
475
+ return filteredObj;
476
+ };
477
+ /**
478
+ * XSS过滤
479
+ * @param str 需要过滤的字符串
480
+ * @returns 过滤后的字符串
481
+ */
482
+
483
+
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;
572
+ }
573
+ };
574
+
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
+ }
591
+ };
592
+
593
+ errorTimer = window.setTimeout(function () {
594
+ abort();
595
+ }, para.timeout);
596
+
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
+ }
605
+
606
+ g.onreadystatechange = null;
607
+ g.onload = null;
608
+ }
609
+ } catch (e) {
610
+ g.onreadystatechange = null;
611
+ g.onload = null;
612
+ }
613
+ };
614
+
615
+ g.open(para.type || "GET", para.url, true);
616
+
617
+ try {
618
+ if (para.credentials) {
619
+ g.withCredentials = true;
620
+ }
621
+
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");
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();
650
+ } else {
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;
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;
715
+ }
716
+ }
717
+
718
+ return supported;
719
+ };
720
+ /**
721
+ * 节流函数
722
+ * @param func 需要节流的函数
723
+ * @param wait 等待时间
724
+ * @returns 节流后的函数
725
+ */
726
+
727
+
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;
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];
773
+ }
774
+
775
+ if (timeout) {
776
+ clearTimeout(timeout);
777
+ }
778
+
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
+ });
809
+ }
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
+ */
54
964
 
55
- function _typeof(obj) {
56
- "@babel/helpers - typeof";
57
965
 
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
- }
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
+ });
67
994
 
68
- return _typeof(obj);
69
- }
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
+ */
70
1008
 
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";
120
- }
121
1009
 
122
- if (wxVersion) {
123
- logMsg.push("WeChat " + wxVersion);
124
- }
1010
+ this.getPluginsFingerprint = function () {
1011
+ try {
1012
+ var plugins = [];
125
1013
 
126
- systemsInfo["client"] = logMsg.length ? logMsg.join(", ") : "Unknown";
127
- systemsInfo["platform"] = platform || _platform; // network type
1014
+ if (navigator.plugins) {
1015
+ for (var i = 0; i < navigator.plugins.length; i++) {
1016
+ var plugin = navigator.plugins[i];
128
1017
 
129
- var network = ua.toLowerCase().match(/ nettype\/([^ ]+)/g);
1018
+ if (plugin) {
1019
+ plugins.push(plugin.name + "|" + plugin.description + "|" + plugin.filename);
1020
+ }
1021
+ }
1022
+ }
130
1023
 
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
1024
+ return plugins.join(";");
1025
+ } catch (e) {
1026
+ return "error";
1027
+ }
1028
+ };
1029
+ /**
1030
+ * 检测localStorage支持
1031
+ * @returns 是否支持localStorage
1032
+ */
137
1033
 
138
1034
 
139
- systemsInfo["ua"] = ua;
140
- return systemsInfo;
141
- };
142
- /**
143
- * 监听事件
144
- * @param {Window | Element} target
145
- * @param {String} type
146
- * @param {Function} handler
147
- */
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
+ */
148
1049
 
149
1050
 
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
- */
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
+ */
165
1065
 
166
1066
 
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
- */
1067
+ this.hasIndexedDB = function () {
1068
+ return "indexedDB" in window && indexedDB !== null;
1069
+ };
1070
+ /**
1071
+ * 获取网络连接指纹
1072
+ * @returns 网络连接指纹字符串
1073
+ */
179
1074
 
180
1075
 
181
- this.rewriteHistory = function () {
182
- var history = window.history;
1076
+ this.getConnectionFingerprint = function () {
1077
+ try {
1078
+ var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
1079
+ if (!connection) return "not-supported"; // 只使用稳定的 effectiveType,不使用 downlink 和 rtt(会随网络状态变化导致指纹不一致)
183
1080
 
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
- };
1081
+ return connection.effectiveType || "unknown";
1082
+ } catch (e) {
1083
+ return "error";
1084
+ }
1085
+ };
1086
+ /**
1087
+ * 将指纹信息哈希为唯一ID
1088
+ * @param fingerprint 指纹信息
1089
+ * @returns 返回哈希后的设备ID
1090
+ */
202
1091
 
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
- */
211
1092
 
1093
+ this.hashFingerprint = function (fingerprint) {
1094
+ // 将指纹对象转换为字符串
1095
+ var fingerprintString = JSON.stringify(fingerprint, Object.keys(fingerprint).sort()); // 使用更强大的哈希算法生成ID
212
1096
 
213
- this.formatJsonString = function (obj) {
214
- try {
215
- return JSON.stringify(obj, null, " ");
216
- } catch (e) {
217
- return JSON.stringify(obj);
218
- }
219
- };
1097
+ var hash1 = 5381; // DJB2哈希算法的初始值
220
1098
 
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
- */
1099
+ var hash2 = 52711; // 第二个哈希的初始值
228
1100
 
229
- this.each = function (obj, iterator, context) {
230
- if (obj == null) {
231
- return false;
232
- }
1101
+ for (var i = 0; i < fingerprintString.length; i++) {
1102
+ var char = fingerprintString.charCodeAt(i);
1103
+ hash1 = (hash1 << 5) + hash1 + char; // hash1 * 33 + char
233
1104
 
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
- }
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
- }
1105
+ hash2 = (hash2 << 5) + hash2 + char; // hash2 * 33 + char
1106
+ } // 将两个32位哈希值转换为十六进制字符串并拼接,保持完整长度
251
1107
 
252
- return true;
253
- };
254
1108
 
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;
1109
+ var hash1Hex = (hash1 >>> 0).toString(16).padStart(8, '0'); // 32位 = 8个十六进制字符
260
1110
 
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
- }
268
- }
269
- }
1111
+ var hash2Hex = (hash2 >>> 0).toString(16).padStart(8, '0'); // 32位 = 8个十六进制字符
270
1112
 
271
- return -1;
272
- };
1113
+ var deviceId = "fp_" + hash1Hex + hash2Hex;
1114
+ return deviceId;
1115
+ };
1116
+ }
1117
+ /**
1118
+ * 打印log
1119
+ */
273
1120
 
274
- this.selector = function (el) {
275
- var i = el.parentNode && 9 == el.parentNode.nodeType ? -1 : _this.getDomIndex(el);
276
1121
 
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
- };
1122
+ WebTrackingTools.prototype.printLog = function () {
1123
+ var rest = [];
283
1124
 
284
- this.getDomSelector = function (el, arr) {
285
- if (!el || !el.parentNode || !el.parentNode.children) {
286
- return false;
287
- }
1125
+ for (var _i = 0; _i < arguments.length; _i++) {
1126
+ rest[_i] = arguments[_i];
1127
+ }
288
1128
 
289
- arr = arr && arr.join ? arr : [];
290
- var name = el.nodeName.toLowerCase();
1129
+ if (this.isObject(rest[0])) {
1130
+ rest[0] = this.formatJsonString(rest[0]);
1131
+ }
291
1132
 
292
- if (!el || name === "body" || 1 != el.nodeType) {
293
- arr.unshift("body");
294
- return arr.join(" > ");
295
- }
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
+ */
296
1144
 
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
1145
 
1146
+ WebTrackingTools.prototype.isObject = function (obj) {
1147
+ if (obj == null) {
1148
+ return false;
1149
+ } else {
1150
+ return toString.call(obj) == "[object Object]";
1151
+ }
1152
+ };
305
1153
 
306
- this.getCookie = function (name) {
307
- var nameEQ = name + "=";
308
- var ca = document.cookie.split(";");
1154
+ WebTrackingTools.prototype.isUndefined = function (obj) {
1155
+ return obj === void 0;
1156
+ };
309
1157
 
310
- for (var i = 0; i < ca.length; i++) {
311
- var c = ca[i];
1158
+ WebTrackingTools.prototype.isString = function (obj) {
1159
+ return toString.call(obj) == "[object String]";
1160
+ };
312
1161
 
313
- while (c.charAt(0) == " ") {
314
- c = c.substring(1, c.length);
315
- }
1162
+ WebTrackingTools.prototype.isDate = function (obj) {
1163
+ return toString.call(obj) == "[object Date]";
1164
+ };
316
1165
 
317
- if (c.indexOf(nameEQ) == 0) {
318
- return this._decodeURIComponent(c.substring(nameEQ.length, c.length));
319
- }
320
- }
1166
+ WebTrackingTools.prototype.isBoolean = function (obj) {
1167
+ return toString.call(obj) == "[object Boolean]";
1168
+ };
321
1169
 
322
- return null;
323
- };
1170
+ WebTrackingTools.prototype.isNumber = function (obj) {
1171
+ return toString.call(obj) == "[object Number]" && /[\d\.]+/.test(String(obj));
1172
+ };
324
1173
 
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 : "";
1174
+ WebTrackingTools.prototype.isElement = function (obj) {
1175
+ return !!(obj && obj.nodeType === 1);
1176
+ };
333
1177
 
334
- if (days !== 0) {
335
- var date = new Date();
1178
+ WebTrackingTools.prototype.isFunction = function (f) {
1179
+ if (!f) {
1180
+ return false;
1181
+ }
336
1182
 
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
- }
1183
+ var type = toString.call(f);
1184
+ return type == "[object Function]" || type == "[object AsyncFunction]";
1185
+ };
342
1186
 
343
- expires = "; expires=" + date.toUTCString();
344
- }
1187
+ WebTrackingTools.prototype.isJSONString = function (str) {
1188
+ if (!this.isString(str)) return false;
345
1189
 
346
- function getValid(data) {
347
- if (data) {
348
- return data;
349
- } else {
350
- return false;
351
- }
352
- }
1190
+ try {
1191
+ JSON.parse(str);
1192
+ } catch (e) {
1193
+ return false;
1194
+ }
353
1195
 
354
- var valid_name = "";
355
- var valid_value = "";
356
- var valid_domain = "";
1196
+ return true;
1197
+ };
357
1198
 
358
- if (name) {
359
- valid_name = getValid(name);
360
- }
1199
+ WebTrackingTools.prototype._decodeURIComponent = function (val) {
1200
+ var result = val;
361
1201
 
362
- if (value) {
363
- valid_value = getValid(value);
364
- }
1202
+ try {
1203
+ result = decodeURIComponent(val);
1204
+ } catch (e) {
1205
+ result = val;
1206
+ }
365
1207
 
366
- if (cdomain) {
367
- valid_domain = getValid(cdomain);
368
- }
1208
+ return result;
1209
+ };
369
1210
 
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
- */
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 = []; // 主域名一定会有两部分组成
379
1218
 
1219
+ urlItems.unshift(domainList.pop()); // 慢慢从后往前测试
380
1220
 
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
- */
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存在,则说明域名合法
393
1226
 
1227
+ if (keyR.test(document.cookie)) {
1228
+ document.cookie = cookie + ";expires=" + expiredTime;
1229
+ return mainHost;
1230
+ }
1231
+ }
394
1232
 
395
- this.setLocalStorage = function (key, value) {
396
- try {
397
- localStorage.setItem(key, value);
398
- } catch (e) {// 静默失败
399
- }
400
- };
1233
+ return domain;
1234
+ };
401
1235
 
402
- this.removeCookie = function (name) {
403
- _this.setCookie(name, "", -1);
404
- };
405
- /**
406
- * 获取当前时间戳
407
- * @return {number} [当前时间戳]
408
- */
1236
+ return WebTrackingTools;
1237
+ }();
409
1238
 
1239
+ var WebTracking =
1240
+ /** @class */
1241
+ function (_super) {
1242
+ __extends(WebTracking, _super);
410
1243
 
411
- this.getTimeStamp = function () {
412
- return new Date().getTime();
413
- };
414
- /**
415
- * 获取 UUID
416
- * @return {string} [UUID唯一值]
417
- */
1244
+ function WebTracking() {
1245
+ var _this = _super.call(this) || this; // 批量发送定时器
418
1246
 
419
1247
 
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
- */
1248
+ _this.batchTimer = null; // LocalStorage 存储 key
431
1249
 
1250
+ _this.BATCH_QUEUE_STORAGE_KEY = "web_tracking_batch_queue"; // 是否使用自定义 pageKey(如果为 true,路由变化时不会自动更新 pageKey)
432
1251
 
433
- this.getDistinctId = function () {
434
- var distinctId = _this.getCookie("distinctId");
1252
+ _this.useCustomPageKey = false; // 页面卸载监听器是否已设置
435
1253
 
436
- if (distinctId) return distinctId;
437
- distinctId = _this.uuid();
1254
+ _this.isUnloadListenerSetup = false; // 定时上报页面停留时长的定时器
438
1255
 
439
- _this.setCookie("distinctId", distinctId);
1256
+ _this.pageDurationTimer = null; // LocalStorage 存储 key(待发送请求)
440
1257
 
441
- return distinctId;
442
- };
443
- /**
444
- * 敏感字段过滤
445
- * @param obj 需要过滤的对象
446
- * @param sensitiveKeys 敏感字段列表
447
- * @returns 过滤后的对象
448
- */
1258
+ _this.PENDING_REQUESTS_STORAGE_KEY = "web_tracking_pending_requests"; // 待发送请求队列最大大小(默认值,可通过配置覆盖)
449
1259
 
1260
+ _this.DEFAULT_PENDING_REQUESTS_MAX_SIZE = 50; // LocalStorage 最大大小限制(4MB)
450
1261
 
451
- this.filterSensitiveData = function (obj, sensitiveKeys) {
452
- if (sensitiveKeys === void 0) {
453
- sensitiveKeys = ['password', 'token', 'secret', 'key'];
454
- }
1262
+ _this.MAX_STORAGE_SIZE = 4 * 1024 * 1024; // 用户信息
455
1263
 
456
- if (!_this.isObject(obj)) return obj;
457
- var filteredObj = {};
1264
+ _this.userInfo = null; // 当前路由
458
1265
 
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
- });
1266
+ _this.currentUrl = ""; // 页面唯一标识,取当前路由 window.location.pathname.replace(/\//g, '_').substr(1)
464
1267
 
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
- });
1268
+ _this.pageKey = ""; // 设备唯一标识
474
1269
 
475
- return filteredObj;
476
- };
477
- /**
478
- * XSS过滤
479
- * @param str 需要过滤的字符串
480
- * @returns 过滤后的字符串
481
- */
1270
+ _this.deviceId = ""; // 上传事件描述
1271
+
1272
+ _this.eventDescMap = {
1273
+ PageView: "Web 浏览页面",
1274
+ WebClick: "Web 元素点击",
1275
+ PageRetained: "Web 页面浏览时长",
1276
+ CustomTrack: "Web 自定义代码上报"
1277
+ };
1278
+ /**
1279
+ * @description 初始化函数
1280
+ * @param {object} InitParams [初始化参数]
1281
+ */
482
1282
 
1283
+ _this.init = function (initParams) {
1284
+ _this.preset(initParams);
483
1285
 
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
- */
1286
+ var pathname = window.location.pathname;
1287
+ _this.currentUrl = window.location.href; // 如果传入了自定义 pageKey,使用自定义值,否则自动生成
493
1288
 
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
+ }
494
1296
 
495
- this.getQueryValue = function () {
496
- var _href = decodeURI(window.location.href);
1297
+ _this.systemsInfo = _this.getSystemsInfo(initParams.platform); // 获取设备ID
497
1298
 
498
- var queryArr = _href.match(new RegExp("[?&][^?&]+=[^?&]+", "g")) || [];
499
- var paramsObj = {};
1299
+ _this.deviceId = _this.getDeviceId(); // 如果传入了 userInfo,设置用户信息
500
1300
 
501
- for (var i = 0; i < queryArr.length; i++) {
502
- var _item = queryArr[i].replace(/\?|\&/, "");
1301
+ if (initParams.userInfo && _this.isObject(initParams.userInfo)) {
1302
+ _this.userInfo = initParams.userInfo;
1303
+ }
503
1304
 
504
- var pair = _item.split("=");
1305
+ _this.setCookie("retainedStartTime", _this.getTimeStamp()); // 如果启用了批量发送,从 LocalStorage 恢复队列
505
1306
 
506
- paramsObj[pair[0]] = pair[1];
507
- }
508
1307
 
509
- return Object.keys(paramsObj).length > 0 ? paramsObj : null;
510
- };
511
- /**
512
- * Ajax请求方法
513
- * @param para Ajax请求参数
514
- * @returns 是否成功发起请求
515
- */
1308
+ if (_this.initConfig.batchSend) {
1309
+ _this.restoreBatchQueueFromStorage();
1310
+ } // 恢复待发送的单个请求
516
1311
 
517
1312
 
518
- this.ajax = function (para) {
519
- para.timeout = para.timeout || 30000;
520
- para.credentials = typeof para.credentials === "undefined" ? true : para.credentials;
1313
+ _this.restorePendingRequestsFromStorage(); // 无论是否启用批量发送,都需要监听页面卸载事件,确保数据发送
521
1314
 
522
- function getJSON(data) {
523
- if (!data) {
524
- return {};
525
- }
526
1315
 
527
- if (typeof data === 'string') {
528
- try {
529
- return JSON.parse(data);
530
- } catch (e) {
531
- return {};
532
- }
533
- }
1316
+ _this.setupBeforeUnloadListener(); // 如果启用了定时上报,启动定时器
534
1317
 
535
- if (_typeof(data) === 'object') {
536
- return data;
537
- }
538
1318
 
539
- return {};
540
- }
1319
+ if (_this.initConfig.autoTrackPageDurationInterval) {
1320
+ _this.startPageDurationTimer();
1321
+ }
1322
+ };
1323
+ /**
1324
+ * @description 预置参数
1325
+ * @param {object} PresetParams [预置参数]
1326
+ */
541
1327
 
542
- var g = _this.xhr(para.cors);
543
1328
 
544
- if (!g) {
545
- return false;
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;
1341
+ }
546
1342
  }
547
1343
 
548
- if (!para.type) {
549
- para.type = para.data ? "POST" : "GET";
550
- }
1344
+ _this.each(presetParams, function (val, key) {
1345
+ // 跳过 pageKey,因为已经单独处理
1346
+ if (key === 'pageKey') return;
551
1347
 
552
- var oldsuccess = para.success;
553
- var olderror = para.error;
554
- var errorTimer;
1348
+ if (_this.initConfig.hasOwnProperty(key)) {
1349
+ // 参数验证
1350
+ var validationResult = _this.validateConfigParam(String(key), val);
555
1351
 
556
- var abort = function abort() {
557
- try {
558
- if (_this.isObject(g) && g.abort) {
559
- g.abort();
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);
560
1356
  }
561
- } catch (error) {
562
- _this.printLog(error);
563
1357
  }
1358
+ });
1359
+ }
564
1360
 
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
- };
1361
+ if (!/^(((ht|f)tps?):\/\/)?[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-\(\)]*[\w@?^=%&/~+#-\(\)])?$/.test(_this.initConfig["serverUrl"])) {
1362
+ _this.printLog("当前 server_url 为空或不正确,只在控制台打印日志,network 中不会发数据,请配置正确的 server_url!");
574
1363
 
575
- para.success = function (data) {
576
- oldsuccess && oldsuccess(data);
1364
+ _this.initConfig["showLog"] = true;
1365
+ } // 如果启用了全埋点或启用了 data-part-key 点击追踪
577
1366
 
578
- if (errorTimer) {
579
- clearTimeout(errorTimer);
580
- errorTimer = null;
581
- }
582
- };
583
1367
 
584
- para.error = function (err, status) {
585
- olderror && olderror(err, status);
1368
+ if (!!_this.initConfig["autoTrack"] || !!_this.initConfig["trackPartKeyClick"]) {
1369
+ // 启用监听
1370
+ _this.listener();
1371
+ } else {
1372
+ // 取消监听
1373
+ _this.unlistener();
1374
+ } // 处理定时上报配置
586
1375
 
587
- if (errorTimer) {
588
- clearTimeout(errorTimer);
589
- errorTimer = null;
590
- }
591
- };
592
1376
 
593
- errorTimer = window.setTimeout(function () {
594
- abort();
595
- }, para.timeout);
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
+ */
596
1389
 
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
- }
605
1390
 
606
- g.onreadystatechange = null;
607
- g.onload = null;
608
- }
609
- } catch (e) {
610
- g.onreadystatechange = null;
611
- g.onload = null;
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
+ };
612
1399
  }
613
- };
614
1400
 
615
- g.open(para.type || "GET", para.url, true);
1401
+ break;
616
1402
 
617
- try {
618
- if (para.credentials) {
619
- g.withCredentials = true;
620
- }
621
-
622
- if (_this.isObject(para.header)) {
623
- _this.each(para.header, function (v, i) {
624
- g.setRequestHeader && g.setRequestHeader(i, v);
625
- });
1403
+ case 'sendTimeout':
1404
+ if (typeof value !== 'number' || value <= 0) {
1405
+ return {
1406
+ valid: false,
1407
+ message: 'sendTimeout 必须是大于 0 的数字'
1408
+ };
626
1409
  }
627
1410
 
628
- if (para.data) {
629
- if (!para.cors) {
630
- g.setRequestHeader && g.setRequestHeader("X-Requested-With", "XMLHttpRequest");
631
- }
1411
+ break;
632
1412
 
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
- }
1413
+ case 'batchInterval':
1414
+ if (typeof value !== 'number' || value <= 0) {
1415
+ return {
1416
+ valid: false,
1417
+ message: 'batchInterval 必须是大于 0 的数字'
1418
+ };
638
1419
  }
639
- } catch (e) {
640
- _this.printLog(e);
641
- }
642
1420
 
643
- g.send(para.data || null);
644
- };
1421
+ break;
645
1422
 
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;
1423
+ case 'batchMaxSize':
1424
+ if (typeof value !== 'number' || value <= 0 || !Number.isInteger(value)) {
1425
+ return {
1426
+ valid: false,
1427
+ message: 'batchMaxSize 必须是大于 0 的整数'
1428
+ };
652
1429
  }
653
- } else if (typeof window.XMLHttpRequest !== "undefined") {
654
- return new XMLHttpRequest();
655
- } else {
656
- return null;
657
- }
658
- };
659
1430
 
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;
1431
+ break;
686
1432
 
687
- if ((typeof navigator === "undefined" ? "undefined" : _typeof(navigator)) !== "object" || typeof navigator.sendBeacon !== "function") {
688
- return supported;
689
- }
1433
+ case 'pendingRequestsMaxSize':
1434
+ if (typeof value !== 'number' || value <= 0 || !Number.isInteger(value)) {
1435
+ return {
1436
+ valid: false,
1437
+ message: 'pendingRequestsMaxSize 必须是大于 0 的整数'
1438
+ };
1439
+ }
690
1440
 
691
- var Sys = _this.getUA();
1441
+ break;
692
1442
 
693
- var ua = navigator.userAgent.toLowerCase();
1443
+ case 'pageDurationInterval':
1444
+ if (typeof value !== 'number' || value <= 0) {
1445
+ return {
1446
+ valid: false,
1447
+ message: 'pageDurationInterval 必须是大于 0 的数字'
1448
+ };
1449
+ }
694
1450
 
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(".");
1451
+ break;
700
1452
 
701
- if (typeof Sys.safari === "undefined") {
702
- Sys.safari = Number(ver[0]);
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
+ };
703
1459
  }
704
1460
 
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;
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
+ };
715
1474
  }
716
- }
717
-
718
- return supported;
719
- };
720
- /**
721
- * 节流函数
722
- * @param func 需要节流的函数
723
- * @param wait 等待时间
724
- * @returns 节流后的函数
725
- */
726
1475
 
1476
+ break;
727
1477
 
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];
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
+ };
736
1485
  }
737
1486
 
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
- }
1487
+ break;
746
1488
 
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);
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
+ };
755
1495
  }
756
- };
757
- };
758
- /**
759
- * 防抖函数
760
- * @param func 需要防抖的函数
761
- * @param wait 等待时间
762
- * @returns 防抖后的函数
763
- */
764
-
765
1496
 
766
- this.debounce = function (func, wait) {
767
- var timeout = null;
768
- return function () {
769
- var args = [];
1497
+ break;
770
1498
 
771
- for (var _i = 0; _i < arguments.length; _i++) {
772
- args[_i] = arguments[_i];
1499
+ case 'platform':
1500
+ if (typeof value !== 'string') {
1501
+ return {
1502
+ valid: false,
1503
+ message: 'platform 必须是字符串'
1504
+ };
773
1505
  }
774
1506
 
775
- if (timeout) {
776
- clearTimeout(timeout);
777
- }
1507
+ break;
1508
+ }
778
1509
 
779
- timeout = window.setTimeout(function () {
780
- func.apply(void 0, args);
781
- }, wait);
782
- };
1510
+ return {
1511
+ valid: true
783
1512
  };
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
- });
809
- }
810
- } else {
811
- return Promise.reject({
812
- success: false,
813
- message: "不支持sendBeacon,发送失败!"
814
- });
815
- }
816
- };
817
- /**
818
- * 获取设备唯一标识
819
- * 基于浏览器指纹技术,通过收集浏览器特征生成唯一ID
820
- * @returns 返回设备唯一标识字符串
821
- */
1513
+ };
1514
+ /**
1515
+ * 用户登录
1516
+ */
1517
+
822
1518
 
1519
+ _this.login = function (userInfo) {
1520
+ if (_this.isObject(userInfo)) _this.userInfo = userInfo;
1521
+ };
1522
+ /**
1523
+ * 获取设备唯一标识
1524
+ * @returns 设备唯一标识
1525
+ */
823
1526
 
824
- this.getDeviceId = function () {
825
- // 获取已存储的设备ID
826
- var storedDeviceId = _this.getCookie('device_id') || _this.getLocalStorage('device_id');
827
1527
 
828
- if (storedDeviceId) {
829
- return storedDeviceId;
830
- } // 收集浏览器指纹信息
1528
+ _this.getDeviceId = function () {
1529
+ // 如果已有设备ID,直接返回
1530
+ if (_this.deviceId) {
1531
+ return _this.deviceId;
1532
+ } // 获取已存储的设备ID
831
1533
 
832
1534
 
833
- var fingerprint = _this.collectFingerprint(); // 生成设备ID
1535
+ var storedDeviceId = _this.getCookie("device_id") || _this.getLocalStorage("device_id");
834
1536
 
1537
+ if (storedDeviceId) {
1538
+ _this.deviceId = storedDeviceId;
1539
+ return _this.deviceId;
1540
+ } // 收集浏览器指纹信息
835
1541
 
836
- var deviceId = _this.hashFingerprint(fingerprint); // 存储设备ID
837
1542
 
1543
+ var fingerprint = _this.collectFingerprint(); // 生成设备ID
838
1544
 
839
- _this.setCookie('device_id', deviceId, 365 * 2); // 存储2年
840
1545
 
1546
+ var deviceId = _this.hashFingerprint(fingerprint); // 存储设备ID(统一使用2年过期时间,与tools.ts保持一致)
841
1547
 
842
- _this.setLocalStorage('device_id', deviceId);
843
1548
 
844
- return deviceId;
845
- };
846
- /**
847
- * 收集浏览器指纹信息
848
- * @returns 返回浏览器指纹对象
849
- */
1549
+ _this.setCookie("device_id", deviceId, 365 * 2); // 存储2年
850
1550
 
851
1551
 
852
- this.collectFingerprint = function () {
853
- var fingerprint = {}; // 1. 用户代理
1552
+ _this.setLocalStorage("device_id", deviceId);
854
1553
 
855
- fingerprint.userAgent = navigator.userAgent; // 2. 屏幕信息
1554
+ _this.deviceId = deviceId;
1555
+ return _this.deviceId;
1556
+ };
1557
+ /**
1558
+ * 重置设备ID
1559
+ * 清除存储的设备ID并重新生成
1560
+ * @returns 新的设备ID
1561
+ */
856
1562
 
857
- fingerprint.screenWidth = screen.width;
858
- fingerprint.screenHeight = screen.height;
859
- fingerprint.colorDepth = screen.colorDepth;
860
- fingerprint.pixelDepth = screen.pixelDepth; // 3. 时区
861
1563
 
862
- fingerprint.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
863
- fingerprint.timezoneOffset = new Date().getTimezoneOffset(); // 4. 语言设置
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
864
1568
 
865
- fingerprint.language = navigator.language;
866
- fingerprint.languages = Array.from(navigator.languages); // 5. 平台信息
1569
+ _this.deviceId = ""; // 重新生成设备ID
867
1570
 
868
- fingerprint.platform = navigator.platform; // 6. WebGL 指纹
1571
+ var newDeviceId = _this.getDeviceId();
869
1572
 
870
- fingerprint.webgl = _this.getWebGLFingerprint(); // 7. Canvas 指纹
1573
+ return newDeviceId;
1574
+ };
1575
+ /**
1576
+ * 自定义代码埋点上报
1577
+ * @param {object} TrackParams [自定义上报参数]
1578
+ * @return {Promise<TrackingResponse>} [回调]
1579
+ */
871
1580
 
872
- fingerprint.canvas = _this.getCanvasFingerprint(); // 8. 音频上下文指纹
873
1581
 
874
- fingerprint.audio = _this.getAudioFingerprint(); // 9. 字体检测
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;
875
1588
 
876
- fingerprint.fonts = _this.getFontFingerprint(); // 10. 插件信息
1589
+ var params = _this.getParams({
1590
+ desc: desc,
1591
+ event: "CustomTrack",
1592
+ itemKey: _this.getItemKey(partkey, pageKey),
1593
+ privateParamMap: {
1594
+ business: business
1595
+ }
1596
+ });
877
1597
 
878
- fingerprint.plugins = _this.getPluginsFingerprint(); // 11. 存储检测
1598
+ return _this.sendData(params, header);
1599
+ };
1600
+ /**
1601
+ * @description 监听全埋点事件
1602
+ */
879
1603
 
880
- fingerprint.localStorage = _this.hasLocalStorage();
881
- fingerprint.sessionStorage = _this.hasSessionStorage();
882
- fingerprint.indexedDB = _this.hasIndexedDB(); // 12. 硬件信息
883
1604
 
884
- fingerprint.hardwareConcurrency = navigator.hardwareConcurrency;
885
- fingerprint.deviceMemory = navigator.deviceMemory;
886
- fingerprint.maxTouchPoints = navigator.maxTouchPoints; // 13. 连接信息
1605
+ _this.listener = function () {
1606
+ // 先移除旧的监听器,避免重复绑定
1607
+ _this.unlistener(); // 如果启用了全埋点,监听页面浏览事件
887
1608
 
888
- fingerprint.connection = _this.getConnectionFingerprint();
889
- return fingerprint;
890
- };
891
- /**
892
- * 获取WebGL指纹
893
- * @returns WebGL指纹字符串
894
- */
895
1609
 
1610
+ if (!!_this.initConfig.autoTrack) {
1611
+ if (!!_this.initConfig.isTrackSinglePage) {
1612
+ _this.rewriteHistory();
896
1613
 
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';
1614
+ _this.addSinglePageEvent(_this.onPageViewCallback);
908
1615
  }
909
- };
910
- /**
911
- * 获取Canvas指纹
912
- * @returns Canvas指纹字符串
913
- */
914
1616
 
1617
+ _this.each(["load", "beforeunload"], function (historyType) {
1618
+ _this.addEventListener(window, historyType, _this.onPageViewCallback);
1619
+ });
1620
+ } // 如果启用了全埋点或启用了 data-part-key 点击追踪,监听点击事件
915
1621
 
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
- */
939
1622
 
1623
+ if (!!_this.initConfig.autoTrack || !!_this.initConfig.trackPartKeyClick) {
1624
+ _this.addEventListener(window, "click", _this.onClickCallback);
1625
+ }
1626
+ };
1627
+ /**
1628
+ * @description 取消全埋点事件
1629
+ */
940
1630
 
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
1631
 
1632
+ _this.unlistener = function () {
1633
+ if (!!_this.initConfig.isTrackSinglePage) {
1634
+ var historyPushState = window.history.pushState;
1635
+ var singlePageEvent = !!historyPushState ? "popstate" : "hashchange";
971
1636
 
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
- });
1637
+ _this.each(["pushState", "replaceState", singlePageEvent], function (historyName) {
1638
+ _this.removeEventListener(window, historyName, _this.onPageViewCallback);
1639
+ });
1640
+ }
1000
1641
 
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
- */
1642
+ _this.each(["load", "beforeunload"], function (historyType) {
1643
+ _this.removeEventListener(window, historyType, _this.onPageViewCallback);
1644
+ });
1014
1645
 
1646
+ _this.removeEventListener(window, "click", _this.onClickCallback); // 清理批量发送定时器
1015
1647
 
1016
- this.getPluginsFingerprint = function () {
1017
- try {
1018
- var plugins = [];
1019
1648
 
1020
- if (navigator.plugins) {
1021
- for (var i = 0; i < navigator.plugins.length; i++) {
1022
- var plugin = navigator.plugins[i];
1649
+ _this.clearBatchTimer(); // 清理定时上报定时器
1023
1650
 
1024
- if (plugin) {
1025
- plugins.push(plugin.name + "|" + plugin.description + "|" + plugin.filename);
1026
- }
1027
- }
1028
- }
1029
1651
 
1030
- return plugins.join(';');
1031
- } catch (e) {
1032
- return 'error';
1033
- }
1034
- };
1035
- /**
1036
- * 检测localStorage支持
1037
- * @returns 是否支持localStorage
1038
- */
1652
+ _this.stopPageDurationTimer();
1653
+ };
1654
+ /**
1655
+ * @description 清理批量发送定时器
1656
+ */
1039
1657
 
1040
1658
 
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
- */
1659
+ _this.clearBatchTimer = function () {
1660
+ if (_this.batchTimer !== null) {
1661
+ clearTimeout(_this.batchTimer);
1662
+ _this.batchTimer = null;
1663
+ }
1664
+ };
1665
+ /**
1666
+ * @description 清空批量队列(包括 LocalStorage 中的数据)
1667
+ */
1055
1668
 
1056
1669
 
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
- */
1670
+ _this.clearBatchQueue = function () {
1671
+ _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
1672
+
1673
+ if (_this.initConfig.showLog) {
1674
+ _this.printLog("批量队列已清空");
1675
+ }
1676
+ };
1677
+ /**
1678
+ * @description 从 LocalStorage 获取批量队列
1679
+ * @returns 批量队列数组
1680
+ */
1071
1681
 
1072
1682
 
1073
- this.hasIndexedDB = function () {
1074
- return 'indexedDB' in window && indexedDB !== null;
1075
- };
1076
- /**
1077
- * 获取网络连接指纹
1078
- * @returns 网络连接指纹字符串
1079
- */
1683
+ _this.getBatchQueueFromStorage = function () {
1684
+ try {
1685
+ var storedQueue = _this.getLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY);
1080
1686
 
1687
+ if (storedQueue) {
1688
+ var parsedQueue = JSON.parse(storedQueue);
1081
1689
 
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';
1690
+ if (Array.isArray(parsedQueue)) {
1691
+ return parsedQueue;
1692
+ }
1089
1693
  }
1090
- };
1091
- /**
1092
- * 将指纹信息哈希为唯一ID
1093
- * @param fingerprint 指纹信息
1094
- * @returns 返回哈希后的设备ID
1095
- */
1694
+ } catch (e) {
1695
+ _this.printLog("\u8BFB\u53D6\u6279\u91CF\u961F\u5217\u5931\u8D25: " + e); // 如果解析失败,清除损坏的数据
1096
1696
 
1097
1697
 
1098
- this.hashFingerprint = function (fingerprint) {
1099
- // 将指纹对象转换为字符串
1100
- var fingerprintString = JSON.stringify(fingerprint, Object.keys(fingerprint).sort()); // 使用更强大的哈希算法生成ID
1698
+ _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
1699
+ }
1101
1700
 
1102
- var hash1 = 5381; // DJB2哈希算法的初始值
1701
+ return [];
1702
+ };
1703
+ /**
1704
+ * @description 保存批量队列到 LocalStorage
1705
+ * @param queue 批量队列数组
1706
+ */
1103
1707
 
1104
- var hash2 = 52711; // 第二个哈希的初始值
1105
1708
 
1106
- for (var i = 0; i < fingerprintString.length; i++) {
1107
- var char = fingerprintString.charCodeAt(i);
1108
- hash1 = (hash1 << 5) + hash1 + char; // hash1 * 33 + char
1709
+ _this.saveBatchQueueToStorage = function (queue) {
1710
+ try {
1711
+ var queueString = JSON.stringify(queue); // 检查存储大小,避免超出 LocalStorage 限制
1109
1712
 
1110
- hash2 = (hash2 << 5) + hash2 + char; // hash2 * 33 + char
1111
- } // 组合两个哈希值并转换为64位整数
1713
+ if (queueString.length > _this.MAX_STORAGE_SIZE) {
1714
+ var maxItems = Math.floor(queue.length * 0.8); // 保留 80%
1112
1715
 
1716
+ var trimmedQueue = queue.slice(-maxItems);
1113
1717
 
1114
- var combinedHash = (hash1 >>> 0) * 4096 + (hash2 >>> 0); // 转换为36进制并添加前缀,增加长度和唯一性
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");
1115
1719
 
1116
- var deviceId = 'fp_' + combinedHash.toString(36);
1117
- return deviceId;
1118
- };
1119
- }
1720
+ _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, JSON.stringify(trimmedQueue));
1721
+ } else {
1722
+ _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, queueString);
1723
+ }
1724
+ } catch (e) {
1725
+ // LocalStorage 可能已满或不可用
1726
+ _this.printLog("\u4FDD\u5B58\u6279\u91CF\u961F\u5217\u5230 LocalStorage \u5931\u8D25: " + e);
1727
+ }
1728
+ };
1120
1729
  /**
1121
- * 打印log
1730
+ * @description 从 LocalStorage 获取待发送请求队列
1731
+ * @returns 待发送请求队列数组
1122
1732
  */
1123
1733
 
1124
1734
 
1125
- WebTrackingTools.prototype.printLog = function () {
1126
- var rest = [];
1127
-
1128
- for (var _i = 0; _i < arguments.length; _i++) {
1129
- rest[_i] = arguments[_i];
1130
- }
1735
+ _this.getPendingRequestsFromStorage = function () {
1736
+ try {
1737
+ var storedRequests = _this.getLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY);
1131
1738
 
1132
- if (this.isObject(rest[0])) {
1133
- rest[0] = this.formatJsonString(rest[0]);
1134
- }
1739
+ if (storedRequests) {
1740
+ var parsedRequests = JSON.parse(storedRequests);
1135
1741
 
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]);
1742
+ if (Array.isArray(parsedRequests)) {
1743
+ return parsedRequests;
1744
+ }
1141
1745
  }
1746
+ } catch (e) {
1747
+ _this.printLog("\u8BFB\u53D6\u5F85\u53D1\u9001\u8BF7\u6C42\u5931\u8D25: " + e); // 如果解析失败,清除损坏的数据
1748
+
1749
+
1750
+ _this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, "[]");
1142
1751
  }
1752
+
1753
+ return [];
1143
1754
  };
1144
1755
  /**
1145
- * @description 检验是否是对象
1756
+ * @description 保存待发送请求队列到 LocalStorage
1757
+ * @param requests 待发送请求队列数组
1146
1758
  */
1147
1759
 
1148
1760
 
1149
- WebTrackingTools.prototype.isObject = function (obj) {
1150
- if (obj == null) {
1151
- return false;
1152
- } else {
1153
- return toString.call(obj) == "[object Object]";
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);
1154
1767
  }
1155
1768
  };
1769
+ /**
1770
+ * @description 设置自定义页面唯一标识
1771
+ * @param pageKey 页面唯一标识,如果传入 null 或空字符串,则恢复自动生成
1772
+ * @param autoUpdate 路由变化时是否自动更新(默认:false,使用自定义值后不再自动更新)
1773
+ */
1156
1774
 
1157
- WebTrackingTools.prototype.isUndefined = function (obj) {
1158
- return obj === void 0;
1159
- };
1160
1775
 
1161
- WebTrackingTools.prototype.isString = function (obj) {
1162
- return toString.call(obj) == "[object String]";
1163
- };
1776
+ _this.setPageKey = function (pageKey, autoUpdate) {
1777
+ if (autoUpdate === void 0) {
1778
+ autoUpdate = false;
1779
+ }
1164
1780
 
1165
- WebTrackingTools.prototype.isDate = function (obj) {
1166
- return toString.call(obj) == "[object Date]";
1167
- };
1781
+ if (pageKey === null || pageKey === '') {
1782
+ // 恢复自动生成
1783
+ _this.useCustomPageKey = false;
1784
+ var pathname = window.location.pathname;
1785
+ _this.pageKey = pathname.replace(/\//g, "_").substring(1);
1168
1786
 
1169
- WebTrackingTools.prototype.isBoolean = function (obj) {
1170
- return toString.call(obj) == "[object Boolean]";
1171
- };
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;
1172
1793
 
1173
- WebTrackingTools.prototype.isNumber = function (obj) {
1174
- return toString.call(obj) == "[object Number]" && /[\d\.]+/.test(String(obj));
1794
+ if (_this.initConfig.showLog) {
1795
+ _this.printLog("\u9875\u9762\u6807\u8BC6\u5DF2\u8BBE\u7F6E\u4E3A: " + pageKey + ", \u81EA\u52A8\u66F4\u65B0: " + autoUpdate);
1796
+ }
1797
+ }
1175
1798
  };
1799
+ /**
1800
+ * @description 获取当前页面唯一标识
1801
+ * @returns 当前页面唯一标识
1802
+ */
1803
+
1176
1804
 
1177
- WebTrackingTools.prototype.isElement = function (obj) {
1178
- return !!(obj && obj.nodeType === 1);
1805
+ _this.getPageKey = function () {
1806
+ return _this.pageKey;
1179
1807
  };
1180
1808
 
1181
- WebTrackingTools.prototype.isFunction = function (f) {
1182
- if (!f) {
1183
- return false;
1184
- }
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
+ });
1185
1836
 
1186
- var type = toString.call(f);
1187
- return type == "[object Function]" || type == "[object AsyncFunction]";
1837
+ _this.sendData(params);
1188
1838
  };
1839
+ /**
1840
+ * @description 路由触发事件
1841
+ */
1189
1842
 
1190
- WebTrackingTools.prototype.isJSONString = function (str) {
1191
- if (!this.isString(str)) return false;
1192
1843
 
1193
- try {
1194
- JSON.parse(str);
1195
- } catch (e) {
1196
- return false;
1197
- }
1844
+ _this.onPageViewCallback = function (e) {
1845
+ var _a, _b; // 在路由变化前,先发送待发送的数据(避免被取消)
1198
1846
 
1199
- return true;
1200
- };
1201
1847
 
1202
- WebTrackingTools.prototype._decodeURIComponent = function (val) {
1203
- var result = val;
1848
+ var pendingRequests = _this.getPendingRequestsFromStorage();
1204
1849
 
1205
- try {
1206
- result = decodeURIComponent(val);
1207
- } catch (e) {
1208
- result = val;
1850
+ var batchQueue = _this.initConfig.batchSend ? _this.getBatchQueueFromStorage() : [];
1851
+
1852
+ if (pendingRequests.length > 0 || batchQueue.length > 0) {
1853
+ _this.flushPendingData();
1209
1854
  }
1210
1855
 
1211
- return result;
1212
- };
1856
+ var ORGIN = window.location.origin;
1213
1857
 
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 = []; // 主域名一定会有两部分组成
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
+ });
1221
1866
 
1222
- urlItems.unshift(domainList.pop()); // 慢慢从后往前测试
1867
+ _this.currentUrl = window.location.href; // 如果使用自定义 pageKey,路由变化时不自动更新
1223
1868
 
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存在,则说明域名合法
1869
+ if (!_this.useCustomPageKey) {
1870
+ _this.pageKey = window.location.pathname.replace(/\//g, "_").substring(1);
1871
+ } // 路由变化时,如果启用了定时上报,需要重启定时器
1229
1872
 
1230
- if (keyR.test(document.cookie)) {
1231
- document.cookie = cookie + ";expires=" + expiredTime;
1232
- return mainHost;
1233
- }
1873
+
1874
+ if (_this.initConfig.autoTrackPageDurationInterval) {
1875
+ _this.stopPageDurationTimer();
1876
+
1877
+ _this.startPageDurationTimer();
1234
1878
  }
1235
1879
 
1236
- return domain;
1237
- };
1880
+ _this.sendRetained(e.type);
1238
1881
 
1239
- return WebTrackingTools;
1240
- }();
1882
+ _this.sendData(params);
1883
+ };
1241
1884
 
1242
- var WebTracking =
1243
- /** @class */
1244
- function (_super) {
1245
- __extends(WebTracking, _super);
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; // 过滤敏感数据
1246
1896
 
1247
- function WebTracking() {
1248
- var _this = _super.call(this) || this; // 批量发送队列
1897
+ var filteredBusiness = _this.filterSensitiveData(business || {});
1249
1898
 
1899
+ var filteredUserInfo = _this.filterSensitiveData(_this.userInfo || {});
1250
1900
 
1251
- _this.batchQueue = []; // 批量发送定时器
1901
+ var filteredPrivateParamMap = _this.filterSensitiveData(privateParamMap || {});
1252
1902
 
1253
- _this.batchTimer = null; // LocalStorage 存储 key
1903
+ var filteredUrlParams = _this.filterSensitiveData(_this.getQueryValue() || {}); // 创建私有参数对象
1254
1904
 
1255
- _this.BATCH_QUEUE_STORAGE_KEY = "web_tracking_batch_queue"; // 是否使用自定义 pageKey(如果为 true,路由变化时不会自动更新 pageKey)
1256
1905
 
1257
- _this.useCustomPageKey = false; // 用户信息
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
1258
1918
 
1259
- _this.userInfo = null; // 当前路由
1919
+ }; // 添加其他可能的属性
1260
1920
 
1261
- _this.currentUrl = ""; // 页面唯一标识,取当前路由 window.location.pathname.replace(/\//g, '_').substr(1)
1921
+ if (filteredPrivateParamMap.targetEle) {
1922
+ privateParamMapData.targetEle = filteredPrivateParamMap.targetEle;
1923
+ }
1262
1924
 
1263
- _this.pageKey = ""; // 设备唯一标识
1925
+ if (filteredPrivateParamMap.targetUrl) {
1926
+ privateParamMapData.targetUrl = filteredPrivateParamMap.targetUrl;
1927
+ }
1264
1928
 
1265
- _this.deviceId = ""; // 上传事件描述
1929
+ if (filteredPrivateParamMap.pointerType) {
1930
+ privateParamMapData.pointerType = filteredPrivateParamMap.pointerType;
1931
+ }
1266
1932
 
1267
- _this.eventDescMap = {
1268
- PageView: "Web 浏览页面",
1269
- WebClick: "Web 元素点击",
1270
- PageRetained: "Web 页面浏览时长",
1271
- CustomTrack: "Web 自定义代码上报"
1272
- };
1273
- /**
1274
- * @description 初始化函数
1275
- * @param {object} InitParams [初始化参数]
1276
- */
1933
+ if (filteredPrivateParamMap.elementSelector) {
1934
+ privateParamMapData.elementSelector = filteredPrivateParamMap.elementSelector;
1935
+ }
1277
1936
 
1278
- _this.init = function (initParams) {
1279
- _this.preset(initParams);
1937
+ if (filteredPrivateParamMap.retainedDuration) {
1938
+ privateParamMapData.retainedDuration = filteredPrivateParamMap.retainedDuration;
1939
+ }
1280
1940
 
1281
- var pathname = window.location.pathname;
1282
- _this.currentUrl = window.location.href; // 如果传入了自定义 pageKey,使用自定义值,否则自动生成
1941
+ return {
1942
+ event: event,
1943
+ desc: desc,
1944
+ itemKey: itemKey || _this.getItemKey(),
1945
+ requestTime: _this.getTimeStamp(),
1946
+ privateParamMap: privateParamMapData
1947
+ };
1948
+ };
1949
+ /**
1950
+ * 数据采样判断
1951
+ * @returns 是否应该采样
1952
+ */
1283
1953
 
1284
- if (initParams.pageKey) {
1285
- _this.pageKey = initParams.pageKey;
1286
- _this.useCustomPageKey = true;
1287
- } else {
1288
- _this.pageKey = pathname.replace(/\//g, "_").substring(1);
1289
- _this.useCustomPageKey = false;
1290
- }
1291
1954
 
1292
- _this.systemsInfo = _this.getSystemsInfo(initParams.platform); // 获取设备ID
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
+ */
1293
1964
 
1294
- _this.deviceId = _this.getDeviceId();
1295
1965
 
1296
- _this.setCookie("retainedStartTime", _this.getTimeStamp()); // 如果启用了批量发送,从 LocalStorage 恢复队列
1966
+ _this.flushBatchQueue = function () {
1967
+ var batchQueue = _this.getBatchQueueFromStorage();
1297
1968
 
1969
+ if (batchQueue.length === 0) return;
1298
1970
 
1299
- if (_this.initConfig.batchSend) {
1300
- _this.restoreBatchQueueFromStorage(); // 监听页面卸载事件,保存队列
1971
+ var currentTime = _this.getTimeStamp(); // 过滤出可以发送的数据(未到重试时间的不发送)
1301
1972
 
1302
1973
 
1303
- _this.setupBeforeUnloadListener();
1974
+ var readyToSend = batchQueue.filter(function (item) {
1975
+ if (!item._nextRetryTime) {
1976
+ return true;
1304
1977
  }
1305
- };
1306
- /**
1307
- * TODO: 需要判断有哪些不能被预制的参数
1308
- * @description 预置参数
1309
- * @param {object} PresetParams [预置参数]
1310
- */
1311
-
1312
-
1313
- _this.preset = function (presetParams) {
1314
- if (presetParams instanceof Object) {
1315
- // 处理 pageKey 特殊逻辑
1316
- if (presetParams.pageKey !== undefined) {
1317
- if (presetParams.pageKey === null || presetParams.pageKey === '') {
1318
- // 恢复自动生成
1319
- _this.useCustomPageKey = false;
1320
- var pathname = window.location.pathname;
1321
- _this.pageKey = pathname.replace(/\//g, "_").substring(1);
1322
- } else {
1323
- _this.pageKey = presetParams.pageKey;
1324
- _this.useCustomPageKey = true;
1325
- }
1326
- }
1327
1978
 
1328
- _this.each(presetParams, function (val, key) {
1329
- // 跳过 pageKey,因为已经单独处理
1330
- if (key === 'pageKey') return;
1979
+ return item._nextRetryTime <= currentTime;
1980
+ });
1331
1981
 
1332
- if (_this.initConfig.hasOwnProperty(key)) {
1333
- // TODO:后面加一些校验
1334
- _this.initConfig[key] = val;
1335
- }
1336
- });
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");
1337
1985
  }
1338
1986
 
1339
- if (!/^(((ht|f)tps?):\/\/)?[\w-]+(\.[\w-]+)+([\w.,@?^=%&:/~+#-\(\)]*[\w@?^=%&/~+#-\(\)])?$/.test(_this.initConfig["serverUrl"])) {
1340
- _this.printLog("当前 server_url 为空或不正确,只在控制台打印日志,network 中不会发数据,请配置正确的 server_url!");
1341
-
1342
- _this.initConfig["showLog"] = true;
1343
- } // 如果启用了全埋点
1987
+ return;
1988
+ } // 从队列中移除已准备发送的数据
1344
1989
 
1345
1990
 
1346
- if (!!_this.initConfig["autoTrack"]) {
1347
- // 启用监听
1348
- _this.listener();
1349
- } else {
1350
- // 取消监听
1351
- _this.unlistener();
1991
+ var remainingQueue = batchQueue.filter(function (item) {
1992
+ if (!item._nextRetryTime) {
1993
+ return false;
1352
1994
  }
1353
- };
1354
- /**
1355
- * 用户登录
1356
- */
1357
1995
 
1996
+ return item._nextRetryTime > currentTime;
1997
+ }); // 保存剩余的队列
1358
1998
 
1359
- _this.login = function (userInfo) {
1360
- if (_this.isObject(userInfo)) _this.userInfo = userInfo;
1361
- };
1362
- /**
1363
- * 获取设备唯一标识
1364
- * @returns 设备唯一标识
1365
- */
1999
+ _this.saveBatchQueueToStorage(remainingQueue); // 发送批量数据
1366
2000
 
1367
2001
 
1368
- _this.getDeviceId = function () {
1369
- // 如果已有设备ID,直接返回
1370
- if (_this.deviceId) {
1371
- return _this.deviceId;
1372
- } // 获取已存储的设备ID
2002
+ _this.sendBatchData(readyToSend);
2003
+ };
2004
+ /**
2005
+ * 发送批量数据
2006
+ * @param data 批量数据
2007
+ */
1373
2008
 
1374
2009
 
1375
- var storedDeviceId = _this.getCookie("device_id") || _this.getLocalStorage("device_id");
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;
1376
2017
 
1377
- if (storedDeviceId) {
1378
- _this.deviceId = storedDeviceId;
1379
- return _this.deviceId;
1380
- } // 收集浏览器指纹信息
2018
+ if (showLog) {
2019
+ _this.printLog("\u6279\u91CF\u53D1\u9001 " + data.length + " \u6761\u6570\u636E");
1381
2020
 
2021
+ data.forEach(function (item) {
2022
+ return _this.printLog(item);
2023
+ });
2024
+ } // 判断是否使用 sendBeacon
1382
2025
 
1383
- var fingerprint = _this.collectFingerprint(); // 生成设备ID
1384
2026
 
2027
+ var shouldUseBeacon = _this.shouldUseBeacon(sendMethod, undefined, initHeader); // 如果使用 sendBeacon
1385
2028
 
1386
- var deviceId = _this.hashFingerprint(fingerprint); // 存储设备ID(统一使用2年过期时间,与tools.ts保持一致)
1387
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);
1388
2036
 
1389
- _this.setCookie("device_id", deviceId, 365 * 2); // 存储2年
2037
+ if (sent) {
2038
+ // 发送成功,确保 LocalStorage 已清空
2039
+ _this.saveBatchQueueToStorage([]);
1390
2040
 
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");
1391
2047
 
1392
- _this.setLocalStorage("device_id", deviceId);
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");
1393
2053
 
1394
- _this.deviceId = deviceId;
1395
- return _this.deviceId;
1396
- };
1397
- /**
1398
- * 重置设备ID
1399
- * 清除存储的设备ID并重新生成
1400
- * @returns 新的设备ID
1401
- */
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");
1402
2080
 
2081
+ _this.retryBatchData(data);
2082
+ }
2083
+ });
2084
+ }
2085
+ };
2086
+ /**
2087
+ * @description 批量数据重试逻辑
2088
+ * @param data 批量数据
2089
+ */
1403
2090
 
1404
- _this.resetDeviceId = function () {
1405
- // 清除cookie和localStorage中的设备ID
1406
- document.cookie = "device_id=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
1407
- localStorage.removeItem("device_id"); // 清除内存中的设备ID
1408
2091
 
1409
- _this.deviceId = ""; // 重新生成设备ID
2092
+ _this.retryBatchData = function (data) {
2093
+ // 获取当前队列
2094
+ var currentQueue = _this.getBatchQueueFromStorage(); // 去重:基于事件类型、itemKey、requestTime 生成唯一键
1410
2095
 
1411
- var newDeviceId = _this.getDeviceId();
1412
2096
 
1413
- return newDeviceId;
2097
+ var getEventKey = function getEventKey(item) {
2098
+ return item.event + "_" + item.itemKey + "_" + item.requestTime;
1414
2099
  };
1415
- /**
1416
- * 自定义代码埋点上报
1417
- * @param {object} TrackParams [自定义上报参数]
1418
- * @return {Promise<TrackingResponse>} [回调]
1419
- */
1420
-
1421
-
1422
- _this.track = function (_a) {
1423
- var desc = _a.desc,
1424
- pageKey = _a.pageKey,
1425
- partkey = _a.partkey,
1426
- business = _a.business,
1427
- header = _a.header;
1428
-
1429
- var params = _this.getParams({
1430
- desc: desc,
1431
- event: "CustomTrack",
1432
- itemKey: _this.getItemKey(partkey, pageKey),
1433
- privateParamMap: {
1434
- business: business
1435
- }
1436
- });
1437
2100
 
1438
- return _this.sendData(params, header);
1439
- };
1440
- /**
1441
- * @description 监听全埋点事件
1442
- */
2101
+ var existingKeys = new Set(currentQueue.map(getEventKey));
2102
+ var maxRetryCount = 3; // 最大重试次数
1443
2103
 
2104
+ var currentTime = _this.getTimeStamp(); // 过滤并更新重试信息
1444
2105
 
1445
- _this.listener = function () {
1446
- if (!!_this.initConfig.isTrackSinglePage) {
1447
- _this.rewriteHistory();
1448
2106
 
1449
- _this.addSinglePageEvent(_this.onPageViewCallback);
1450
- }
2107
+ var retryData = data.filter(function (item) {
2108
+ var key = getEventKey(item); // 检查是否已存在
1451
2109
 
1452
- _this.each(["load", "beforeunload"], function (historyType) {
1453
- _this.addEventListener(window, historyType, _this.onPageViewCallback);
1454
- });
2110
+ if (existingKeys.has(key)) {
2111
+ return false;
2112
+ } // 检查重试次数
1455
2113
 
1456
- _this.addEventListener(window, "click", _this.onClickCallback);
1457
- };
1458
- /**
1459
- * @description 取消全埋点事件
1460
- */
1461
2114
 
2115
+ var retryCount = (item._retryCount || 0) + 1;
1462
2116
 
1463
- _this.unlistener = function () {
1464
- if (!!_this.initConfig.isTrackSinglePage) {
1465
- var historyPushState = window.history.pushState;
1466
- var singlePageEvent = !!historyPushState ? "popstate" : "hashchange";
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
+ }
1467
2121
 
1468
- _this.each(["pushState", "replaceState", singlePageEvent], function (historyName) {
1469
- _this.removeEventListener(window, historyName, _this.onPageViewCallback);
1470
- });
1471
- }
2122
+ return false;
2123
+ } // 更新重试信息
1472
2124
 
1473
- _this.each(["load", "beforeunload"], function (historyType) {
1474
- _this.removeEventListener(window, historyType, _this.onPageViewCallback);
1475
- });
1476
2125
 
1477
- _this.removeEventListener(window, "click", _this.onClickCallback); // 清理批量发送定时器
2126
+ item._retryCount = retryCount; // 指数退避:2^retryCount * 1000ms
1478
2127
 
2128
+ item._nextRetryTime = currentTime + Math.pow(2, retryCount) * 1000;
2129
+ existingKeys.add(key);
2130
+ return true;
2131
+ }); // 将失败的数据重新加入队列(添加到队列前面,优先重试)
1479
2132
 
1480
- _this.clearBatchTimer();
1481
- };
1482
- /**
1483
- * @description 清理批量发送定时器
1484
- */
2133
+ var retryQueue = __spreadArray(__spreadArray([], retryData), currentQueue); // 限制重试队列大小,避免内存溢出
1485
2134
 
1486
2135
 
1487
- _this.clearBatchTimer = function () {
1488
- if (_this.batchTimer !== null) {
1489
- clearTimeout(_this.batchTimer);
1490
- _this.batchTimer = null;
1491
- }
1492
- };
1493
- /**
1494
- * @description 清空批量队列(包括 LocalStorage 中的数据)
1495
- */
2136
+ var maxSize = _this.initConfig.batchMaxSize * 2;
2137
+ var trimmedQueue = retryQueue.length > maxSize ? retryQueue.slice(0, maxSize) : retryQueue; // 保存失败的数据到 LocalStorage,确保数据不丢失
1496
2138
 
2139
+ _this.saveBatchQueueToStorage(trimmedQueue);
1497
2140
 
1498
- _this.clearBatchQueue = function () {
1499
- _this.batchQueue = [];
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
+ */
1500
2149
 
1501
- _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
1502
2150
 
2151
+ _this.addToBatchQueue = function (params) {
2152
+ var _a = _this.initConfig,
2153
+ batchInterval = _a.batchInterval,
2154
+ batchMaxSize = _a.batchMaxSize; // 数据采样判断(在添加到队列前判断)
2155
+
2156
+ if (!_this.shouldSample()) {
1503
2157
  if (_this.initConfig.showLog) {
1504
- _this.printLog("批量队列已清空");
2158
+ _this.printLog("数据已采样跳过(批量模式)");
1505
2159
  }
1506
- };
1507
- /**
1508
- * @description 设置自定义页面唯一标识
1509
- * @param pageKey 页面唯一标识,如果传入 null 或空字符串,则恢复自动生成
1510
- * @param autoUpdate 路由变化时是否自动更新(默认:false,使用自定义值后不再自动更新)
1511
- */
1512
2160
 
2161
+ return;
2162
+ } // 从 LocalStorage 获取当前队列
1513
2163
 
1514
- _this.setPageKey = function (pageKey, autoUpdate) {
1515
- if (autoUpdate === void 0) {
1516
- autoUpdate = false;
1517
- }
1518
2164
 
1519
- if (pageKey === null || pageKey === '') {
1520
- // 恢复自动生成
1521
- _this.useCustomPageKey = false;
1522
- var pathname = window.location.pathname;
1523
- _this.pageKey = pathname.replace(/\//g, "_").substring(1);
2165
+ var currentQueue = _this.getBatchQueueFromStorage(); // 添加新数据
1524
2166
 
1525
- if (_this.initConfig.showLog) {
1526
- _this.printLog("\u9875\u9762\u6807\u8BC6\u5DF2\u6062\u590D\u81EA\u52A8\u751F\u6210: " + _this.pageKey);
1527
- }
1528
- } else {
1529
- _this.pageKey = pageKey;
1530
- _this.useCustomPageKey = !autoUpdate;
1531
2167
 
1532
- if (_this.initConfig.showLog) {
1533
- _this.printLog("\u9875\u9762\u6807\u8BC6\u5DF2\u8BBE\u7F6E\u4E3A: " + pageKey + ", \u81EA\u52A8\u66F4\u65B0: " + autoUpdate);
1534
- }
1535
- }
1536
- };
1537
- /**
1538
- * @description 获取当前页面唯一标识
1539
- * @returns 当前页面唯一标识
1540
- */
2168
+ currentQueue.push(params); // 保存到 LocalStorage
1541
2169
 
2170
+ _this.saveBatchQueueToStorage(currentQueue); // 如果队列达到最大数量,立即发送
1542
2171
 
1543
- _this.getPageKey = function () {
1544
- return _this.pageKey;
1545
- };
1546
2172
 
1547
- _this.onClickCallback = function (e) {
1548
- var _a;
1549
-
1550
- var target = e.target;
1551
- if (!((_a = target === null || target === void 0 ? void 0 : target.dataset) === null || _a === void 0 ? void 0 : _a.partKey)) return;
1552
- var position = [e.pageX, e.pageY];
1553
- var id = target.id;
1554
- var className = target.className;
1555
- var nodeName = target.nodeName;
1556
- var targetEle = {
1557
- id: id,
1558
- nodeName: nodeName,
1559
- className: className,
1560
- position: position
1561
- };
2173
+ if (currentQueue.length >= batchMaxSize) {
2174
+ _this.flushBatchQueue();
1562
2175
 
1563
- var params = _this.getParams({
1564
- event: "WebClick",
1565
- desc: _this.eventDescMap["WebClick"],
1566
- itemKey: _this.getItemKey(target.dataset.partKey),
1567
- privateParamMap: {
1568
- targetEle: targetEle,
1569
- pointerType: e.pointerType,
1570
- currentUrl: _this.currentUrl,
1571
- elementSelector: _this.getDomSelector(target) || ""
1572
- }
1573
- });
2176
+ return;
2177
+ } // 设置定时发送
1574
2178
 
1575
- return _this.sendData(params);
1576
- };
1577
- /**
1578
- * @description 路由触发事件
1579
- */
1580
2179
 
2180
+ if (!_this.batchTimer) {
2181
+ _this.batchTimer = window.setTimeout(function () {
2182
+ _this.flushBatchQueue();
1581
2183
 
1582
- _this.onPageViewCallback = function (e) {
1583
- var _a, _b;
2184
+ _this.batchTimer = null;
2185
+ }, batchInterval);
2186
+ }
2187
+ };
2188
+ /**
2189
+ * 从 LocalStorage 恢复批量队列
2190
+ */
1584
2191
 
1585
- var ORGIN = window.location.origin;
1586
2192
 
1587
- var params = _this.getParams({
1588
- event: "PageView",
1589
- desc: _this.eventDescMap["PageView"],
1590
- privateParamMap: {
1591
- currentUrl: _this.currentUrl,
1592
- 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
1593
- }
1594
- });
2193
+ _this.restoreBatchQueueFromStorage = function () {
2194
+ var batchQueue = _this.getBatchQueueFromStorage();
1595
2195
 
1596
- _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
+ } // 恢复后立即尝试发送(如果达到条件)
1597
2200
 
1598
- if (!_this.useCustomPageKey) {
1599
- _this.pageKey = window.location.pathname.replace(/\//g, "_").substring(1);
1600
- }
1601
2201
 
1602
- _this.sendRetained(e.type);
2202
+ var batchMaxSize = _this.initConfig.batchMaxSize;
1603
2203
 
1604
- _this.sendData(params);
1605
- };
2204
+ if (batchQueue.length >= batchMaxSize) {
2205
+ _this.flushBatchQueue();
2206
+ } else {
2207
+ // 设置定时发送
2208
+ var batchInterval = _this.initConfig.batchInterval;
1606
2209
 
1607
- _this.getParams = function (_a) {
1608
- var event = _a.event,
1609
- desc = _a.desc,
1610
- _b = _a.privateParamMap,
1611
- privateParamMap = _b === void 0 ? {} : _b,
1612
- itemKey = _a.itemKey;
1613
- var business = _this.initConfig.business;
1614
- var pageWidth = window.innerWidth;
1615
- var pageHeight = window.innerHeight;
1616
- var screenWidth = window.screen.width;
1617
- var screenHeight = window.screen.height; // 过滤敏感数据
2210
+ if (!_this.batchTimer) {
2211
+ _this.batchTimer = window.setTimeout(function () {
2212
+ _this.flushBatchQueue();
1618
2213
 
1619
- var filteredBusiness = _this.filterSensitiveData(business || {});
2214
+ _this.batchTimer = null;
2215
+ }, batchInterval);
2216
+ }
2217
+ }
2218
+ }
2219
+ };
2220
+ /**
2221
+ * 添加到待发送请求队列
2222
+ * @param params 数据参数
2223
+ */
1620
2224
 
1621
- var filteredUserInfo = _this.filterSensitiveData(_this.userInfo || {});
1622
2225
 
1623
- var filteredPrivateParamMap = _this.filterSensitiveData(privateParamMap || {});
2226
+ _this.addToPendingRequests = function (params) {
2227
+ // 从 LocalStorage 获取当前队列
2228
+ var currentRequests = _this.getPendingRequestsFromStorage(); // 添加新数据
1624
2229
 
1625
- var filteredUrlParams = _this.filterSensitiveData(_this.getQueryValue() || {}); // 创建私有参数对象
1626
2230
 
2231
+ currentRequests.push(params); // 限制队列大小,防止内存溢出
1627
2232
 
1628
- var privateParamMapData = {
1629
- currentUrl: filteredPrivateParamMap.currentUrl || _this.currentUrl,
1630
- business: Object.assign({}, filteredBusiness, filteredPrivateParamMap.business || {}),
1631
- pageWidth: pageWidth,
1632
- pageHeight: pageHeight,
1633
- screenWidth: screenWidth,
1634
- screenHeight: screenHeight,
1635
- sdkVersion: _this.sdkVersion,
1636
- systemsInfo: _this.systemsInfo,
1637
- urlParams: filteredUrlParams,
1638
- userInfo: filteredUserInfo,
1639
- deviceId: _this.deviceId // 添加设备ID
2233
+ var maxSize = _this.initConfig.pendingRequestsMaxSize || _this.DEFAULT_PENDING_REQUESTS_MAX_SIZE;
1640
2234
 
1641
- }; // 添加其他可能的属性
2235
+ if (currentRequests.length > maxSize) {
2236
+ var trimmedRequests = currentRequests.slice(-maxSize);
1642
2237
 
1643
- if (filteredPrivateParamMap.targetEle) {
1644
- 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");
1645
2240
  }
1646
2241
 
1647
- if (filteredPrivateParamMap.targetUrl) {
1648
- privateParamMapData.targetUrl = filteredPrivateParamMap.targetUrl;
1649
- }
2242
+ _this.savePendingRequestsToStorage(trimmedRequests);
2243
+ } else {
2244
+ _this.savePendingRequestsToStorage(currentRequests);
2245
+ }
2246
+ };
2247
+ /**
2248
+ * 从 LocalStorage 恢复待发送请求
2249
+ */
1650
2250
 
1651
- if (filteredPrivateParamMap.pointerType) {
1652
- privateParamMapData.pointerType = filteredPrivateParamMap.pointerType;
1653
- }
1654
2251
 
1655
- if (filteredPrivateParamMap.elementSelector) {
1656
- privateParamMapData.elementSelector = filteredPrivateParamMap.elementSelector;
1657
- }
2252
+ _this.restorePendingRequestsFromStorage = function () {
2253
+ var pendingRequests = _this.getPendingRequestsFromStorage();
1658
2254
 
1659
- if (filteredPrivateParamMap.retainedDuration) {
1660
- privateParamMapData.retainedDuration = filteredPrivateParamMap.retainedDuration;
1661
- }
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
+ // 这样可以避免与批量队列冲突
1662
2261
 
1663
- return {
1664
- event: event,
1665
- desc: desc,
1666
- itemKey: itemKey || _this.getItemKey(),
1667
- requestTime: _this.getTimeStamp(),
1668
- privateParamMap: privateParamMapData
1669
- };
1670
- };
1671
- /**
1672
- * 数据采样判断
1673
- * @returns 是否应该采样
1674
- */
2262
+ }
2263
+ };
2264
+ /**
2265
+ * 检查页面是否即将卸载
2266
+ * @returns 如果页面即将卸载返回 true,否则返回 false
2267
+ */
1675
2268
 
1676
2269
 
1677
- _this.shouldSample = function () {
1678
- var sampleRate = _this.initConfig.sampleRate;
1679
- if (sampleRate >= 1) return true;
1680
- if (sampleRate <= 0) return false;
1681
- return Math.random() < sampleRate;
1682
- };
1683
- /**
1684
- * 批量发送数据
1685
- */
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
+ */
2280
+
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
+ }
1686
2292
 
2293
+ return false;
2294
+ }
2295
+ };
2296
+ /**
2297
+ * 刷新待发送的单个请求(正常情况下的发送)
2298
+ * 注意:这个方法会直接使用 ajax 发送,避免通过 sendData 导致重复
2299
+ */
1687
2300
 
1688
- _this.flushBatchQueue = function () {
1689
- if (_this.batchQueue.length === 0) return;
1690
2301
 
1691
- var batchData = __spreadArray([], _this.batchQueue);
2302
+ _this.flushPendingRequests = function () {
2303
+ var pendingRequests = _this.getPendingRequestsFromStorage();
1692
2304
 
1693
- _this.batchQueue = []; // 从 LocalStorage 中移除已发送的数据
2305
+ if (pendingRequests.length === 0) {
2306
+ return;
2307
+ } // 清除 LocalStorage 中的待发送请求
1694
2308
 
1695
- _this.saveBatchQueueToStorage(); // 发送批量数据
1696
2309
 
2310
+ _this.savePendingRequestsToStorage([]); // 直接使用 ajax 发送每个请求,避免通过 sendData 导致重复
1697
2311
 
1698
- _this.sendBatchData(batchData);
1699
- };
1700
- /**
1701
- * 发送批量数据
1702
- * @param data 批量数据
1703
- */
1704
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
+ }
1705
2325
 
1706
- _this.sendBatchData = function (data) {
1707
- var _a = _this.initConfig,
1708
- serverUrl = _a.serverUrl,
1709
- contentType = _a.contentType,
1710
- showLog = _a.showLog;
2326
+ return;
2327
+ }
1711
2328
 
1712
2329
  if (showLog) {
1713
- _this.printLog("\u6279\u91CF\u53D1\u9001 " + data.length + " \u6761\u6570\u636E");
1714
-
1715
- data.forEach(function (item) {
1716
- return _this.printLog(item);
1717
- });
1718
- } // 这里可以根据实际需求决定是逐条发送还是打包发送
1719
- // 这里选择打包发送
2330
+ _this.printLog(params);
2331
+ } // 直接使用 ajax 发送
1720
2332
 
1721
2333
 
1722
2334
  _this.ajax({
2335
+ header: initHeader,
1723
2336
  url: serverUrl,
1724
2337
  type: "POST",
1725
- data: JSON.stringify({
1726
- events: data
1727
- }),
2338
+ data: JSON.stringify(params),
1728
2339
  contentType: contentType,
1729
2340
  credentials: false,
1730
- timeout: _this.initConfig.sendTimeout,
2341
+ timeout: sendTimeout,
1731
2342
  cors: true,
1732
2343
  success: function success() {
1733
- // 批量发送成功,数据已从队列中移除,无需额外操作
1734
- if (_this.initConfig.showLog) {
1735
- _this.printLog("\u6279\u91CF\u53D1\u9001\u6210\u529F: " + data.length + " \u6761\u6570\u636E");
2344
+ if (showLog) {
2345
+ _this.printLog("待发送请求发送成功");
1736
2346
  }
1737
2347
  },
1738
2348
  error: function error(err) {
1739
- var _a; // 批量发送失败,重新加入队列以便重试
1740
-
1741
-
1742
- _this.printLog("\u6279\u91CF\u53D1\u9001\u5931\u8D25: " + err + "\uFF0C\u6570\u636E\u5DF2\u91CD\u65B0\u52A0\u5165\u961F\u5217"); // 将失败的数据重新加入队列,避免数据丢失
2349
+ if (showLog) {
2350
+ _this.printLog("\u5F85\u53D1\u9001\u8BF7\u6C42\u53D1\u9001\u5931\u8D25\uFF08\u4E0D\u518D\u91CD\u8BD5\uFF09: " + err);
2351
+ }
2352
+ }
2353
+ });
2354
+ });
2355
+ };
2356
+ /**
2357
+ * 设置页面卸载监听器,确保数据发送
2358
+ */
1743
2359
 
1744
2360
 
1745
- (_a = _this.batchQueue).unshift.apply(_a, data); // 限制重试队列大小,避免内存溢出
2361
+ _this.setupBeforeUnloadListener = function () {
2362
+ // 避免重复设置监听器
2363
+ if (_this.isUnloadListenerSetup) {
2364
+ return;
2365
+ }
1746
2366
 
2367
+ _this.isUnloadListenerSetup = true; // 使用 visibilitychange 事件(更可靠,支持页面跳转、切换标签页等场景)
1747
2368
 
1748
- if (_this.batchQueue.length > _this.initConfig.batchMaxSize * 2) {
1749
- _this.batchQueue = _this.batchQueue.slice(0, _this.initConfig.batchMaxSize);
1750
- } // 保存失败的数据到 LocalStorage
2369
+ document.addEventListener("visibilitychange", function () {
2370
+ if (_this.isPageUnloading()) {
2371
+ _this.flushPendingData();
2372
+ }
2373
+ }); // 使用 beforeunload 事件作为备用(页面关闭/刷新)
1751
2374
 
2375
+ window.addEventListener("beforeunload", function () {
2376
+ _this.flushPendingData();
2377
+ }); // 使用 pagehide 事件(更可靠,支持移动端)
1752
2378
 
1753
- _this.saveBatchQueueToStorage();
1754
- }
1755
- });
1756
- };
1757
- /**
1758
- * 添加到批量队列
1759
- * @param params 数据参数
1760
- */
2379
+ window.addEventListener("pagehide", function () {
2380
+ _this.flushPendingData();
2381
+ });
2382
+ }; // 标记是否正在刷新待发送数据,避免重复发送
1761
2383
 
1762
2384
 
1763
- _this.addToBatchQueue = function (params) {
1764
- var _a = _this.initConfig,
1765
- batchInterval = _a.batchInterval,
1766
- batchMaxSize = _a.batchMaxSize;
2385
+ _this.isFlushingPendingData = false;
2386
+ /**
2387
+ * 刷新待发送数据(在页面卸载/跳转时调用)
2388
+ */
1767
2389
 
1768
- _this.batchQueue.push(params); // 保存到 LocalStorage
2390
+ _this.flushPendingData = function () {
2391
+ // 如果正在刷新,避免重复执行
2392
+ if (_this.isFlushingPendingData) {
2393
+ return;
2394
+ } // 页面卸载时停止定时器
1769
2395
 
1770
2396
 
1771
- _this.saveBatchQueueToStorage(); // 如果队列达到最大数量,立即发送
2397
+ _this.stopPageDurationTimer(); // 收集所有待发送的数据
1772
2398
 
1773
2399
 
1774
- if (_this.batchQueue.length >= batchMaxSize) {
1775
- _this.flushBatchQueue();
2400
+ var allPendingData = []; // 如果有批量队列,添加到待发送列表
1776
2401
 
1777
- return;
1778
- } // 设置定时发送
2402
+ var batchQueue = _this.getBatchQueueFromStorage();
1779
2403
 
2404
+ if (batchQueue.length > 0) {
2405
+ allPendingData.push.apply(allPendingData, batchQueue);
2406
+ } // 如果有待发送的单个请求,也添加到列表
1780
2407
 
1781
- if (!_this.batchTimer) {
1782
- _this.batchTimer = window.setTimeout(function () {
1783
- _this.flushBatchQueue();
1784
2408
 
1785
- _this.batchTimer = null;
1786
- }, batchInterval);
1787
- }
1788
- };
1789
- /**
1790
- * 从 LocalStorage 恢复批量队列
1791
- */
2409
+ var pendingRequests = _this.getPendingRequestsFromStorage();
1792
2410
 
2411
+ if (pendingRequests.length > 0) {
2412
+ allPendingData.push.apply(allPendingData, pendingRequests);
2413
+ }
1793
2414
 
1794
- _this.restoreBatchQueueFromStorage = function () {
1795
- try {
1796
- var storedQueue = _this.getLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY);
2415
+ if (allPendingData.length === 0) {
2416
+ return;
2417
+ } // 标记正在刷新
1797
2418
 
1798
- if (storedQueue) {
1799
- var parsedQueue = JSON.parse(storedQueue);
1800
2419
 
1801
- if (Array.isArray(parsedQueue) && parsedQueue.length > 0) {
1802
- _this.batchQueue = parsedQueue;
2420
+ _this.isFlushingPendingData = true; // 先保存到 LocalStorage,确保数据不丢失(在发送前保存)
1803
2421
 
1804
- if (_this.initConfig.showLog) {
1805
- _this.printLog("\u4ECE LocalStorage \u6062\u590D " + parsedQueue.length + " \u6761\u5F85\u53D1\u9001\u6570\u636E");
1806
- } // 恢复后立即尝试发送(如果达到条件)
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));
2427
+ }
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 发送数据(最可靠的方式)
1807
2433
 
1808
2434
 
1809
- var batchMaxSize = _this.initConfig.batchMaxSize;
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);
1810
2443
 
1811
- if (_this.batchQueue.length >= batchMaxSize) {
1812
- _this.flushBatchQueue();
1813
- } else {
1814
- // 设置定时发送
1815
- var batchInterval = _this.initConfig.batchInterval;
2444
+ if (sent) {
2445
+ // 发送成功,清除所有 LocalStorage
2446
+ _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
1816
2447
 
1817
- if (!_this.batchTimer) {
1818
- _this.batchTimer = window.setTimeout(function () {
1819
- _this.flushBatchQueue();
2448
+ _this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, "[]");
1820
2449
 
1821
- _this.batchTimer = null;
1822
- }, batchInterval);
1823
- }
1824
- }
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");
1825
2457
  }
1826
2458
  }
1827
2459
  } catch (e) {
1828
- _this.printLog("\u6062\u590D\u6279\u91CF\u961F\u5217\u5931\u8D25: " + e); // 如果解析失败,清除损坏的数据
1829
-
1830
-
1831
- _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
2460
+ // sendBeacon 失败,数据已在 LocalStorage 中,等待下次恢复
2461
+ if (_this.initConfig.showLog) {
2462
+ _this.printLog("\u9875\u9762\u5378\u8F7D\u65F6\u53D1\u9001\u6570\u636E\u5931\u8D25: " + e + "\uFF0C\u6570\u636E\u5DF2\u4FDD\u5B58\u5230 LocalStorage");
2463
+ }
2464
+ } finally {
2465
+ // 重置标记
2466
+ _this.isFlushingPendingData = false;
1832
2467
  }
1833
- };
1834
- /**
1835
- * 保存批量队列到 LocalStorage
1836
- */
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
+ } // 重置标记
1837
2473
 
1838
2474
 
1839
- _this.saveBatchQueueToStorage = function () {
1840
- try {
1841
- var queueString = JSON.stringify(_this.batchQueue); // 检查存储大小,避免超出 LocalStorage 限制(通常 5-10MB)
1842
- // 如果队列过大,只保留最新的数据
2475
+ _this.isFlushingPendingData = false;
2476
+ }
2477
+ };
2478
+ /**
2479
+ * 发送数据通用函数
2480
+ */
1843
2481
 
1844
- if (queueString.length > 4 * 1024 * 1024) {
1845
- // 4MB 限制
1846
- var maxItems = Math.floor(_this.batchQueue.length * 0.8); // 保留 80%
1847
2482
 
1848
- _this.batchQueue = _this.batchQueue.slice(-maxItems);
2483
+ _this.sendData = function (params, header) {
2484
+ // 数据采样判断
2485
+ if (!_this.shouldSample()) {
2486
+ return Promise.resolve({
2487
+ success: true,
2488
+ message: "数据已采样跳过"
2489
+ });
2490
+ }
1849
2491
 
1850
- _this.printLog("\u961F\u5217\u8FC7\u5927\uFF0C\u5DF2\u622A\u65AD\u4FDD\u7559\u6700\u65B0 " + maxItems + " \u6761\u6570\u636E");
1851
- }
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
1852
2510
 
1853
- _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, JSON.stringify(_this.batchQueue));
1854
- } catch (e) {
1855
- // LocalStorage 可能已满或不可用
1856
- _this.printLog("\u4FDD\u5B58\u6279\u91CF\u961F\u5217\u5230 LocalStorage \u5931\u8D25: " + e);
1857
- }
1858
- };
1859
- /**
1860
- * 设置页面卸载监听器,保存队列
1861
- */
1862
2511
 
2512
+ var shouldUseBeacon = _this.shouldUseBeacon(sendMethod, header, initHeader); // 如果使用 sendBeacon
1863
2513
 
1864
- _this.setupBeforeUnloadListener = function () {
1865
- // 使用 visibilitychange 事件(更可靠)
1866
- document.addEventListener("visibilitychange", function () {
1867
- if (document.visibilityState === "hidden" && _this.batchQueue.length > 0) {
1868
- _this.saveBatchQueueToStorage();
1869
- }
1870
- }); // 使用 beforeunload 事件作为备用
1871
-
1872
- window.addEventListener("beforeunload", function () {
1873
- if (_this.batchQueue.length > 0) {
1874
- // 使用 sendBeacon 尝试发送数据(如果支持)
1875
- if (navigator.sendBeacon && _this.initConfig.serverUrl) {
1876
- try {
1877
- var data = JSON.stringify({
1878
- events: _this.batchQueue
1879
- });
1880
- var blob = new Blob([data], {
1881
- type: "application/json"
1882
- });
1883
- navigator.sendBeacon(_this.initConfig.serverUrl, blob); // 如果 sendBeacon 成功,清除队列
1884
-
1885
- _this.batchQueue = [];
1886
-
1887
- _this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
1888
- } catch (e) {
1889
- // sendBeacon 失败,保存到 LocalStorage
1890
- _this.saveBatchQueueToStorage();
1891
- }
1892
- } else {
1893
- // 不支持 sendBeacon,保存到 LocalStorage
1894
- _this.saveBatchQueueToStorage();
1895
- }
1896
- }
1897
- });
1898
- };
1899
- /**
1900
- * 发送数据通用函数
1901
- */
1902
2514
 
2515
+ if (shouldUseBeacon) {
2516
+ // 检查页面是否即将卸载,如果是,直接使用 sendBeacon 发送,避免被取消
2517
+ if (_this.isPageUnloading()) {
2518
+ var sent = _this.sendWithBeacon(params, serverUrl, contentType);
1903
2519
 
1904
- _this.sendData = function (params, header) {
1905
- // 数据采样判断
1906
- if (!_this.shouldSample()) {
1907
- return Promise.resolve({
1908
- success: true,
1909
- message: "数据已采样跳过"
1910
- });
1911
- }
2520
+ if (sent) {
2521
+ return Promise.resolve({
2522
+ success: true,
2523
+ message: "页面卸载时发送成功"
2524
+ });
2525
+ } else {
2526
+ // sendBeacon 返回 false,添加到待发送队列
2527
+ _this.addToPendingRequests(params);
1912
2528
 
1913
- var _a = _this.initConfig,
1914
- serverUrl = _a.serverUrl,
1915
- sendTimeout = _a.sendTimeout,
1916
- contentType = _a.contentType,
1917
- showLog = _a.showLog,
1918
- initHeader = _a.header,
1919
- batchSend = _a.batchSend;
1920
- if (!!showLog) _this.printLog(params); // 如果启用批量发送
2529
+ return Promise.resolve({
2530
+ success: true,
2531
+ message: "已添加到待发送队列"
2532
+ });
2533
+ }
2534
+ } // 正常情况使用 sendBeacon
1921
2535
 
1922
- if (batchSend) {
1923
- _this.addToBatchQueue(params);
2536
+
2537
+ return _this.sendBeacon({
2538
+ contentType: contentType,
2539
+ url: serverUrl,
2540
+ data: params
2541
+ }).catch(function (err) {
2542
+ // sendBeacon 失败,添加到待发送队列,避免数据丢失
2543
+ _this.addToPendingRequests(params);
1924
2544
 
1925
2545
  return Promise.resolve({
1926
2546
  success: true,
1927
- message: "已添加到批量队列"
2547
+ message: "sendBeacon 失败,已添加到待发送队列"
1928
2548
  });
1929
- }
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);
2556
+
2557
+ if (sent) {
2558
+ resolve({
2559
+ success: true,
2560
+ message: "页面卸载时使用 sendBeacon 发送成功"
2561
+ });
2562
+ return;
2563
+ } // sendBeacon 失败,继续使用 XMLHttpRequest
1930
2564
 
1931
- if (_this.isSupportBeaconSend() === true && !header && !initHeader) {
1932
- return _this.sendBeacon({
1933
- contentType: contentType,
2565
+ }
2566
+
2567
+ _this.ajax({
2568
+ header: header || initHeader,
1934
2569
  url: serverUrl,
1935
- data: params
1936
- });
1937
- } else {
1938
- return new Promise(function (resolve, reject) {
1939
- _this.ajax({
1940
- header: header || initHeader,
1941
- url: serverUrl,
1942
- type: "POST",
1943
- data: JSON.stringify(params),
1944
- contentType: contentType,
1945
- credentials: false,
1946
- timeout: sendTimeout,
1947
- cors: true,
1948
- success: function success(res) {
1949
- return resolve({
1950
- success: true,
1951
- data: res
1952
- });
1953
- },
1954
- error: function error(err, status) {
1955
- return reject({
1956
- success: false,
1957
- message: String(err),
1958
- code: status
1959
- });
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
+ }
1960
2594
  }
1961
- });
2595
+
2596
+ reject({
2597
+ success: false,
2598
+ message: String(err),
2599
+ code: status
2600
+ });
2601
+ }
1962
2602
  });
1963
- }
1964
- };
1965
- /**
1966
- * @description 留存时长上报
1967
- * @param type
1968
- */
2603
+ });
2604
+ }
2605
+ };
2606
+ /**
2607
+ * @description 判断是否应该使用 sendBeacon
2608
+ * @param sendMethod 配置的发送方式
2609
+ * @param header 自定义 header
2610
+ * @param initHeader 初始化配置的 header
2611
+ * @returns 是否使用 sendBeacon
2612
+ */
2613
+
2614
+
2615
+ _this.shouldUseBeacon = function (sendMethod, header, initHeader) {
2616
+ // 如果配置为 xhr,不使用 beacon
2617
+ if (sendMethod === 'xhr') {
2618
+ return false;
2619
+ } // 如果配置为 beacon,检查是否支持
2620
+
2621
+
2622
+ if (sendMethod === 'beacon') {
2623
+ return _this.isSupportBeaconSend() === true;
2624
+ } // 如果配置为 auto(默认),使用原有逻辑
2625
+ // 只有在支持 sendBeacon 且没有自定义 header 时才使用
1969
2626
 
1970
2627
 
1971
- _this.sendRetained = function (type) {
1972
- var params = _this.getParams({
1973
- event: "PageRetained",
1974
- desc: _this.eventDescMap["PageRetained"]
2628
+ return _this.isSupportBeaconSend() === true && !header && !initHeader;
2629
+ };
2630
+ /**
2631
+ * @description 留存时长上报
2632
+ * @param type
2633
+ */
2634
+
2635
+
2636
+ _this.sendRetained = function (type) {
2637
+ var params = _this.getParams({
2638
+ event: "PageRetained",
2639
+ desc: _this.eventDescMap["PageRetained"]
2640
+ });
2641
+
2642
+ if (["beforeunload", "pushState", "replaceState", "hashchange", "popstate"].indexOf(type) >= 0) {
2643
+ var __time = _this.getCookie("retainedStartTime");
2644
+
2645
+ var retainedStartTime = __time ? +__time : _this.getTimeStamp();
2646
+
2647
+ var retainedData = __assign(__assign({}, params), {
2648
+ privateParamMap: __assign(__assign({}, params.privateParamMap), {
2649
+ retainedDuration: Math.max(params.requestTime - retainedStartTime, 0)
2650
+ })
1975
2651
  });
1976
2652
 
1977
- if (["beforeunload", "pushState", "replaceState", "hashchange", "popstate"].indexOf(type) >= 0) {
1978
- var __time = _this.getCookie("retainedStartTime");
2653
+ _this.sendData(retainedData);
2654
+
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
+ */
2665
+
1979
2666
 
1980
- var retainedStartTime = __time ? +__time : _this.getTimeStamp();
2667
+ _this.trackPageDuration = function (duration, options, resetStartTime) {
2668
+ if (resetStartTime === void 0) {
2669
+ resetStartTime = true;
2670
+ } // 计算停留时长
1981
2671
 
1982
- var retainedData = __assign(__assign({}, params), {
1983
- privateParamMap: __assign(__assign({}, params.privateParamMap), {
1984
- retainedDuration: Math.max(params.requestTime - retainedStartTime, 0)
1985
- })
1986
- });
1987
2672
 
1988
- _this.sendData(retainedData);
2673
+ var retainedDuration;
1989
2674
 
1990
- _this.setCookie("retainedStartTime", _this.getTimeStamp());
2675
+ if (duration !== undefined && duration !== null) {
2676
+ // 使用自定义时长
2677
+ retainedDuration = Math.max(duration, 0);
2678
+ } else {
2679
+ // 自动计算时长
2680
+ var __time = _this.getCookie("retainedStartTime");
2681
+
2682
+ var retainedStartTime = __time ? +__time : _this.getTimeStamp();
2683
+
2684
+ var currentTime = _this.getTimeStamp();
2685
+
2686
+ retainedDuration = Math.max(currentTime - retainedStartTime, 0);
2687
+ } // 构建参数
2688
+
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;
2694
+
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
1991
2702
  }
1992
- };
1993
- /**
1994
- * @description 获取 itemKey
1995
- * @param {[string]} partkey [控件/自定义事件的唯一标识]
1996
- * @return {[string]}
1997
- */
2703
+ }); // 上报数据
1998
2704
 
1999
2705
 
2000
- _this.getItemKey = function (partkey, pageKey) {
2001
- var appKey = _this.initConfig.appKey;
2002
- var keys = [appKey, (pageKey || _this.pageKey).toString(), partkey ? partkey.toString() : undefined].filter(function (key) {
2003
- return !!key;
2004
- });
2005
- return keys.reduce(function (str, key) {
2006
- return str + ("" + (str.length ? "." : "")) + key;
2007
- }, "");
2008
- };
2706
+ var result = _this.sendData(params, header); // 根据 resetStartTime 参数决定是否重置起始时间
2707
+ // 手动上报后重置起始时间,定时上报不重置(累积计算)
2009
2708
 
2010
- _this.sdkVersion = "1.2.0"; // sdk版本
2011
-
2012
- _this.initConfig = {
2013
- appKey: "",
2014
- platform: undefined,
2015
- showLog: false,
2016
- serverUrl: "",
2017
- autoTrack: false,
2018
- sendTimeout: 3000,
2019
- isTrackSinglePage: false,
2020
- contentType: "application/json",
2021
- business: {},
2022
- header: undefined,
2023
- sampleRate: 1,
2024
- batchSend: false,
2025
- batchInterval: 5000,
2026
- batchMaxSize: 10 // 批量发送最大数量
2027
-
2028
- }; // 系统信息
2029
-
2030
- _this.systemsInfo = {};
2031
- return _this;
2032
- }
2709
+
2710
+ if (resetStartTime) {
2711
+ _this.setCookie("retainedStartTime", _this.getTimeStamp());
2712
+ }
2713
+
2714
+ return result;
2715
+ };
2033
2716
  /**
2034
- * @description 添加单页面监听事件
2035
- * @param callback
2717
+ * @description 启动定时上报页面停留时长的定时器
2036
2718
  */
2037
2719
 
2038
2720
 
2039
- WebTracking.prototype.addSinglePageEvent = function (callback) {
2040
- var _this = this;
2721
+ _this.startPageDurationTimer = function () {
2722
+ // 先停止现有的定时器(避免重复启动)
2723
+ _this.stopPageDurationTimer();
2041
2724
 
2042
- var historyPushState = window.history.pushState;
2043
- var singlePageEvent = historyPushState ? "popstate" : "hashchange";
2044
- this.each(["pushState", "replaceState", singlePageEvent], function (historyType) {
2045
- _this.addEventListener(window, historyType, callback);
2725
+ var interval = _this.initConfig.pageDurationInterval || 30000; // 检查间隔时间是否有效
2726
+
2727
+ if (interval <= 0) {
2728
+ if (_this.initConfig.showLog) {
2729
+ _this.printLog("定时上报间隔时间无效,已禁用定时上报");
2730
+ }
2731
+
2732
+ return;
2733
+ } // 启动定时器
2734
+
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);
2749
+ }
2750
+ });
2751
+ }
2752
+ }, interval);
2753
+
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
+ */
2761
+
2762
+
2763
+ _this.stopPageDurationTimer = function () {
2764
+ if (_this.pageDurationTimer !== null) {
2765
+ clearInterval(_this.pageDurationTimer);
2766
+ _this.pageDurationTimer = null;
2767
+
2768
+ if (_this.initConfig.showLog) {
2769
+ _this.printLog("定时上报页面停留时长已停止");
2770
+ }
2771
+ }
2772
+ };
2773
+ /**
2774
+ * @description 获取 itemKey
2775
+ * @param {[string]} partkey [控件/自定义事件的唯一标识]
2776
+ * @return {[string]}
2777
+ */
2778
+
2779
+
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;
2046
2784
  });
2785
+ return keys.reduce(function (str, key) {
2786
+ return str + ("" + (str.length ? "." : "")) + key;
2787
+ }, "");
2047
2788
  };
2048
2789
 
2049
- return WebTracking;
2050
- }(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);
2051
2836
 
2052
- var index = new WebTracking();
2837
+ var index = new WebTracking();
2053
2838
 
2054
- return index;
2839
+ return index;
2055
2840
 
2056
2841
  })));