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