@fle-sdk/event-tracking-web 1.2.1 → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +246 -2
- package/lib/index.esm.js +1002 -217
- package/lib/index.esm.min.js +1 -1
- package/lib/index.js +2395 -1610
- package/lib/index.min.js +1 -1
- package/lib/types/index.d.ts +94 -7
- package/lib/types/tools.d.ts +2 -0
- package/lib/types/type.d.ts +61 -0
- package/package.json +2 -3
package/lib/index.js
CHANGED
|
@@ -1,2056 +1,2841 @@
|
|
|
1
1
|
(function (global, factory) {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\//g, "/");
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
123
|
-
|
|
124
|
-
|
|
1010
|
+
this.getPluginsFingerprint = function () {
|
|
1011
|
+
try {
|
|
1012
|
+
var plugins = [];
|
|
125
1013
|
|
|
126
|
-
|
|
127
|
-
|
|
1014
|
+
if (navigator.plugins) {
|
|
1015
|
+
for (var i = 0; i < navigator.plugins.length; i++) {
|
|
1016
|
+
var plugin = navigator.plugins[i];
|
|
128
1017
|
|
|
129
|
-
|
|
1018
|
+
if (plugin) {
|
|
1019
|
+
plugins.push(plugin.name + "|" + plugin.description + "|" + plugin.filename);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
130
1023
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
1024
|
+
return plugins.join(";");
|
|
1025
|
+
} catch (e) {
|
|
1026
|
+
return "error";
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
/**
|
|
1030
|
+
* 检测localStorage支持
|
|
1031
|
+
* @returns 是否支持localStorage
|
|
1032
|
+
*/
|
|
137
1033
|
|
|
138
1034
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
182
|
-
|
|
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
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
235
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
278
|
-
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
1125
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
1126
|
+
rest[_i] = arguments[_i];
|
|
1127
|
+
}
|
|
288
1128
|
|
|
289
|
-
|
|
290
|
-
|
|
1129
|
+
if (this.isObject(rest[0])) {
|
|
1130
|
+
rest[0] = this.formatJsonString(rest[0]);
|
|
1131
|
+
}
|
|
291
1132
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
1154
|
+
WebTrackingTools.prototype.isUndefined = function (obj) {
|
|
1155
|
+
return obj === void 0;
|
|
1156
|
+
};
|
|
309
1157
|
|
|
310
|
-
|
|
311
|
-
|
|
1158
|
+
WebTrackingTools.prototype.isString = function (obj) {
|
|
1159
|
+
return toString.call(obj) == "[object String]";
|
|
1160
|
+
};
|
|
312
1161
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
1162
|
+
WebTrackingTools.prototype.isDate = function (obj) {
|
|
1163
|
+
return toString.call(obj) == "[object Date]";
|
|
1164
|
+
};
|
|
316
1165
|
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
}
|
|
1166
|
+
WebTrackingTools.prototype.isBoolean = function (obj) {
|
|
1167
|
+
return toString.call(obj) == "[object Boolean]";
|
|
1168
|
+
};
|
|
321
1169
|
|
|
322
|
-
|
|
323
|
-
|
|
1170
|
+
WebTrackingTools.prototype.isNumber = function (obj) {
|
|
1171
|
+
return toString.call(obj) == "[object Number]" && /[\d\.]+/.test(String(obj));
|
|
1172
|
+
};
|
|
324
1173
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
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
|
-
|
|
335
|
-
|
|
1178
|
+
WebTrackingTools.prototype.isFunction = function (f) {
|
|
1179
|
+
if (!f) {
|
|
1180
|
+
return false;
|
|
1181
|
+
}
|
|
336
1182
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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
|
-
|
|
344
|
-
|
|
1187
|
+
WebTrackingTools.prototype.isJSONString = function (str) {
|
|
1188
|
+
if (!this.isString(str)) return false;
|
|
345
1189
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
}
|
|
1190
|
+
try {
|
|
1191
|
+
JSON.parse(str);
|
|
1192
|
+
} catch (e) {
|
|
1193
|
+
return false;
|
|
1194
|
+
}
|
|
353
1195
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
var valid_domain = "";
|
|
1196
|
+
return true;
|
|
1197
|
+
};
|
|
357
1198
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
}
|
|
1199
|
+
WebTrackingTools.prototype._decodeURIComponent = function (val) {
|
|
1200
|
+
var result = val;
|
|
361
1201
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
1202
|
+
try {
|
|
1203
|
+
result = decodeURIComponent(val);
|
|
1204
|
+
} catch (e) {
|
|
1205
|
+
result = val;
|
|
1206
|
+
}
|
|
365
1207
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
1208
|
+
return result;
|
|
1209
|
+
};
|
|
369
1210
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
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
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
|
|
396
|
-
|
|
397
|
-
localStorage.setItem(key, value);
|
|
398
|
-
} catch (e) {// 静默失败
|
|
399
|
-
}
|
|
400
|
-
};
|
|
1233
|
+
return domain;
|
|
1234
|
+
};
|
|
401
1235
|
|
|
402
|
-
|
|
403
|
-
|
|
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
|
-
|
|
412
|
-
|
|
413
|
-
};
|
|
414
|
-
/**
|
|
415
|
-
* 获取 UUID
|
|
416
|
-
* @return {string} [UUID唯一值]
|
|
417
|
-
*/
|
|
1244
|
+
function WebTracking() {
|
|
1245
|
+
var _this = _super.call(this) || this; // 批量发送定时器
|
|
418
1246
|
|
|
419
1247
|
|
|
420
|
-
|
|
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
|
-
|
|
434
|
-
var distinctId = _this.getCookie("distinctId");
|
|
1252
|
+
_this.useCustomPageKey = false; // 页面卸载监听器是否已设置
|
|
435
1253
|
|
|
436
|
-
|
|
437
|
-
distinctId = _this.uuid();
|
|
1254
|
+
_this.isUnloadListenerSetup = false; // 定时上报页面停留时长的定时器
|
|
438
1255
|
|
|
439
|
-
|
|
1256
|
+
_this.pageDurationTimer = null; // LocalStorage 存储 key(待发送请求)
|
|
440
1257
|
|
|
441
|
-
|
|
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
|
-
|
|
452
|
-
if (sensitiveKeys === void 0) {
|
|
453
|
-
sensitiveKeys = ['password', 'token', 'secret', 'key'];
|
|
454
|
-
}
|
|
1262
|
+
_this.MAX_STORAGE_SIZE = 4 * 1024 * 1024; // 用户信息
|
|
455
1263
|
|
|
456
|
-
|
|
457
|
-
var filteredObj = {};
|
|
1264
|
+
_this.userInfo = null; // 当前路由
|
|
458
1265
|
|
|
459
|
-
|
|
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
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
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
|
-
|
|
485
|
-
|
|
486
|
-
if (typeof str !== 'string') return str.toString();
|
|
487
|
-
return str.replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g, '/');
|
|
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
|
-
|
|
496
|
-
var _href = decodeURI(window.location.href);
|
|
1297
|
+
_this.systemsInfo = _this.getSystemsInfo(initParams.platform); // 获取设备ID
|
|
497
1298
|
|
|
498
|
-
|
|
499
|
-
var paramsObj = {};
|
|
1299
|
+
_this.deviceId = _this.getDeviceId(); // 如果传入了 userInfo,设置用户信息
|
|
500
1300
|
|
|
501
|
-
|
|
502
|
-
|
|
1301
|
+
if (initParams.userInfo && _this.isObject(initParams.userInfo)) {
|
|
1302
|
+
_this.userInfo = initParams.userInfo;
|
|
1303
|
+
}
|
|
503
1304
|
|
|
504
|
-
|
|
1305
|
+
_this.setCookie("retainedStartTime", _this.getTimeStamp()); // 如果启用了批量发送,从 LocalStorage 恢复队列
|
|
505
1306
|
|
|
506
|
-
paramsObj[pair[0]] = pair[1];
|
|
507
|
-
}
|
|
508
1307
|
|
|
509
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
545
|
-
|
|
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
|
-
|
|
549
|
-
|
|
550
|
-
|
|
1344
|
+
_this.each(presetParams, function (val, key) {
|
|
1345
|
+
// 跳过 pageKey,因为已经单独处理
|
|
1346
|
+
if (key === 'pageKey') return;
|
|
551
1347
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
1348
|
+
if (_this.initConfig.hasOwnProperty(key)) {
|
|
1349
|
+
// 参数验证
|
|
1350
|
+
var validationResult = _this.validateConfigParam(String(key), val);
|
|
555
1351
|
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
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
|
-
|
|
566
|
-
|
|
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
|
-
|
|
576
|
-
|
|
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
|
-
|
|
585
|
-
|
|
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
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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
|
-
|
|
1401
|
+
break;
|
|
616
1402
|
|
|
617
|
-
|
|
618
|
-
if (
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
-
|
|
629
|
-
if (!para.cors) {
|
|
630
|
-
g.setRequestHeader && g.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
|
631
|
-
}
|
|
1411
|
+
break;
|
|
632
1412
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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
|
-
|
|
644
|
-
};
|
|
1421
|
+
break;
|
|
645
1422
|
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
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
|
-
|
|
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
|
-
|
|
688
|
-
|
|
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
|
-
|
|
1441
|
+
break;
|
|
692
1442
|
|
|
693
|
-
|
|
1443
|
+
case 'pageDurationInterval':
|
|
1444
|
+
if (typeof value !== 'number' || value <= 0) {
|
|
1445
|
+
return {
|
|
1446
|
+
valid: false,
|
|
1447
|
+
message: 'pageDurationInterval 必须是大于 0 的数字'
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
694
1450
|
|
|
695
|
-
|
|
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
|
-
|
|
702
|
-
|
|
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
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
if (
|
|
714
|
-
|
|
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
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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
|
-
|
|
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
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
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
|
-
|
|
767
|
-
var timeout = null;
|
|
768
|
-
return function () {
|
|
769
|
-
var args = [];
|
|
1497
|
+
break;
|
|
770
1498
|
|
|
771
|
-
|
|
772
|
-
|
|
1499
|
+
case 'platform':
|
|
1500
|
+
if (typeof value !== 'string') {
|
|
1501
|
+
return {
|
|
1502
|
+
valid: false,
|
|
1503
|
+
message: 'platform 必须是字符串'
|
|
1504
|
+
};
|
|
773
1505
|
}
|
|
774
1506
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
}
|
|
1507
|
+
break;
|
|
1508
|
+
}
|
|
778
1509
|
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
}, wait);
|
|
782
|
-
};
|
|
1510
|
+
return {
|
|
1511
|
+
valid: true
|
|
783
1512
|
};
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
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
|
-
|
|
829
|
-
|
|
830
|
-
|
|
1528
|
+
_this.getDeviceId = function () {
|
|
1529
|
+
// 如果已有设备ID,直接返回
|
|
1530
|
+
if (_this.deviceId) {
|
|
1531
|
+
return _this.deviceId;
|
|
1532
|
+
} // 获取已存储的设备ID
|
|
831
1533
|
|
|
832
1534
|
|
|
833
|
-
|
|
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
|
-
|
|
845
|
-
};
|
|
846
|
-
/**
|
|
847
|
-
* 收集浏览器指纹信息
|
|
848
|
-
* @returns 返回浏览器指纹对象
|
|
849
|
-
*/
|
|
1549
|
+
_this.setCookie("device_id", deviceId, 365 * 2); // 存储2年
|
|
850
1550
|
|
|
851
1551
|
|
|
852
|
-
|
|
853
|
-
var fingerprint = {}; // 1. 用户代理
|
|
1552
|
+
_this.setLocalStorage("device_id", deviceId);
|
|
854
1553
|
|
|
855
|
-
|
|
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
|
-
|
|
863
|
-
|
|
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
|
-
|
|
866
|
-
fingerprint.languages = Array.from(navigator.languages); // 5. 平台信息
|
|
1569
|
+
_this.deviceId = ""; // 重新生成设备ID
|
|
867
1570
|
|
|
868
|
-
|
|
1571
|
+
var newDeviceId = _this.getDeviceId();
|
|
869
1572
|
|
|
870
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
885
|
-
|
|
886
|
-
|
|
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
|
-
|
|
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
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
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
|
-
|
|
1002
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
/**
|
|
1036
|
-
* 检测localStorage支持
|
|
1037
|
-
* @returns 是否支持localStorage
|
|
1038
|
-
*/
|
|
1652
|
+
_this.stopPageDurationTimer();
|
|
1653
|
+
};
|
|
1654
|
+
/**
|
|
1655
|
+
* @description 清理批量发送定时器
|
|
1656
|
+
*/
|
|
1039
1657
|
|
|
1040
1658
|
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
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
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
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
|
-
|
|
1074
|
-
|
|
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
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
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
|
-
|
|
1099
|
-
|
|
1100
|
-
var fingerprintString = JSON.stringify(fingerprint, Object.keys(fingerprint).sort()); // 使用更强大的哈希算法生成ID
|
|
1698
|
+
_this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
|
|
1699
|
+
}
|
|
1101
1700
|
|
|
1102
|
-
|
|
1701
|
+
return [];
|
|
1702
|
+
};
|
|
1703
|
+
/**
|
|
1704
|
+
* @description 保存批量队列到 LocalStorage
|
|
1705
|
+
* @param queue 批量队列数组
|
|
1706
|
+
*/
|
|
1103
1707
|
|
|
1104
|
-
var hash2 = 52711; // 第二个哈希的初始值
|
|
1105
1708
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1709
|
+
_this.saveBatchQueueToStorage = function (queue) {
|
|
1710
|
+
try {
|
|
1711
|
+
var queueString = JSON.stringify(queue); // 检查存储大小,避免超出 LocalStorage 限制
|
|
1109
1712
|
|
|
1110
|
-
|
|
1111
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1117
|
-
|
|
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
|
-
*
|
|
1730
|
+
* @description 从 LocalStorage 获取待发送请求队列
|
|
1731
|
+
* @returns 待发送请求队列数组
|
|
1122
1732
|
*/
|
|
1123
1733
|
|
|
1124
1734
|
|
|
1125
|
-
|
|
1126
|
-
|
|
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
|
-
|
|
1133
|
-
|
|
1134
|
-
}
|
|
1739
|
+
if (storedRequests) {
|
|
1740
|
+
var parsedRequests = JSON.parse(storedRequests);
|
|
1135
1741
|
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
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
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
}
|
|
1153
|
-
|
|
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
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1776
|
+
_this.setPageKey = function (pageKey, autoUpdate) {
|
|
1777
|
+
if (autoUpdate === void 0) {
|
|
1778
|
+
autoUpdate = false;
|
|
1779
|
+
}
|
|
1164
1780
|
|
|
1165
|
-
|
|
1166
|
-
|
|
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
|
-
|
|
1170
|
-
|
|
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
|
-
|
|
1174
|
-
|
|
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
|
-
|
|
1178
|
-
return
|
|
1805
|
+
_this.getPageKey = function () {
|
|
1806
|
+
return _this.pageKey;
|
|
1179
1807
|
};
|
|
1180
1808
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1194
|
-
|
|
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
|
-
|
|
1203
|
-
var result = val;
|
|
1848
|
+
var pendingRequests = _this.getPendingRequestsFromStorage();
|
|
1204
1849
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
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
|
-
|
|
1212
|
-
};
|
|
1856
|
+
var ORGIN = window.location.origin;
|
|
1213
1857
|
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
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
|
-
|
|
1867
|
+
_this.currentUrl = window.location.href; // 如果使用自定义 pageKey,路由变化时不自动更新
|
|
1223
1868
|
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
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
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1873
|
+
|
|
1874
|
+
if (_this.initConfig.autoTrackPageDurationInterval) {
|
|
1875
|
+
_this.stopPageDurationTimer();
|
|
1876
|
+
|
|
1877
|
+
_this.startPageDurationTimer();
|
|
1234
1878
|
}
|
|
1235
1879
|
|
|
1236
|
-
|
|
1237
|
-
};
|
|
1880
|
+
_this.sendRetained(e.type);
|
|
1238
1881
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1882
|
+
_this.sendData(params);
|
|
1883
|
+
};
|
|
1241
1884
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
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
|
-
|
|
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.
|
|
1901
|
+
var filteredPrivateParamMap = _this.filterSensitiveData(privateParamMap || {});
|
|
1252
1902
|
|
|
1253
|
-
_this.
|
|
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
|
-
|
|
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
|
-
|
|
1919
|
+
}; // 添加其他可能的属性
|
|
1260
1920
|
|
|
1261
|
-
|
|
1921
|
+
if (filteredPrivateParamMap.targetEle) {
|
|
1922
|
+
privateParamMapData.targetEle = filteredPrivateParamMap.targetEle;
|
|
1923
|
+
}
|
|
1262
1924
|
|
|
1263
|
-
|
|
1925
|
+
if (filteredPrivateParamMap.targetUrl) {
|
|
1926
|
+
privateParamMapData.targetUrl = filteredPrivateParamMap.targetUrl;
|
|
1927
|
+
}
|
|
1264
1928
|
|
|
1265
|
-
|
|
1929
|
+
if (filteredPrivateParamMap.pointerType) {
|
|
1930
|
+
privateParamMapData.pointerType = filteredPrivateParamMap.pointerType;
|
|
1931
|
+
}
|
|
1266
1932
|
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
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
|
-
|
|
1279
|
-
|
|
1937
|
+
if (filteredPrivateParamMap.retainedDuration) {
|
|
1938
|
+
privateParamMapData.retainedDuration = filteredPrivateParamMap.retainedDuration;
|
|
1939
|
+
}
|
|
1280
1940
|
|
|
1281
|
-
|
|
1282
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1966
|
+
_this.flushBatchQueue = function () {
|
|
1967
|
+
var batchQueue = _this.getBatchQueueFromStorage();
|
|
1297
1968
|
|
|
1969
|
+
if (batchQueue.length === 0) return;
|
|
1298
1970
|
|
|
1299
|
-
|
|
1300
|
-
_this.restoreBatchQueueFromStorage(); // 监听页面卸载事件,保存队列
|
|
1971
|
+
var currentTime = _this.getTimeStamp(); // 过滤出可以发送的数据(未到重试时间的不发送)
|
|
1301
1972
|
|
|
1302
1973
|
|
|
1303
|
-
|
|
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
|
-
|
|
1329
|
-
|
|
1330
|
-
if (key === 'pageKey') return;
|
|
1979
|
+
return item._nextRetryTime <= currentTime;
|
|
1980
|
+
});
|
|
1331
1981
|
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
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
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
_this.initConfig["showLog"] = true;
|
|
1343
|
-
} // 如果启用了全埋点
|
|
1987
|
+
return;
|
|
1988
|
+
} // 从队列中移除已准备发送的数据
|
|
1344
1989
|
|
|
1345
1990
|
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
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.
|
|
1360
|
-
if (_this.isObject(userInfo)) _this.userInfo = userInfo;
|
|
1361
|
-
};
|
|
1362
|
-
/**
|
|
1363
|
-
* 获取设备唯一标识
|
|
1364
|
-
* @returns 设备唯一标识
|
|
1365
|
-
*/
|
|
1999
|
+
_this.saveBatchQueueToStorage(remainingQueue); // 发送批量数据
|
|
1366
2000
|
|
|
1367
2001
|
|
|
1368
|
-
_this.
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
2002
|
+
_this.sendBatchData(readyToSend);
|
|
2003
|
+
};
|
|
2004
|
+
/**
|
|
2005
|
+
* 发送批量数据
|
|
2006
|
+
* @param data 批量数据
|
|
2007
|
+
*/
|
|
1373
2008
|
|
|
1374
2009
|
|
|
1375
|
-
|
|
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
|
-
|
|
1378
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1395
|
-
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
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
|
-
|
|
2092
|
+
_this.retryBatchData = function (data) {
|
|
2093
|
+
// 获取当前队列
|
|
2094
|
+
var currentQueue = _this.getBatchQueueFromStorage(); // 去重:基于事件类型、itemKey、requestTime 生成唯一键
|
|
1410
2095
|
|
|
1411
|
-
var newDeviceId = _this.getDeviceId();
|
|
1412
2096
|
|
|
1413
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1450
|
-
|
|
2107
|
+
var retryData = data.filter(function (item) {
|
|
2108
|
+
var key = getEventKey(item); // 检查是否已存在
|
|
1451
2109
|
|
|
1452
|
-
|
|
1453
|
-
|
|
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
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
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
|
-
|
|
1469
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1481
|
-
};
|
|
1482
|
-
/**
|
|
1483
|
-
* @description 清理批量发送定时器
|
|
1484
|
-
*/
|
|
2133
|
+
var retryQueue = __spreadArray(__spreadArray([], retryData), currentQueue); // 限制重试队列大小,避免内存溢出
|
|
1485
2134
|
|
|
1486
2135
|
|
|
1487
|
-
|
|
1488
|
-
|
|
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.
|
|
1499
|
-
_this.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1548
|
-
|
|
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
|
-
|
|
1564
|
-
|
|
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
|
-
|
|
1583
|
-
|
|
2184
|
+
_this.batchTimer = null;
|
|
2185
|
+
}, batchInterval);
|
|
2186
|
+
}
|
|
2187
|
+
};
|
|
2188
|
+
/**
|
|
2189
|
+
* 从 LocalStorage 恢复批量队列
|
|
2190
|
+
*/
|
|
1584
2191
|
|
|
1585
|
-
var ORGIN = window.location.origin;
|
|
1586
2192
|
|
|
1587
|
-
|
|
1588
|
-
|
|
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
|
-
|
|
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.
|
|
2202
|
+
var batchMaxSize = _this.initConfig.batchMaxSize;
|
|
1603
2203
|
|
|
1604
|
-
|
|
1605
|
-
|
|
2204
|
+
if (batchQueue.length >= batchMaxSize) {
|
|
2205
|
+
_this.flushBatchQueue();
|
|
2206
|
+
} else {
|
|
2207
|
+
// 设置定时发送
|
|
2208
|
+
var batchInterval = _this.initConfig.batchInterval;
|
|
1606
2209
|
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
1644
|
-
|
|
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
|
-
|
|
1648
|
-
|
|
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
|
-
|
|
1656
|
-
|
|
1657
|
-
}
|
|
2252
|
+
_this.restorePendingRequestsFromStorage = function () {
|
|
2253
|
+
var pendingRequests = _this.getPendingRequestsFromStorage();
|
|
1658
2254
|
|
|
1659
|
-
|
|
1660
|
-
|
|
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
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
};
|
|
1670
|
-
};
|
|
1671
|
-
/**
|
|
1672
|
-
* 数据采样判断
|
|
1673
|
-
* @returns 是否应该采样
|
|
1674
|
-
*/
|
|
2262
|
+
}
|
|
2263
|
+
};
|
|
2264
|
+
/**
|
|
2265
|
+
* 检查页面是否即将卸载
|
|
2266
|
+
* @returns 如果页面即将卸载返回 true,否则返回 false
|
|
2267
|
+
*/
|
|
1675
2268
|
|
|
1676
2269
|
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
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
|
-
|
|
2302
|
+
_this.flushPendingRequests = function () {
|
|
2303
|
+
var pendingRequests = _this.getPendingRequestsFromStorage();
|
|
1692
2304
|
|
|
1693
|
-
|
|
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
|
-
|
|
1707
|
-
|
|
1708
|
-
serverUrl = _a.serverUrl,
|
|
1709
|
-
contentType = _a.contentType,
|
|
1710
|
-
showLog = _a.showLog;
|
|
2326
|
+
return;
|
|
2327
|
+
}
|
|
1711
2328
|
|
|
1712
2329
|
if (showLog) {
|
|
1713
|
-
_this.printLog(
|
|
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:
|
|
2341
|
+
timeout: sendTimeout,
|
|
1731
2342
|
cors: true,
|
|
1732
2343
|
success: function success() {
|
|
1733
|
-
|
|
1734
|
-
|
|
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
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
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
|
-
|
|
2361
|
+
_this.setupBeforeUnloadListener = function () {
|
|
2362
|
+
// 避免重复设置监听器
|
|
2363
|
+
if (_this.isUnloadListenerSetup) {
|
|
2364
|
+
return;
|
|
2365
|
+
}
|
|
1746
2366
|
|
|
2367
|
+
_this.isUnloadListenerSetup = true; // 使用 visibilitychange 事件(更可靠,支持页面跳转、切换标签页等场景)
|
|
1747
2368
|
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
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
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
/**
|
|
1758
|
-
* 添加到批量队列
|
|
1759
|
-
* @param params 数据参数
|
|
1760
|
-
*/
|
|
2379
|
+
window.addEventListener("pagehide", function () {
|
|
2380
|
+
_this.flushPendingData();
|
|
2381
|
+
});
|
|
2382
|
+
}; // 标记是否正在刷新待发送数据,避免重复发送
|
|
1761
2383
|
|
|
1762
2384
|
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
2385
|
+
_this.isFlushingPendingData = false;
|
|
2386
|
+
/**
|
|
2387
|
+
* 刷新待发送数据(在页面卸载/跳转时调用)
|
|
2388
|
+
*/
|
|
1767
2389
|
|
|
1768
|
-
|
|
2390
|
+
_this.flushPendingData = function () {
|
|
2391
|
+
// 如果正在刷新,避免重复执行
|
|
2392
|
+
if (_this.isFlushingPendingData) {
|
|
2393
|
+
return;
|
|
2394
|
+
} // 页面卸载时停止定时器
|
|
1769
2395
|
|
|
1770
2396
|
|
|
1771
|
-
|
|
2397
|
+
_this.stopPageDurationTimer(); // 收集所有待发送的数据
|
|
1772
2398
|
|
|
1773
2399
|
|
|
1774
|
-
|
|
1775
|
-
_this.flushBatchQueue();
|
|
2400
|
+
var allPendingData = []; // 如果有批量队列,添加到待发送列表
|
|
1776
2401
|
|
|
1777
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
2415
|
+
if (allPendingData.length === 0) {
|
|
2416
|
+
return;
|
|
2417
|
+
} // 标记正在刷新
|
|
1797
2418
|
|
|
1798
|
-
if (storedQueue) {
|
|
1799
|
-
var parsedQueue = JSON.parse(storedQueue);
|
|
1800
2419
|
|
|
1801
|
-
|
|
1802
|
-
_this.batchQueue = parsedQueue;
|
|
2420
|
+
_this.isFlushingPendingData = true; // 先保存到 LocalStorage,确保数据不丢失(在发送前保存)
|
|
1803
2421
|
|
|
1804
|
-
|
|
1805
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
// 设置定时发送
|
|
1815
|
-
var batchInterval = _this.initConfig.batchInterval;
|
|
2444
|
+
if (sent) {
|
|
2445
|
+
// 发送成功,清除所有 LocalStorage
|
|
2446
|
+
_this.setLocalStorage(_this.BATCH_QUEUE_STORAGE_KEY, "[]");
|
|
1816
2447
|
|
|
1817
|
-
|
|
1818
|
-
_this.batchTimer = window.setTimeout(function () {
|
|
1819
|
-
_this.flushBatchQueue();
|
|
2448
|
+
_this.setLocalStorage(_this.PENDING_REQUESTS_STORAGE_KEY, "[]");
|
|
1820
2449
|
|
|
1821
|
-
|
|
1822
|
-
|
|
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
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
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
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
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
|
-
|
|
1923
|
-
|
|
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
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
2565
|
+
}
|
|
2566
|
+
|
|
2567
|
+
_this.ajax({
|
|
2568
|
+
header: header || initHeader,
|
|
1934
2569
|
url: serverUrl,
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
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
|
-
|
|
1967
|
-
|
|
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.
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
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
|
-
|
|
1978
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2673
|
+
var retainedDuration;
|
|
1989
2674
|
|
|
1990
|
-
|
|
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
|
-
|
|
2001
|
-
|
|
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
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
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
|
-
|
|
2040
|
-
|
|
2721
|
+
_this.startPageDurationTimer = function () {
|
|
2722
|
+
// 先停止现有的定时器(避免重复启动)
|
|
2723
|
+
_this.stopPageDurationTimer();
|
|
2041
2724
|
|
|
2042
|
-
var
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
_this.
|
|
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
|
-
|
|
2050
|
-
|
|
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
|
-
|
|
2837
|
+
var index = new WebTracking();
|
|
2053
2838
|
|
|
2054
|
-
|
|
2839
|
+
return index;
|
|
2055
2840
|
|
|
2056
2841
|
})));
|